diff --git a/include/wx/msw/private/customdraw.h b/include/wx/msw/private/customdraw.h index 7097585a67..3beb565336 100644 --- a/include/wx/msw/private/customdraw.h +++ b/include/wx/msw/private/customdraw.h @@ -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)); diff --git a/include/wx/msw/private/custompaint.h b/include/wx/msw/private/custompaint.h new file mode 100644 index 0000000000..e8ef9a3d7c --- /dev/null +++ b/include/wx/msw/private/custompaint.h @@ -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 +// 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 +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 +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_ diff --git a/include/wx/msw/uxtheme.h b/include/wx/msw/uxtheme.h index d1b60006ad..c90c384da2 100644 --- a/include/wx/msw/uxtheme.h +++ b/include/wx/msw/uxtheme.h @@ -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); }; diff --git a/src/aui/barartmsw.cpp b/src/aui/barartmsw.cpp index e9c8f8e82c..797ac9a00f 100644 --- a/src/aui/barartmsw.cpp +++ b/src/aui/barartmsw.cpp @@ -35,31 +35,19 @@ wxAuiMSWToolBarArt::wxAuiMSWToolBarArt() wxWindow* window = static_cast(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); diff --git a/src/aui/tabartmsw.cpp b/src/aui/tabartmsw.cpp index f56b8d1593..5ea75a9c9c 100644 --- a/src/aui/tabartmsw.cpp +++ b/src/aui/tabartmsw.cpp @@ -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 diff --git a/src/msw/anybutton.cpp b/src/msw/anybutton.cpp index 744482639f..bc8b9f9560 100644 --- a/src/msw/anybutton.cpp +++ b/src/msw/anybutton.cpp @@ -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 diff --git a/src/msw/combo.cpp b/src/msw/combo.cpp index 23f0cd4884..5c3dbbb3a0 100644 --- a/src/msw/combo.cpp +++ b/src/msw/combo.cpp @@ -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 ) { diff --git a/src/msw/darkmode.cpp b/src/msw/darkmode.cpp index eec84f5edf..023d6af101 100644 --- a/src/msw/darkmode.cpp +++ b/src/msw/darkmode.cpp @@ -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 @@ -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); diff --git a/src/msw/dialog.cpp b/src/msw/dialog.cpp index dcdb8be4de..e0309a71a1 100644 --- a/src/msw/dialog.cpp +++ b/src/msw/dialog.cpp @@ -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(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)); } } diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index cfe49ebe38..f833fd33df 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -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() ) diff --git a/src/msw/menuitem.cpp b/src/msw/menuitem.cpp index 1903f2cd01..dd162341de 100644 --- a/src/msw/menuitem.cpp +++ b/src/msw/menuitem.cpp @@ -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 diff --git a/src/msw/notebook.cpp b/src/msw/notebook.cpp index 131f7c212f..7d778a1b13 100644 --- a/src/msw/notebook.cpp +++ b/src/msw/notebook.cpp @@ -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; } } diff --git a/src/msw/renderer.cpp b/src/msw/renderer.cpp index 7a95ca16cd..e964453677 100644 --- a/src/msw/renderer.cpp +++ b/src/msw/renderer.cpp @@ -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 + ); } // ---------------------------------------------------------------------------- diff --git a/src/msw/statusbar.cpp b/src/msw/statusbar.cpp index 9ff7066968..7881d79f7e 100644 --- a/src/msw/statusbar.cpp +++ b/src/msw/statusbar.cpp @@ -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 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() ) diff --git a/src/msw/uxtheme.cpp b/src/msw/uxtheme.cpp index 948fbe67a4..e0cbb9a506 100644 --- a/src/msw/uxtheme.cpp +++ b/src/msw/uxtheme.cpp @@ -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() { diff --git a/src/msw/window.cpp b/src/msw/window.cpp index 45a199c030..e1cabfe8e7 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -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;