Merge branch 'dark-statusbar'

Fixes for status bar and dialog grippers appearance in dark mode.

See #23602.
This commit is contained in:
Vadim Zeitlin 2023-06-03 22:20:32 +01:00
commit 4c94d5d1e5
16 changed files with 496 additions and 325 deletions

View file

@ -78,7 +78,7 @@ public:
// need to be called any more.
void UseHeaderThemeColors(HWND hwndHdr)
{
wxUxThemeHandle theme{hwndHdr, L"Header"};
auto theme = wxUxThemeHandle::NewAtStdDPI(hwndHdr, L"Header");
m_attr.SetTextColour(theme.GetColour(HP_HEADERITEM, TMT_TEXTCOLOR));

View file

@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/msw/private/custompaint.h
// Purpose: Helper function for customizing the standard WM_PAINT handling.
// Author: Vadim Zeitlin
// Created: 2023-06-02
// Copyright: (c) 2023 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_MSW_PRIVATE_CUSTOMPAINT_H_
#define _WX_MSW_PRIVATE_CUSTOMPAINT_H_
#include "wx/dcmemory.h"
#include "wx/image.h"
#include "wx/msw/dc.h"
namespace wxMSWImpl
{
// This function can be used as CustomPaint() callback to post process the
// bitmap by applying the specific functor to each of its pixels.
template <typename FuncProcessPixel>
wxBitmap
PostPaintEachPixel(const wxBitmap& bmp, FuncProcessPixel processPixel)
{
#if wxUSE_IMAGE
wxImage image = bmp.ConvertToImage();
unsigned char *data = image.GetData();
unsigned char *alpha = image.GetAlpha();
unsigned char alphaOpaque = wxALPHA_OPAQUE;
const int len = image.GetWidth()*image.GetHeight();
for ( int i = 0; i < len; ++i, data += 3 )
{
processPixel(data[0], data[1], data[2], alpha ? *alpha++ : alphaOpaque);
}
return wxBitmap(image);
#else // !wxUSE_IMAGE
return bmp;
#endif // wxUSE_IMAGE/!wxUSE_IMAGE
}
// This function uses the default WM_PAINT handler to paint the window contents
// into a bitmap and then the provided function to tweak the pixels of this
// bitmap.
//
// The first argument is a functor (typically a lambda) to paint the on the
// given HDC and the second one is another functor called to post process the
// bitmap before actually drawing it.
//
// It can only be called from WM_PAINT handler for a native control and assumes
// that this control handles WPARAM argument of WM_PAINT as HDC to paint on.
template <typename FuncDefPaint, typename FuncPostProcess>
void
CustomPaint(HWND hwnd, FuncDefPaint defPaint, FuncPostProcess postProcess)
{
const RECT rc = wxGetClientRect(hwnd);
const wxSize size{rc.right - rc.left, rc.bottom - rc.top};
// Don't bother doing anything with the empty windows.
if ( size == wxSize() )
return;
// Ask the control to paint itself on the given bitmap.
wxBitmap bmp(size);
{
wxMemoryDC mdc(bmp);
defPaint(hwnd, (WPARAM)GetHdcOf(mdc));
}
PAINTSTRUCT ps;
wxDCTemp dc(::BeginPaint(hwnd, &ps), size);
dc.DrawBitmap(postProcess(bmp), 0, 0);
::EndPaint(hwnd, &ps);
}
} // namespace wxMSWImpl
#endif // _WX_MSW_PRIVATE_CUSTOMPAINT_H_

View file

@ -166,17 +166,40 @@ WXDLLIMPEXP_CORE bool wxUxThemeIsActive();
// wxUxThemeHandle: encapsulates ::Open/CloseThemeData()
// ----------------------------------------------------------------------------
class wxUxThemeHandle
class WXDLLIMPEXP_CORE wxUxThemeHandle
{
public:
wxUxThemeHandle(HWND hwnd, const wchar_t *classes) :
m_hTheme{::OpenThemeData(hwnd, classes)}
// For all factory functions, HWND doesn't need to be valid and may be
// entirely omitted when using NewAtStdDPI(). However DPI must be valid if
// it's specified, i.e. NewForStdDPI() can be used if the DPI doesn't
// matter at all (which is the case if the theme is only used to query some
// colours, for example), but otherwise (e.g. when using the theme to get
// any metrics) the actual DPI of the window must be passed to NewForDPI().
static wxUxThemeHandle NewAtDPI(HWND hwnd, const wchar_t *classes, int dpi)
{
return wxUxThemeHandle(hwnd, classes, dpi);
}
static wxUxThemeHandle NewAtStdDPI(HWND hwnd, const wchar_t *classes)
{
return NewAtDPI(hwnd, classes, STD_DPI);
}
static wxUxThemeHandle NewAtStdDPI(const wchar_t *classes)
{
return NewAtStdDPI(0, classes);
}
// wxWindow pointer here must be valid and its DPI is always used.
wxUxThemeHandle(const wxWindow *win, const wchar_t *classes) :
wxUxThemeHandle(GetHwndOf(win), classes, win->GetDPI().y)
{
}
wxUxThemeHandle(const wxWindow *win, const wchar_t *classes) :
wxUxThemeHandle(GetHwndOf(win), classes)
wxUxThemeHandle(wxUxThemeHandle&& other)
: m_hTheme{other.m_hTheme}
{
other.m_hTheme = 0;
}
operator HTHEME() const { return m_hTheme; }
@ -195,8 +218,41 @@ public:
// GetThemeColor() because we want to default the state.
wxColour GetColour(int part, int prop, int state = 0) const;
// Return the size of a theme element, either "as is" (TS_TRUE size) or as
// it would be used for drawing (TS_DRAW size).
//
// For now we don't allow specifying the HDC or rectangle as they don't
// seem to be useful.
wxSize GetTrueSize(int part, int state = 0) const
{
return DoGetSize(part, state, TS_TRUE);
}
wxSize GetDrawSize(int part, int state = 0) const
{
return DoGetSize(part, state, TS_DRAW);
}
// Draw theme background: if the caller already has a RECT, it can be
// provided directly, otherwise wxRect is converted to it.
void DrawBackground(HDC hdc, const RECT& rc, int part, int state = 0);
void DrawBackground(HDC hdc, const wxRect& rect, int part, int state = 0);
private:
const HTHEME m_hTheme;
static const int STD_DPI = 96;
static HTHEME DoOpenThemeData(HWND hwnd, const wchar_t *classes, int dpi);
wxUxThemeHandle(HWND hwnd, const wchar_t *classes, int dpi) :
m_hTheme{DoOpenThemeData(hwnd, classes, dpi)}
{
}
wxSize DoGetSize(int part, int state, THEMESIZE ts) const;
// This is almost, but not quite, const: it's only reset in move ctor.
HTHEME m_hTheme;
wxDECLARE_NO_COPY_CLASS(wxUxThemeHandle);
};

View file

@ -35,31 +35,19 @@ wxAuiMSWToolBarArt::wxAuiMSWToolBarArt()
wxWindow* window = static_cast<wxApp*>(wxApp::GetInstance())->GetTopWindow();
wxUxThemeHandle hTheme(window, L"Rebar");
SIZE overflowSize;
::GetThemePartSize(hTheme, nullptr, RP_CHEVRON, 0,
nullptr, TS_TRUE, &overflowSize);
m_overflowSize = overflowSize.cx;
m_overflowSize = hTheme.GetTrueSize(RP_CHEVRON).x;
SIZE gripperSize;
::GetThemePartSize(hTheme, nullptr, RP_GRIPPER, 0,
nullptr, TS_TRUE, &gripperSize);
m_gripperSize = gripperSize.cx;
m_gripperSize = hTheme.GetTrueSize(RP_GRIPPER).x;
wxUxThemeHandle hThemeToolbar(window, L"Toolbar");
SIZE seperatorSize;
::GetThemePartSize(hThemeToolbar, nullptr, TP_SEPARATOR, 0,
nullptr, TS_TRUE, &seperatorSize);
m_separatorSize = seperatorSize.cx;
m_separatorSize = hThemeToolbar.GetTrueSize(TP_SEPARATOR).x;
// TP_DROPDOWNBUTTON is only 7px, too small to fit the dropdown arrow,
// use 14px instead.
m_dropdownSize = window->FromDIP(14);
SIZE buttonSize;
::GetThemePartSize(hThemeToolbar, nullptr, TP_BUTTON, 0,
nullptr, TS_TRUE, &buttonSize);
m_buttonSize.Set(buttonSize.cx, buttonSize.cy);
m_buttonSize = hThemeToolbar.GetTrueSize(TP_BUTTON);
}
else
m_themed = false;
@ -77,18 +65,13 @@ void wxAuiMSWToolBarArt::DrawBackground(
{
if ( m_themed )
{
RECT r;
wxCopyRectToRECT(rect, r);
wxUxThemeHandle hTheme(wnd, L"Rebar");
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
RP_BACKGROUND,
0,
&r,
nullptr);
rect,
RP_BACKGROUND
);
}
else
wxAuiGenericToolBarArt::DrawBackground(dc, wnd, rect);
@ -111,9 +94,6 @@ void wxAuiMSWToolBarArt::DrawButton(
{
if ( m_themed )
{
RECT r;
wxCopyRectToRECT(rect, r);
wxUxThemeHandle hTheme(wnd, L"Toolbar");
int btnState;
@ -131,13 +111,12 @@ void wxAuiMSWToolBarArt::DrawButton(
else
btnState = TS_NORMAL;
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
rect,
TP_BUTTON,
btnState,
&r,
nullptr);
btnState
);
int textWidth = 0, textHeight = 0;
@ -237,11 +216,6 @@ void wxAuiMSWToolBarArt::DrawDropDownButton(
dc.GetTextExtent(item.GetLabel(), &textWidth, &ty);
}
RECT btnR;
wxCopyRectToRECT(buttonRect, btnR);
RECT dropDownR;
wxCopyRectToRECT(dropDownRect, dropDownR);
int btnState;
if ( item.GetState() & wxAUI_BUTTON_STATE_DISABLED )
btnState = TS_DISABLED;
@ -252,21 +226,23 @@ void wxAuiMSWToolBarArt::DrawDropDownButton(
else
btnState = TS_NORMAL;
::DrawThemeBackground(
hTheme,
GetHdcOf(dc.GetTempHDC()),
TP_SPLITBUTTON,
btnState,
&btnR,
nullptr);
{
auto tempHDC = dc.GetTempHDC();
::DrawThemeBackground(
hTheme,
GetHdcOf(dc.GetTempHDC()),
hTheme.DrawBackground(
GetHdcOf(tempHDC),
buttonRect,
TP_SPLITBUTTON,
btnState
);
hTheme.DrawBackground(
GetHdcOf(tempHDC),
dropDownRect,
TP_SPLITBUTTONDROPDOWN,
btnState,
&dropDownR,
nullptr);
btnState
);
} // End of tempHDC scope.
const wxBitmap& bmp = item.GetCurrentBitmapFor(wnd);
if ( !bmp.IsOk() )
@ -332,18 +308,13 @@ void wxAuiMSWToolBarArt::DrawSeparator(
{
if ( m_themed )
{
RECT r;
wxCopyRectToRECT(rect, r);
wxUxThemeHandle hTheme(wnd, L"Toolbar");
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
(m_flags & wxAUI_TB_VERTICAL) ? TP_SEPARATORVERT : TP_SEPARATOR,
0,
&r,
nullptr);
rect,
(m_flags & wxAUI_TB_VERTICAL) ? TP_SEPARATORVERT : TP_SEPARATOR
);
}
else
wxAuiGenericToolBarArt::DrawSeparator(dc, wnd, rect);
@ -356,18 +327,13 @@ void wxAuiMSWToolBarArt::DrawGripper(
{
if ( m_themed )
{
RECT r;
wxCopyRectToRECT(rect, r);
wxUxThemeHandle hTheme(wnd, L"Rebar");
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
(m_flags & wxAUI_TB_VERTICAL) ? RP_GRIPPERVERT : RP_GRIPPER,
0,
&r,
nullptr);
rect,
(m_flags & wxAUI_TB_VERTICAL) ? RP_GRIPPERVERT : RP_GRIPPER
);
}
else
wxAuiGenericToolBarArt::DrawGripper(dc, wnd, rect);
@ -381,26 +347,22 @@ void wxAuiMSWToolBarArt::DrawOverflowButton(
{
if ( m_themed )
{
RECT r;
wxCopyRectToRECT(rect, r);
wxUxThemeHandle hTheme(wnd, L"Rebar");
int chevState;
if ( state & wxAUI_BUTTON_STATE_PRESSED )
chevState = CHEVS_PRESSED;
else if ( state & wxAUI_BUTTON_STATE_HOVER )
chevState = CHEVS_HOT;
chevState = CHEVS_HOT;
else
chevState = CHEVS_NORMAL;
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
rect,
(m_flags & wxAUI_TB_VERTICAL) ? RP_CHEVRONVERT : RP_CHEVRON,
chevState,
&r,
nullptr);
chevState
);
}
else
wxAuiGenericToolBarArt::DrawOverflowButton(dc, wnd, rect, state);

View file

@ -59,18 +59,13 @@ void wxAuiMSWTabArt::DrawBorder(wxDC& dc, wxWindow* wnd, const wxRect& rect)
dc.SetPen(wxPen(wnd->GetBackgroundColour(), GetBorderWidth(wnd)));
dc.DrawRectangle(topDrawRect);
RECT r;
wxCopyRectToRECT(drawRect, r);
wxUxThemeHandle hTheme(wnd, L"TAB");
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
TABP_PANE,
0,
&r,
nullptr);
drawRect,
TABP_PANE
);
}
void wxAuiMSWTabArt::DrawBackground(wxDC& dc,
@ -99,18 +94,13 @@ void wxAuiMSWTabArt::DrawBackground(wxDC& dc,
drawRect.Inflate(1, 0);
RECT r;
wxCopyRectToRECT(drawRect, r);
wxUxThemeHandle hTheme(wnd, L"TAB");
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
TABP_PANE,
0,
&r,
nullptr);
drawRect,
TABP_PANE
);
}
void wxAuiMSWTabArt::DrawTab(wxDC& dc,
@ -175,25 +165,23 @@ void wxAuiMSWTabArt::DrawTab(wxDC& dc,
tabState = TIS_NORMAL;
wxUxThemeHandle hTabTheme(wnd, L"Tab");
RECT tabR;
wxCopyRectToRECT(tabRect, tabR);
::DrawThemeBackground(hTabTheme, GetHdcOf(dc.GetTempHDC()), TABP_TABITEM,
tabState,
&tabR, nullptr);
hTabTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
tabRect,
TABP_TABITEM,
tabState
);
// Apparently, in at least some Windows 10 installations the call above
// does not draw the left edge of the first tab and it needs to be drawn
// separately, or it wouldn't be drawn at all.
if ( tabX == GetIndentSize() )
{
::DrawThemeBackground
(
hTabTheme,
hTabTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
tabRect,
TABP_TABITEMLEFTEDGE,
tabState,
&tabR,
nullptr
tabState
);
}
@ -234,9 +222,12 @@ void wxAuiMSWTabArt::DrawTab(wxDC& dc,
m_closeBtnSize.x,
m_closeBtnSize.y);
RECT btnR;
wxCopyRectToRECT(rect, btnR);
::DrawThemeBackground(hToolTipTheme, GetHdcOf(dc.GetTempHDC()), TTP_CLOSE, btnState, &btnR, nullptr);
hToolTipTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
rect,
TTP_CLOSE,
btnState
);
if ( out_button_rect )
*out_button_rect = rect;
@ -412,9 +403,12 @@ void wxAuiMSWTabArt::DrawButton(wxDC& dc,
wxRect btnRect(rect);
btnRect.width -= wnd->FromDIP(1);
RECT btnR;
wxCopyRectToRECT(btnRect, btnR);
::DrawThemeBackground(hTheme, GetHdcOf(dc.GetTempHDC()), part, btnState, &btnR, nullptr);
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
rect,
part,
btnState
);
if ( out_rect )
*out_rect = rect;
@ -434,21 +428,14 @@ int wxAuiMSWTabArt::GetBestTabCtrlSize(wxWindow* wnd,
return wxAuiGenericTabArt::GetBestTabCtrlSize(wnd, pages, requiredBmp_size);
}
void wxAuiMSWTabArt::InitSizes(wxWindow* wnd, wxDC& dc)
void wxAuiMSWTabArt::InitSizes(wxWindow* wnd, wxDC& WXUNUSED(dc))
{
SIZE uxSize;
// Borrow close button from tooltip (best fit on various backgrounds)
wxUxThemeHandle hTooltipTheme(wnd, L"Tooltip");
::GetThemePartSize(hTooltipTheme, GetHdcOf(dc.GetTempHDC()),
TTP_CLOSE, 0, nullptr, TS_TRUE, &uxSize);
m_closeBtnSize.Set(uxSize.cx, uxSize.cy);
m_closeBtnSize = hTooltipTheme.GetTrueSize(TTP_CLOSE);
wxUxThemeHandle hTabTheme(wnd, L"Tab");
::GetThemePartSize(hTabTheme, GetHdcOf(dc.GetTempHDC()),
TABP_TABITEM, 0, nullptr, TS_TRUE, &uxSize);
m_tabSize.Set(uxSize.cx, uxSize.cy);
m_tabSize = hTabTheme.GetTrueSize(TABP_TABITEM);
}
bool wxAuiMSWTabArt::IsThemed() const

