diff --git a/include/wx/msw/spinbutt.h b/include/wx/msw/spinbutt.h index 4ae6bf0946..743260e094 100644 --- a/include/wx/msw/spinbutt.h +++ b/include/wx/msw/spinbutt.h @@ -48,7 +48,6 @@ public: virtual void SetRange(int minVal, int maxVal) override; // implementation - virtual bool MSWCommand(WXUINT param, WXWORD id) override; virtual bool MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) override; virtual bool MSWOnScroll(int orientation, WXWORD wParam, WXWORD pos, WXHWND control) override; @@ -61,8 +60,6 @@ public: virtual void SetIncrement(int value) override; virtual int GetIncrement() const override; - virtual bool MSWShouldUseAutoDarkMode() const override; - protected: virtual wxSize DoGetBestSize() const override; @@ -70,6 +67,8 @@ protected: virtual void NormalizeValue(); private: + void OnPaint(wxPaintEvent& event); + wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxSpinButton); }; diff --git a/include/wx/msw/window.h b/include/wx/msw/window.h index 9d2677284a..af9f673cdd 100644 --- a/include/wx/msw/window.h +++ b/include/wx/msw/window.h @@ -213,13 +213,6 @@ public: void OnPaint(wxPaintEvent& event); - // Override this to return true to automatically invert the window colours - // in dark mode. - // - // This doesn't result in visually great results, but may still be better - // than using light background. - virtual bool MSWShouldUseAutoDarkMode() const { return false; } - public: // Windows subclassing void SubclassWin(WXHWND hWnd); diff --git a/src/msw/spinbutt.cpp b/src/msw/spinbutt.cpp index ffc7864098..45995ee9a2 100644 --- a/src/msw/spinbutt.cpp +++ b/src/msw/spinbutt.cpp @@ -23,13 +23,17 @@ #ifndef WX_PRECOMP #include "wx/msw/wrapcctl.h" // include "properly" #include "wx/app.h" + #include "wx/dcclient.h" + #include "wx/dcmemory.h" #endif #if wxUSE_SPINBTN #include "wx/spinbutt.h" +#include "wx/msw/dc.h" #include "wx/msw/private.h" +#include "wx/msw/private/darkmode.h" #ifndef UDM_SETRANGE32 #define UDM_SETRANGE32 (WM_USER+111) @@ -130,6 +134,8 @@ bool wxSpinButton::Create(wxWindow *parent, SubclassWin(m_hWnd); + Bind(wxEVT_PAINT, &wxSpinButton::OnPaint, this); + SetInitialSize(size); return true; @@ -139,13 +145,6 @@ wxSpinButton::~wxSpinButton() { } -bool wxSpinButton::MSWShouldUseAutoDarkMode() const -{ - // Native control doesn't seem to have any support for dark theme, so - // invert it in dark mode -- this is not great, but better than nothing. - return true; -} - // ---------------------------------------------------------------------------- // size calculation // ---------------------------------------------------------------------------- @@ -166,7 +165,107 @@ wxSize wxSpinButton::DoGetBestSize() const } // ---------------------------------------------------------------------------- -// Attributes +// painting +// ---------------------------------------------------------------------------- + +void wxSpinButton::OnPaint(wxPaintEvent& event) +{ + if ( wxMSWDarkMode::IsActive() ) + { + // Unfortunately PaintIfNecessary() can't be used here as we need to + // handle the extra border below, so duplicate what it does here. + const RECT rc = wxGetClientRect(GetHwnd()); + const wxSize size{rc.right - rc.left, rc.bottom - rc.top}; + + if ( size == wxSize() ) + return; + + wxBitmap bmp(size); + { + wxMemoryDC mdc(bmp); + + ::CallWindowProc(m_oldWndProc, + GetHwnd(), WM_PAINT, (WPARAM)GetHdcOf(mdc), 0); + } + +#if wxUSE_IMAGE + // When using a buddy control, the spin button tries to mimic being a + // part of it by adding an extra border, not used for standalone + // controls. This doesn't work very well even in light mode in modern + // Windows (it was apparently done for the classic 3D appearance and + // never updated since then), but looks completely horrible in dark + // mode, so we must get rid of this border by overdrawing it. + const bool drawBorder = ::SendMessage(GetHwnd(), UDM_GETBUDDY, 0, 0); + wxImage::RGBValue border; + if ( drawBorder ) + { + const auto col = wxMSWDarkMode::GetBorderPen().GetColour(); + border.red = col.GetRed(); + border.green = col.GetGreen(); + border.blue = col.GetBlue(); + } + + wxImage image = bmp.ConvertToImage(); + + const int width = image.GetWidth(); + const int height = image.GetHeight(); + unsigned char *data = image.GetData(); + unsigned char *alpha = image.GetAlpha(); + for ( int y = 0; y < height; ++y ) + { + for ( int x = 0; x < width; ++x ) + { + wxImage::RGBValue rgb(data[0], data[1], data[2]); + + if ( drawBorder && + (y == 0 || y == height - 1 || x == width - 1) ) + { + rgb = border; + if ( alpha ) + *alpha = wxALPHA_OPAQUE; + } + else + { + // This uses a slightly different formula than the one in + // InvertBitmapPixel() because the one there results in the + // lines being too bright. + auto hsv = wxImage::RGBtoHSV(rgb); + hsv.value = 1.0 - hsv.value; + rgb = wxImage::HSVtoRGB(hsv); + } + + data[0] = rgb.red; + data[1] = rgb.green; + data[2] = rgb.blue; + data += 3; + + if ( alpha ) + alpha++; + } + } + + bmp = wxBitmap(image); +#endif // wxUSE_IMAGE + + PAINTSTRUCT ps; + wxDCTemp dc(::BeginPaint(GetHwnd(), &ps), size); + dc.DrawBitmap(bmp, 0, 0); + ::EndPaint(GetHwnd(), &ps); + } + else + { + // We need to always paint this control explicitly instead of letting + // DefWndProc() do it, as this avoids whichever optimization the latter + // function does when WS_EX_COMPOSITED is on that result in not drawing + // parts of the control at all (see #23656). + wxPaintDC dc(this); + + wxSpinButtonBase::OnPaint(event); + } +} + +// ---------------------------------------------------------------------------- +// value and range // ---------------------------------------------------------------------------- int wxSpinButton::GetValue() const @@ -231,6 +330,10 @@ void wxSpinButton::SetRange(int minVal, int maxVal) } } +// ---------------------------------------------------------------------------- +// event generation +// ---------------------------------------------------------------------------- + bool wxSpinButton::MSWOnScroll(int WXUNUSED(orientation), WXWORD wParam, WXWORD WXUNUSED(pos), WXHWND control) { @@ -289,11 +392,9 @@ bool wxSpinButton::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM * return processed; } -bool wxSpinButton::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD WXUNUSED(id)) -{ - // No command messages - return false; -} +// ---------------------------------------------------------------------------- +// increment +// ---------------------------------------------------------------------------- void wxSpinButton::SetIncrement(int value) { diff --git a/src/msw/spinctrl.cpp b/src/msw/spinctrl.cpp index 22e7ee286b..8179e92c68 100644 --- a/src/msw/spinctrl.cpp +++ b/src/msw/spinctrl.cpp @@ -281,8 +281,9 @@ bool wxSpinCtrl::Create(wxWindow *parent, // set style for the base class style |= wxSP_VERTICAL; + // the border is only used for the text control part if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT ) - style |= wxBORDER_SUNKEN; + style |= DoTranslateBorder(wxBORDER_THEME); SetWindowStyle(style); @@ -327,8 +328,10 @@ bool wxSpinCtrl::Create(wxWindow *parent, } - // create the spin button - if ( !wxSpinButton::Create(parent, id, pos, wxSize(0, 0), style, name) ) + // create the spin button without any border as it doesn't make sense for + // it (even if it doesn't seem to be actually taken into account anyhow) + if ( !wxSpinButton::Create(parent, id, pos, wxSize(0, 0), + (style & ~wxBORDER_MASK) | wxBORDER_NONE, name) ) { return false; } diff --git a/src/msw/window.cpp b/src/msw/window.cpp index c911d5de18..444634f9b6 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -3197,11 +3197,7 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result, } else // no DC given { - if ( MSWShouldUseAutoDarkMode() && - wxMSWDarkMode::PaintIfNecessary(GetHwnd(), m_oldWndProc) ) - processed = true; - else - processed = HandlePaint(); + processed = HandlePaint(); } break;