Merge branch 'msw-spin-appearance'

Fixes for wxSpinCtrl appearance in wxMSW.

See #23696.
This commit is contained in:
Vadim Zeitlin 2023-07-10 16:37:34 +02:00
commit 44b99195bc
5 changed files with 123 additions and 31 deletions

View file

@ -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);
};

View file

@ -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);

View file

@ -23,13 +23,17 @@
#ifndef WX_PRECOMP
#include "wx/msw/wrapcctl.h" // include <commctrl.h> "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)
{

View file

@ -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;
}

View file

@ -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;