View file

@ -1288,8 +1288,7 @@ void DrawXPBackground(wxAnyButton *button, HDC hdc, RECT& rectBtn, UINT state)
}
// draw background
::DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
&rectBtn, nullptr);
theme.DrawBackground(hdc, rectBtn, BP_PUSHBUTTON, iState);
// calculate content area margins, using the defaults in case we fail to
// retrieve the current theme margins

View file

@ -429,7 +429,7 @@ void wxComboCtrl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) )
// Draw the control background (including the border)
if ( m_widthCustomBorder > 0 )
{
::DrawThemeBackground( hTheme, hDc, comboBoxPart, bgState, rUseForBg, nullptr );
hTheme.DrawBackground(hDc, *rUseForBg, comboBoxPart, bgState);
}
else
{
@ -462,7 +462,7 @@ void wxComboCtrl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) )
else
butPart = CP_DROPDOWNBUTTONLEFT;
::DrawThemeBackground( hTheme, hDc, butPart, butState, &rButton, nullptr );
hTheme.DrawBackground(hDc, rButton, butPart, butState);
}
else if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE )
{

View file

@ -48,6 +48,7 @@
#include "wx/msw/dc.h"
#include "wx/msw/uxtheme.h"
#include "wx/msw/private/custompaint.h"
#include "wx/msw/private/darkmode.h"
#include <memory>
@ -431,32 +432,35 @@ HBRUSH GetBackgroundBrush()
return brush ? GetHbrushOf(*brush) : 0;
}
#if wxUSE_IMAGE
static void
InvertBitmapPixel(unsigned char& r, unsigned char& g, unsigned char& b,
unsigned char& WXUNUSED(a))
{
wxImage::RGBValue rgb(r, g, b);
wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
// There is no really good way to convert normal colours to dark mode,
// but try to do a bit better than just inverting the value because
// this results in colours which are much too dark.
hsv.value = sqrt(1.0 - hsv.value*hsv.value);
rgb = wxImage::HSVtoRGB(hsv);
r = rgb.red;
g = rgb.green;
b = rgb.blue;
}
#endif // wxUSE_IMAGE
wxBitmap InvertBitmap(const wxBitmap& bmp)
{
#if wxUSE_IMAGE
wxImage image = bmp.ConvertToImage();
unsigned char *data = image.GetData();
const int len = image.GetWidth()*image.GetHeight();
for ( int i = 0; i < len; ++i, data += 3 )
{
wxImage::RGBValue rgb(data[0], data[1], data[2]);
wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
// There is no really good way to convert normal colours to dark mode,
// but try to do a bit better than just inverting the value because
// this results in colours which are much too dark.
hsv.value = sqrt(1.0 - hsv.value*hsv.value);
rgb = wxImage::HSVtoRGB(hsv);
data[0] = rgb.red;
data[1] = rgb.green;
data[2] = rgb.blue;
}
return wxBitmap(image);
return wxMSWImpl::PostPaintEachPixel(bmp, InvertBitmapPixel);
#else // !wxUSE_IMAGE
return wxBitmap();
return bmp;
#endif // wxUSE_IMAGE/!wxUSE_IMAGE
}
@ -466,29 +470,18 @@ bool PaintIfNecessary(HWND hwnd, WXWNDPROC defWndProc)
if ( !wxMSWImpl::ShouldUseDarkMode() )
return false;
const RECT rc = wxGetClientRect(hwnd);
const wxSize size{rc.right - rc.left, rc.bottom - rc.top};
// Don't bother doing anything with the empty windows.
if ( size == wxSize() )
return false;
// Ask the control to paint itself on the given bitmap.
wxBitmap bmp(size);
{
wxMemoryDC mdc(bmp);
WPARAM wparam = (WPARAM)GetHdcOf(mdc);
if ( defWndProc )
::CallWindowProc(defWndProc, hwnd, WM_PAINT, wparam, 0);
else
::DefWindowProc(hwnd, WM_PAINT, wparam, 0);
}
PAINTSTRUCT ps;
wxDCTemp dc(::BeginPaint(hwnd, &ps), size);
dc.DrawBitmap(InvertBitmap(bmp), 0, 0);
::EndPaint(hwnd, &ps);
wxMSWImpl::CustomPaint
(
hwnd,
[defWndProc](HWND hwnd, WPARAM wParam)
{
if ( defWndProc )
::CallWindowProc(defWndProc, hwnd, WM_PAINT, wParam, 0);
else
::DefWindowProc(hwnd, WM_PAINT, wParam, 0);
},
InvertBitmap
);
return true;
#else // !wxUSE_IMAGE
@ -690,7 +683,7 @@ HandleMenuMessage(WXLRESULT* result,
if ( itemState & ODS_NOACCEL)
drawTextFlags |= DT_HIDEPREFIX;
wxUxThemeHandle menuTheme(GetHwndOf(w), L"Menu");
wxUxThemeHandle menuTheme(w, L"Menu");
::DrawThemeTextEx(menuTheme, dis.hDC, MENU_BARITEM, partState,
buf, mii.cch, drawTextFlags, rcItem,
&textOpts);

View file

@ -36,7 +36,9 @@
#endif
#include "wx/msw/private.h"
#include "wx/msw/private/custompaint.h"
#include "wx/msw/private/darkmode.h"
#include "wx/msw/wrapcctl.h"
#include "wx/evtloop.h"
#include "wx/scopedptr.h"
@ -76,6 +78,70 @@ wxDEFINE_TIED_SCOPED_PTR_TYPE(wxDialogModalData)
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// Gripper subclass proc
// ----------------------------------------------------------------------------
namespace wxMSWImpl
{
LRESULT CALLBACK
GripperProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
wxDialog* const self = reinterpret_cast<wxDialog*>(dwRefData);
switch ( nMsg )
{
case WM_PAINT:
{
const auto bg = self->GetBackgroundColour();
wxMSWImpl::CustomPaint
(
hwnd,
[](HWND hwnd, WPARAM wParam)
{
::DefSubclassProc(hwnd, WM_PAINT, wParam, 0);
},
[bg](const wxBitmap& bmp)
{
return wxMSWImpl::PostPaintEachPixel
(
bmp,
[bg](unsigned char& r,
unsigned char& g,
unsigned char& b,
unsigned char& a)
{
// Replace all background pixels, which
// are transparent, with the colour we
// want to use.
if ( a == wxALPHA_TRANSPARENT )
{
r = bg.Red();
g = bg.Green();
b = bg.Blue();
a = wxALPHA_OPAQUE;
}
}
);
}
);
}
return 0;
case WM_NCDESTROY:
::RemoveWindowSubclass(hwnd, GripperProc, uIdSubclass);
break;
}
return ::DefSubclassProc(hwnd, nMsg, wParam, lParam);
}
} // namespace wxMSWImpl
// ----------------------------------------------------------------------------
// wxDialog construction
// ----------------------------------------------------------------------------
@ -243,6 +309,13 @@ void wxDialog::CreateGripper()
);
wxMSWDarkMode::AllowForWindow((HWND)m_hGripper);
// Whether we use the dark mode or not, handle WM_PAINT for the gripper
// ourselves, as even in the light mode its background is wrong if the
// dialog doesn't use the default background colour -- and in dark mode
// it's wrong even by default.
::SetWindowSubclass(m_hGripper, wxMSWImpl::GripperProc,
0, wxPtrToUInt(this));
}
}

View file

@ -653,7 +653,7 @@ wxVisualAttributes wxListCtrl::GetDefaultAttributes() const
// Note that we intentionally do not use this window HWND for the
// theme, as it doesn't have dark values for it -- but does have them
// when we use null window.
wxUxThemeHandle theme{HWND(0), L"ItemsView"};
auto theme = wxUxThemeHandle::NewAtStdDPI(L"ItemsView");
wxColour col = theme.GetColour(0, TMT_TEXTCOLOR);
if ( col.IsOk() )

View file

@ -958,25 +958,19 @@ bool wxMenuItem::OnDrawItem(wxDC& dc, const wxRect& rc,
if ( ::IsThemeBackgroundPartiallyTransparent(hTheme,
MENU_POPUPITEM, state) )
{
::DrawThemeBackground(hTheme, hdc,
MENU_POPUPBACKGROUND,
0, &rect, nullptr);
hTheme.DrawBackground(hdc, rect, MENU_POPUPBACKGROUND);
}
::DrawThemeBackground(hTheme, hdc, MENU_POPUPGUTTER,
0, &rcGutter, nullptr);
hTheme.DrawBackground(hdc, rcGutter, MENU_POPUPGUTTER);
if ( IsSeparator() )
{
rcSeparator.left = rcGutter.right;
::DrawThemeBackground(hTheme, hdc, MENU_POPUPSEPARATOR,
0, &rcSeparator, nullptr);
hTheme.DrawBackground(hdc, rcSeparator, MENU_POPUPSEPARATOR);
return true;
}
::DrawThemeBackground(hTheme, hdc, MENU_POPUPITEM,
state, &rcSelection, nullptr);
hTheme.DrawBackground(hdc, rcSelection, MENU_POPUPITEM, state);
}
else
#endif // wxUSE_UXTHEME
@ -1192,8 +1186,7 @@ void wxMenuItem::DrawStdCheckMark(WXHDC hdc_, const RECT* rc, wxODStatus stat)
? MCB_DISABLED
: MCB_NORMAL;
::DrawThemeBackground(hTheme, hdc, MENU_POPUPCHECKBACKGROUND,
stateCheckBg, &rcBg, nullptr);
hTheme.DrawBackground(hdc, rcBg, MENU_POPUPCHECKBACKGROUND, stateCheckBg);
POPUPCHECKSTATES stateCheck;
if ( GetKind() == wxITEM_CHECK )
@ -1207,8 +1200,7 @@ void wxMenuItem::DrawStdCheckMark(WXHDC hdc_, const RECT* rc, wxODStatus stat)
: MC_BULLETNORMAL;
}
::DrawThemeBackground(hTheme, hdc, MENU_POPUPCHECK,
stateCheck, rc, nullptr);
hTheme.DrawBackground(hdc, *rc, MENU_POPUPCHECK, stateCheck);
}
else
#endif // wxUSE_UXTHEME

View file

@ -1651,7 +1651,7 @@ WXHBRUSH wxNotebook::QueryBgBitmap()
RECT rcBg;
::GetThemeBackgroundContentRect(theme,
(HDC) hDC,
9, /* TABP_PANE */
TABP_PANE,
0,
&rc,
&rcBg);
@ -1663,7 +1663,7 @@ WXHBRUSH wxNotebook::QueryBgBitmap()
(
theme,
(HDC) hDC,
9 /* TABP_PANE */,
TABP_PANE,
0,
&rcBg,
&rc
@ -1674,15 +1674,7 @@ WXHBRUSH wxNotebook::QueryBgBitmap()
{
SelectInHDC selectBmp(hDCMem, hBmp);
::DrawThemeBackground
(
theme,
hDCMem,
9 /* TABP_PANE */,
0,
&rc,
nullptr
);
theme.DrawBackground(hDCMem, rc, TABP_PANE);
} // deselect bitmap from the memory HDC before using it
return (WXHBRUSH)::CreatePatternBrush(hBmp);
@ -1738,20 +1730,13 @@ bool wxNotebook::MSWPrintChild(WXHDC hDC, wxWindow *child)
(
theme,
(HDC) hDC,
9 /* TABP_PANE */,
TABP_PANE,
0,
&rc,
&rc
);
::DrawThemeBackground
(
theme,
(HDC) hDC,
9 /* TABP_PANE */,
0,
&rc,
nullptr
);
theme.DrawBackground((HDC) hDC, rc, TABP_PANE);
return true;
}
}

View file

@ -300,7 +300,7 @@ private:
// wrapper around DrawThemeBackground() translating flags to NORMAL/HOT/
// PUSHED/DISABLED states (and so suitable for drawing anything
// button-like)
void DoDrawButtonLike(HTHEME htheme,
void DoDrawButtonLike(wxUxThemeHandle& hTheme,
int part,
wxDC& dc,
const wxRect& rect,
@ -644,16 +644,7 @@ wxRendererXP::DrawComboBoxDropButton(wxWindow * win,
else
state = CBXS_NORMAL;
::DrawThemeBackground
(
hTheme,
GetHdcOf(dc.GetTempHDC()),
CP_DROPDOWNBUTTON,
state,
&r,
nullptr
);
hTheme.DrawBackground(GetHdcOf(dc.GetTempHDC()), r, CP_DROPDOWNBUTTON, state);
}
int
@ -681,15 +672,8 @@ wxRendererXP::DrawHeaderButton(wxWindow *win,
state = HIS_HOT;
else
state = HIS_NORMAL;
::DrawThemeBackground
(
hTheme,
GetHdcOf(dc.GetTempHDC()),
HP_HEADERITEM,
state,
&r,
nullptr
);
hTheme.DrawBackground(GetHdcOf(dc.GetTempHDC()), r, HP_HEADERITEM, state);
// NOTE: Using the theme to draw HP_HEADERSORTARROW doesn't do anything.
// Why? If this can be fixed then draw the sort arrows using the theme
@ -718,15 +702,8 @@ wxRendererXP::DrawTreeItemButton(wxWindow *win,
RECT r = ConvertToRECT(dc, rect);
int state = flags & wxCONTROL_EXPANDED ? GLPS_OPENED : GLPS_CLOSED;
::DrawThemeBackground
(
hTheme,
GetHdcOf(dc.GetTempHDC()),
TVP_GLYPH,
state,
&r,
nullptr
);
hTheme.DrawBackground(GetHdcOf(dc.GetTempHDC()), r, TVP_GLYPH, state);
}
bool
@ -764,21 +741,13 @@ wxRendererXP::DoDrawCheckMark(int kind,
if ( flags & wxCONTROL_DISABLED )
state = MC_CHECKMARKDISABLED;
::DrawThemeBackground
(
hTheme,
GetHdcOf(dc.GetTempHDC()),
kind,
state,
&r,
nullptr
);
hTheme.DrawBackground(GetHdcOf(dc.GetTempHDC()), r, kind, state);
return true;
}
void
wxRendererXP::DoDrawButtonLike(HTHEME htheme,
wxRendererXP::DoDrawButtonLike(wxUxThemeHandle& hTheme,
int part,
wxDC& dc,
const wxRect& rect,
@ -820,15 +789,7 @@ wxRendererXP::DoDrawButtonLike(HTHEME htheme,
else if ( part == BP_PUSHBUTTON && (flags & wxCONTROL_ISDEFAULT) )
state = PBS_DEFAULTED;
::DrawThemeBackground
(
htheme,
GetHdcOf(dc.GetTempHDC()),
part,
state,
&r,
nullptr
);
hTheme.DrawBackground(GetHdcOf(dc.GetTempHDC()), r, part, state);
}
void
@ -885,9 +846,7 @@ wxSize wxRendererXP::GetCheckBoxSize(wxWindow* win, int flags)
{
if (::IsThemePartDefined(hTheme, BP_CHECKBOX, 0))
{
SIZE checkSize;
if (::GetThemePartSize(hTheme, nullptr, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, nullptr, TS_DRAW, &checkSize) == S_OK)
return wxSize(checkSize.cx, checkSize.cy);
return hTheme.GetDrawSize(BP_CHECKBOX, CBS_UNCHECKEDNORMAL);
}
}
return m_rendererNative.GetCheckBoxSize(win, flags);
@ -902,9 +861,7 @@ wxSize wxRendererXP::GetCheckMarkSize(wxWindow* win)
{
if (::IsThemePartDefined(hTheme, MENU_POPUPCHECK, 0))
{
SIZE checkSize;
if (::GetThemePartSize(hTheme, nullptr, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr, TS_DRAW, &checkSize) == S_OK)
return wxSize(checkSize.cx, checkSize.cy);
return hTheme.GetDrawSize(MENU_POPUPCHECK, MC_CHECKMARKNORMAL);
}
}
return m_rendererNative.GetCheckMarkSize(win);
@ -919,11 +876,7 @@ wxSize wxRendererXP::GetExpanderSize(wxWindow* win)
{
if ( ::IsThemePartDefined(hTheme, TVP_GLYPH, 0) )
{
SIZE expSize;
if (::GetThemePartSize(hTheme, nullptr, TVP_GLYPH, GLPS_CLOSED, nullptr,
TS_DRAW, &expSize) == S_OK)
return wxSize(expSize.cx, expSize.cy);
return hTheme.GetDrawSize(TVP_GLYPH, GLPS_CLOSED);
}
}
@ -948,15 +901,8 @@ DoDrawCollapseButton(wxWindow* win, HDC hdc, RECT r, int flags)
if ( flags & wxCONTROL_EXPANDED )
state += 3;
::DrawThemeBackground
(
hTheme,
hdc,
TDLG_EXPANDOBUTTON,
state,
&r,
nullptr
);
hTheme.DrawBackground(hdc, r, TDLG_EXPANDOBUTTON, state);
return true;
}
@ -1014,19 +960,10 @@ wxSize wxRendererXP::GetCollapseButtonSize(wxWindow *win, wxDC& dc)
if ( ::IsThemePartDefined(hTheme, TDLG_EXPANDOBUTTON, 0) )
{
SIZE s;
::GetThemePartSize(hTheme,
GetHdcOf(dc.GetTempHDC()),
TDLG_EXPANDOBUTTON,
TDLGEBS_NORMAL,
nullptr,
TS_TRUE,
&s);
return wxSize(s.cx, s.cy);
return hTheme.GetTrueSize(TDLG_EXPANDOBUTTON, TDLGEBS_NORMAL);
}
else
return m_rendererNative.GetCollapseButtonSize(win, dc);
return m_rendererNative.GetCollapseButtonSize(win, dc);
}
void
@ -1046,7 +983,7 @@ wxRendererXP::DrawItemSelectionRect(wxWindow *win,
if ( ::IsThemeBackgroundPartiallyTransparent(hTheme, LVP_LISTITEM, itemState) )
::DrawThemeParentBackground(GetHwndOf(win), GetHdcOf(dc.GetTempHDC()), &rc);
::DrawThemeBackground(hTheme, GetHdcOf(dc.GetTempHDC()), LVP_LISTITEM, itemState, &rc, 0);
hTheme.DrawBackground(GetHdcOf(dc.GetTempHDC()), rc, LVP_LISTITEM, itemState);
}
else
{
@ -1292,13 +1229,11 @@ void wxRendererXP::DrawGauge(wxWindow* win,
RECT r = ConvertToRECT(dc, rect);
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
flags & wxCONTROL_SPECIAL ? PP_BARVERT : PP_BAR,
0,
&r,
nullptr);
r,
flags & wxCONTROL_SPECIAL ? PP_BARVERT : PP_BAR
);
RECT contentRect;
::GetThemeBackgroundContentRect(
@ -1325,13 +1260,11 @@ void wxRendererXP::DrawGauge(wxWindow* win,
max);
}
::DrawThemeBackground(
hTheme,
hTheme.DrawBackground(
GetHdcOf(dc.GetTempHDC()),
flags & wxCONTROL_SPECIAL ? PP_CHUNKVERT : PP_CHUNK,
0,
&contentRect,
nullptr);
contentRect,
flags & wxCONTROL_SPECIAL ? PP_CHUNKVERT : PP_CHUNK
);
}
// ----------------------------------------------------------------------------

View file

@ -35,6 +35,7 @@
#endif
#include "wx/msw/private.h"
#include "wx/msw/private/custompaint.h"
#include "wx/msw/private/darkmode.h"
#include "wx/tooltip.h"
@ -203,7 +204,7 @@ void wxStatusBar::MSWUpdateFieldsWidths()
// update the field widths in the native control:
int *pWidths = new int[count];
std::vector<int> pWidths(count);
int nCurPos = 0;
int i;
@ -218,7 +219,7 @@ void wxStatusBar::MSWUpdateFieldsWidths()
// separator line just before it.
pWidths[count - 1] += gripWidth;
if ( !StatusBar_SetParts(GetHwnd(), count, pWidths) )
if ( !StatusBar_SetParts(GetHwnd(), count, &pWidths[0]) )
{
wxLogLastError("StatusBar_SetParts");
}
@ -228,8 +229,6 @@ void wxStatusBar::MSWUpdateFieldsWidths()
{
DoUpdateStatusText(i);
}
delete [] pWidths;
}
void wxStatusBar::DoUpdateStatusText(int nField)
@ -573,17 +572,12 @@ wxStatusBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
// resizing. It is possible to send this message to any window.
if ( wParam == HTBOTTOMRIGHT )
{
wxWindow *win;
for ( win = GetParent(); win; win = win->GetParent() )
if ( wxWindow *win = wxGetTopLevelParent(this) )
{
if ( win->IsTopLevel() )
{
SendMessage(GetHwndOf(win), WM_NCLBUTTONDOWN,
wParam, lParam);
SendMessage(GetHwndOf(win), WM_NCLBUTTONDOWN,
wParam, lParam);
return 0;
}
return 0;
}
}
}
@ -605,6 +599,51 @@ wxStatusBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
}
}
#if wxUSE_UXTHEME
// We need to paint the size grip ourselves in dark mode as the default one
// is simply invisible.
if ( nMsg == WM_PAINT &&
(::GetWindowLong(GetHwnd(), GWL_STYLE) & SBARS_SIZEGRIP) &&
wxMSWDarkMode::IsActive() )
{
wxMSWImpl::CustomPaint
(
GetHwnd(),
[this](HWND hwnd, WPARAM wParam)
{
m_oldWndProc(hwnd, WM_PAINT, wParam, 0);
},
[this](const wxBitmap& bmpOrig)
{
wxBitmap bmp(bmpOrig);
wxMemoryDC dc(bmp);
// Note that we must _not_ open theme data for this window: it
// uses "ExplorerStatusBar" theme which doesn't draw SP_GRIPPER
// correctly (which is why we have to draw it ourselves).
auto theme = wxUxThemeHandle::NewAtDPI(0, L"Status", GetDPI().y);
if ( !theme )
return bmp;
const wxRect rectTotal(bmp.GetSize());
const wxSize sizeGrip = theme.GetDrawSize(SP_GRIPPER);
// Draw the grip in the lower right corner of the window.
//
// TODO-RTL: Is this correct for RTL layout?
wxRect rect(sizeGrip);
rect.x = rectTotal.width - sizeGrip.x;
rect.y = rectTotal.height - sizeGrip.y;
theme.DrawBackground(dc.GetHDC(), rect, SP_GRIPPER);
return bmp;
}
);
}
#endif // wxUSE_UXTHEME
return wxStatusBarBase::MSWWindowProc(nMsg, wParam, lParam);
}
@ -653,7 +692,8 @@ bool wxStatusBar::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM* W
bool wxStatusBar::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
{
// This is not documented anywhere but seems to work.
// This is not documented anywhere but seems to work (except for the size
// grip which we draw ourselves in our WM_PAINT handler).
//
// Note that we should _not_ set the theme name to "Explorer", this ID only
// works if we do _not_ do it.
@ -677,7 +717,7 @@ wxStatusBar::GetClassDefaultAttributes(wxWindowVariant variant)
if ( wxMSWDarkMode::IsActive() )
{
// It looks like we don't have to use a valid HWND here.
wxUxThemeHandle theme{HWND(0), L"ExplorerStatusBar"};
auto theme = wxUxThemeHandle::NewAtStdDPI(L"ExplorerStatusBar");
wxColour col = theme.GetColour(0, TMT_TEXTCOLOR);
if ( col.IsOk() )

View file

@ -30,6 +30,8 @@
#include "wx/module.h"
#endif //WX_PRECOMP
#include "wx/dynlib.h"
#include "wx/msw/uxtheme.h"
bool wxUxThemeIsActive()
@ -37,6 +39,33 @@ bool wxUxThemeIsActive()
return ::IsAppThemed() && ::IsThemeActive();
}
/* static */
HTHEME
wxUxThemeHandle::DoOpenThemeData(HWND hwnd, const wchar_t *classes, int dpi)
{
// If DPI is the default one, we can use the old function.
if ( dpi != STD_DPI )
{
// We need to use OpenThemeDataForDpi() which is not available under
// all supported systems, so try to load it dynamically if not done yet.
typedef HTHEME (WINAPI *OpenThemeDataForDpi_t)(HWND, LPCWSTR, UINT);
static OpenThemeDataForDpi_t s_pfnOpenThemeDataForDpi = nullptr;
static bool s_initDone = false;
if ( !s_initDone )
{
wxLoadedDLL dllUxTheme(wxS("uxtheme.dll"));
wxDL_INIT_FUNC(s_pfn, OpenThemeDataForDpi, dllUxTheme);
s_initDone = true;
}
if ( s_pfnOpenThemeDataForDpi )
return s_pfnOpenThemeDataForDpi(hwnd, classes, dpi);
}
return ::OpenThemeData(hwnd, classes);
}
wxColour wxUxThemeHandle::GetColour(int part, int prop, int state) const
{
COLORREF col;
@ -53,6 +82,46 @@ wxColour wxUxThemeHandle::GetColour(int part, int prop, int state) const
return wxRGBToColour(col);
}
wxSize wxUxThemeHandle::DoGetSize(int part, int state, THEMESIZE ts) const
{
SIZE size;
HRESULT hr = ::GetThemePartSize(m_hTheme, nullptr, part, state, nullptr, ts,
&size);
if ( FAILED(hr) )
{
wxLogApiError(
wxString::Format("GetThemePartSize(%i, %i)", part, state),
hr
);
return {};
}
return wxSize{size.cx, size.cy};
}
void
wxUxThemeHandle::DrawBackground(HDC hdc, const RECT& rc, int part, int state)
{
HRESULT hr = ::DrawThemeBackground(m_hTheme, hdc, part, state, &rc, nullptr);
if ( FAILED(hr) )
{
wxLogApiError(
wxString::Format("DrawThemeBackground(%i, %i)", part, state),
hr
);
}
}
void
wxUxThemeHandle::DrawBackground(HDC hdc, const wxRect& rect, int part, int state)
{
RECT rc;
wxCopyRectToRECT(rect, rc);
DrawBackground(hdc, rc, part, state);
}
#else
bool wxUxThemeIsActive()
{

View file

@ -3970,7 +3970,7 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
}
// Draw the border
::DrawThemeBackground(hTheme, GetHdcOf(*impl), EP_EDITTEXT, nState, &rcBorder, nullptr);
hTheme.DrawBackground(GetHdcOf(*impl), rcBorder, EP_EDITTEXT, nState);
}
}
break;