Merge branch 'dpi-scale'

Add wxDPIChangedEvent::Scale() and use it in our own code.

See https://github.com/wxWidgets/wxWidgets/pull/2429
This commit is contained in:
Vadim Zeitlin
2021-07-13 23:06:34 +01:00
17 changed files with 255 additions and 77 deletions

View File

@@ -3140,6 +3140,13 @@ public:
wxSize GetOldDPI() const { return m_oldDPI; } wxSize GetOldDPI() const { return m_oldDPI; }
wxSize GetNewDPI() const { return m_newDPI; } wxSize GetNewDPI() const { return m_newDPI; }
// Scale the value by the ratio between new and old DPIs carried by this
// event.
wxSize Scale(wxSize sz) const;
int ScaleX(int x) const { return Scale(wxSize(x, -1)).x; }
int ScaleY(int y) const { return Scale(wxSize(-1, y)).y; }
virtual wxEvent *Clone() const wxOVERRIDE { return new wxDPIChangedEvent(*this); } virtual wxEvent *Clone() const wxOVERRIDE { return new wxDPIChangedEvent(*this); }
private: private:

View File

@@ -0,0 +1,146 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/private/rescale.h
// Purpose: Helpers for rescaling coordinates
// Author: Vadim Zeitlin
// Created: 2021-07-13
// Copyright: (c) 2021 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_PRIVATE_RESCALE_H_
#define _WX_PRIVATE_RESCALE_H_
#include "wx/gdicmn.h"
#include "wx/math.h"
#ifdef __WINDOWS__
// Required in order to use wxMulDivInt32().
#include "wx/msw/wrapwin.h"
#endif
// wxRescaleCoord is used to scale the components of the given wxSize by the
// ratio between 2 scales, with rounding. It doesn't not scale the components
// if they're set to -1 (wxDefaultCoord), as this value is special in wxSize.
//
// The way it's used is special because we want to ensure there is no confusion
// between the scale being converted from and the scale being converted to, so
// instead of just using a single function, we use an intermediate object,
// which is not supposed to be used directly, but is only returned by From() in
// order to allow calling To() on it.
//
// Another complication is that we want this to work for both wxSize and
// wxPoint, as well as for just plain coordinate values, so wxRescaleCoord() is
// an overloaded function and the helper classes are templates, with their
// template parameter being either wxSize, wxPoint or int.
namespace wxPrivate
{
template <typename T> class wxRescaleCoordWithValue;
template <typename T>
class wxRescaleCoordWithFrom
{
public:
T To(wxSize newScale) const
{
T value(m_value);
if ( value.x != wxDefaultCoord )
value.x = wxMulDivInt32(value.x, newScale.x, m_oldScale.x);
if ( value.y != wxDefaultCoord )
value.y = wxMulDivInt32(value.y, newScale.y, m_oldScale.y);
return value;
}
T To(int newScaleX, int newScaleY) const
{
return To(wxSize(newScaleX, newScaleY));
}
private:
wxRescaleCoordWithFrom(T value, wxSize oldScale)
: m_value(value), m_oldScale(oldScale)
{
}
const T m_value;
const wxSize m_oldScale;
// Only it can create objects of this class.
friend wxRescaleCoordWithValue<T>;
};
// Specialization for just a single value.
template <>
class wxRescaleCoordWithFrom<int>
{
public:
int To(wxSize newScale) const
{
return m_value == wxDefaultCoord
? wxDefaultCoord
: wxMulDivInt32(m_value, newScale.x, m_oldScale.x);
}
private:
wxRescaleCoordWithFrom(int value, wxSize oldScale)
: m_value(value), m_oldScale(oldScale)
{
}
const int m_value;
const wxSize m_oldScale;
// Only it can create objects of this class.
friend wxRescaleCoordWithValue<int>;
};
template <typename T>
class wxRescaleCoordWithValue
{
public:
explicit wxRescaleCoordWithValue(T value)
: m_value(value)
{
}
wxRescaleCoordWithFrom<T> From(wxSize oldScale)
{
return wxRescaleCoordWithFrom<T>(m_value, oldScale);
}
wxRescaleCoordWithFrom<T> From(int oldScaleX, int oldScaleY)
{
return From(wxSize(oldScaleX, oldScaleY));
}
private:
const T m_value;
};
} // namespace wxPrivate
inline wxPrivate::wxRescaleCoordWithValue<int> wxRescaleCoord(int coord)
{
return wxPrivate::wxRescaleCoordWithValue<int>(coord);
}
inline wxPrivate::wxRescaleCoordWithValue<wxSize> wxRescaleCoord(wxSize sz)
{
return wxPrivate::wxRescaleCoordWithValue<wxSize>(sz);
}
inline wxPrivate::wxRescaleCoordWithValue<wxSize> wxRescaleCoord(int x, int y)
{
return wxPrivate::wxRescaleCoordWithValue<wxSize>(wxSize(x, y));
}
inline wxPrivate::wxRescaleCoordWithValue<wxPoint> wxRescaleCoord(wxPoint pt)
{
return wxPrivate::wxRescaleCoordWithValue<wxPoint>(pt);
}
#endif // _WX_PRIVATE_RESCALE_H_

View File

@@ -3444,6 +3444,39 @@ public:
Returns the new DPI. Returns the new DPI.
*/ */
wxSize GetNewDPI() const; wxSize GetNewDPI() const;
/**
Rescale a value in pixels to match the new DPI.
This is a convenience function to use in wxEVT_DPI_CHANGED event
handlers, as they often need to update some sizes to the new DPI.
It simply calls wxMulDivInt32() with new and old DPI values, but
is more readable and less error-prone.
For example, the returned value will be twice bigger than the original
one when switching from normal (96) DPI to high (2x, 192) DPI.
@since 3.1.6
*/
wxSize Scale(wxSize sz) const;
/**
Rescale horizontal component to match the new DPI.
This is the same as Scale(), but for the horizontal component only.
@since 3.1.6
*/
int ScaleX(int x) const;
/**
Rescale vertical component to match the new DPI.
This is the same as Scale(), but for the vertical component only.
@since 3.1.6
*/
int ScaleY(int y) const;
}; };

View File

@@ -1344,4 +1344,3 @@ void wxDisplaySizeMM(int* width, int* height);
*/ */
wxSize wxGetDisplaySizeMM(); wxSize wxGetDisplaySizeMM();
//@} //@}

View File

@@ -115,5 +115,15 @@ bool wxIsSameDouble(double x, double y);
*/ */
bool wxIsNullDouble(double x); bool wxIsNullDouble(double x);
/**
Computes the product of a number with a fraction with rounding.
This function returns @c n*numerator/denominator rounding the result. It is
similar to the standard Win32 @c MulDiv() function and, in fact, is
implemented by calling it under MSW, where @c wx/msw/wrapwin.h must be
included in addition to @c wx/math.h for it to be used.
*/
int wxMulDivInt32(int n, int numerator, int denominator);
//@} //@}

View File

@@ -51,6 +51,10 @@
wxDEFINE_SCOPED_PTR(wxEvent, wxEventPtr) wxDEFINE_SCOPED_PTR(wxEvent, wxEventPtr)
#endif // wxUSE_BASE #endif // wxUSE_BASE
#if wxUSE_GUI
#include "wx/private/rescale.h"
#endif
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// wxWin macros // wxWin macros
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -933,6 +937,15 @@ wxHelpEvent::Origin wxHelpEvent::GuessOrigin(Origin origin)
return origin; return origin;
} }
// ----------------------------------------------------------------------------
// wxDPIChangedEvent
// ----------------------------------------------------------------------------
wxSize wxDPIChangedEvent::Scale(wxSize sz) const
{
return wxRescaleCoord(sz).From(m_oldDPI).To(m_newDPI);
}
#endif // wxUSE_GUI #endif // wxUSE_GUI

View File

@@ -74,12 +74,9 @@
#include "wx/display.h" #include "wx/display.h"
#include "wx/platinfo.h" #include "wx/platinfo.h"
#include "wx/recguard.h" #include "wx/recguard.h"
#include "wx/private/rescale.h"
#include "wx/private/window.h" #include "wx/private/window.h"
#ifdef __WINDOWS__
#include "wx/msw/wrapwin.h"
#endif
// Windows List // Windows List
WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows;
@@ -2905,12 +2902,9 @@ wxWindowBase::FromDIP(const wxSize& sz, const wxWindowBase* w)
{ {
const wxSize dpi = GetDPIHelper(w); const wxSize dpi = GetDPIHelper(w);
const int baseline = wxDisplay::GetStdPPIValue(); const wxSize baseline = wxDisplay::GetStdPPI();
// Take care to not scale -1 because it has a special meaning of return wxRescaleCoord(sz).From(baseline).To(dpi);
// "unspecified" which should be preserved.
return wxSize(sz.x == -1 ? -1 : wxMulDivInt32(sz.x, dpi.x, baseline),
sz.y == -1 ? -1 : wxMulDivInt32(sz.y, dpi.y, baseline));
} }
/* static */ /* static */
@@ -2919,12 +2913,9 @@ wxWindowBase::ToDIP(const wxSize& sz, const wxWindowBase* w)
{ {
const wxSize dpi = GetDPIHelper(w); const wxSize dpi = GetDPIHelper(w);
const int baseline = wxDisplay::GetStdPPIValue(); const wxSize baseline = wxDisplay::GetStdPPI();
// Take care to not scale -1 because it has a special meaning of return wxRescaleCoord(sz).From(dpi).To(baseline);
// "unspecified" which should be preserved.
return wxSize(sz.x == -1 ? -1 : wxMulDivInt32(sz.x, baseline, dpi.x),
sz.y == -1 ? -1 : wxMulDivInt32(sz.y, baseline, dpi.y));
} }
#endif // !wxHAVE_DPI_INDEPENDENT_PIXELS #endif // !wxHAVE_DPI_INDEPENDENT_PIXELS
@@ -2963,28 +2954,14 @@ wxPoint wxWindowBase::ConvertPixelsToDialog(const wxPoint& pt) const
{ {
const wxSize base = GetDlgUnitBase(); const wxSize base = GetDlgUnitBase();
// NB: wxMulDivInt32() is used, because it correctly rounds the result return wxRescaleCoord(pt).From(base).To(4, 8);
wxPoint pt2 = wxDefaultPosition;
if (pt.x != wxDefaultCoord)
pt2.x = wxMulDivInt32(pt.x, 4, base.x);
if (pt.y != wxDefaultCoord)
pt2.y = wxMulDivInt32(pt.y, 8, base.y);
return pt2;
} }
wxPoint wxWindowBase::ConvertDialogToPixels(const wxPoint& pt) const wxPoint wxWindowBase::ConvertDialogToPixels(const wxPoint& pt) const
{ {
const wxSize base = GetDlgUnitBase(); const wxSize base = GetDlgUnitBase();
wxPoint pt2 = wxDefaultPosition; return wxRescaleCoord(pt).From(4, 8).To(base);
if (pt.x != wxDefaultCoord)
pt2.x = wxMulDivInt32(pt.x, base.x, 4);
if (pt.y != wxDefaultCoord)
pt2.y = wxMulDivInt32(pt.y, base.y, 8);
return pt2;
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -5669,12 +5669,12 @@ void wxDataViewCtrl::OnDPIChanged(wxDPIChangedEvent& event)
{ {
int minWidth = m_cols[i]->GetMinWidth(); int minWidth = m_cols[i]->GetMinWidth();
if ( minWidth > 0 ) if ( minWidth > 0 )
minWidth = minWidth * event.GetNewDPI().x / event.GetOldDPI().x; minWidth = event.ScaleX(minWidth);
m_cols[i]->SetMinWidth(minWidth); m_cols[i]->SetMinWidth(minWidth);
int width = m_cols[i]->WXGetSpecifiedWidth(); int width = m_cols[i]->WXGetSpecifiedWidth();
if ( width > 0 ) if ( width > 0 )
width = width * event.GetNewDPI().x / event.GetOldDPI().x; width = event.ScaleX(width);
m_cols[i]->SetWidth(width); m_cols[i]->SetWidth(width);
} }
} }

View File

@@ -5781,7 +5781,7 @@ void wxGrid::OnDPIChanged(wxDPIChangedEvent& event)
if ( height <= 0 ) if ( height <= 0 )
continue; continue;
height = height * event.GetNewDPI().x / event.GetOldDPI().x; height = event.ScaleY(height);
total += height; total += height;
m_rowHeights[i] = height; m_rowHeights[i] = height;
@@ -5804,7 +5804,7 @@ void wxGrid::OnDPIChanged(wxDPIChangedEvent& event)
if ( width <= 0 ) if ( width <= 0 )
continue; continue;
width = width * event.GetNewDPI().x / event.GetOldDPI().x; width = event.ScaleX(width);
total += width; total += width;
m_colWidths[i] = width; m_colWidths[i] = width;

View File

@@ -42,6 +42,7 @@
#include "wx/stockitem.h" #include "wx/stockitem.h"
#include "wx/msw/private/button.h" #include "wx/msw/private/button.h"
#include "wx/msw/private/dc.h" #include "wx/msw/private/dc.h"
#include "wx/private/rescale.h"
#include "wx/private/window.h" #include "wx/private/window.h"
#if wxUSE_MARKUP #if wxUSE_MARKUP
@@ -185,11 +186,8 @@ wxSize wxButtonBase::GetDefaultSize(wxWindow* win)
// character width metadata stored in the font; see // character width metadata stored in the font; see
// http://support.microsoft.com/default.aspx/kb/145994 for detailed // http://support.microsoft.com/default.aspx/kb/145994 for detailed
// discussion. // discussion.
//
// NB: wxMulDivInt32() is used, because it correctly rounds the result
s_sizeBtn.SetAtNewDPI(wxSize(wxMulDivInt32(50, base.x, 4), s_sizeBtn.SetAtNewDPI(wxRescaleCoord(50, 14).From(4, 8).To(base));
wxMulDivInt32(14, base.y, 8)));
} }
return s_sizeBtn.Get(); return s_sizeBtn.Get();

View File

@@ -446,9 +446,10 @@ void wxListCtrl::OnDPIChanged(wxDPIChangedEvent &event)
for ( int i = 0; i < numCols; ++i ) for ( int i = 0; i < numCols; ++i )
{ {
int width = GetColumnWidth(i); int width = GetColumnWidth(i);
if ( width > 0 ) if ( width <= 0 )
width = width * event.GetNewDPI().x / event.GetOldDPI().x; continue;
SetColumnWidth(i, width);
SetColumnWidth(i, event.ScaleX(width));
} }
} }

View File

@@ -653,11 +653,7 @@ void wxSlider::OnDPIChanged(wxDPIChangedEvent& event)
{ {
int thumbLen = GetThumbLength(); int thumbLen = GetThumbLength();
const double scaleFactor = (double)event.GetNewDPI().x / event.GetOldDPI().x; SetThumbLength(event.ScaleX(thumbLen));
const double thumbLenScaled = thumbLen * scaleFactor;
thumbLen = (int)(scaleFactor > 1.0 ? ceil(thumbLenScaled) : floor(thumbLenScaled));
SetThumbLength(thumbLen);
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -1949,8 +1949,6 @@ void wxToolBar::OnDPIChanged(wxDPIChangedEvent& event)
{ {
// Manually scale the size of the controls. Even though the font has been // Manually scale the size of the controls. Even though the font has been
// updated, the internal size of the controls does not. // updated, the internal size of the controls does not.
const float scaleFactor = (float)event.GetNewDPI().y / event.GetOldDPI().y;
wxToolBarToolsList::compatibility_iterator node; wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() ) for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{ {
@@ -1961,7 +1959,7 @@ void wxToolBar::OnDPIChanged(wxDPIChangedEvent& event)
if ( wxControl* const control = tool->GetControl() ) if ( wxControl* const control = tool->GetControl() )
{ {
const wxSize oldSize = control->GetSize(); const wxSize oldSize = control->GetSize();
wxSize newSize = oldSize * scaleFactor; wxSize newSize = event.Scale(oldSize);
// Use the best height for choice-based controls. // Use the best height for choice-based controls.
// Scaling the current size does not work, because the control // Scaling the current size does not work, because the control

View File

@@ -82,6 +82,7 @@
#include "wx/msw/dcclient.h" #include "wx/msw/dcclient.h"
#include "wx/msw/seh.h" #include "wx/msw/seh.h"
#include "wx/private/textmeasure.h" #include "wx/private/textmeasure.h"
#include "wx/private/rescale.h"
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
#include "wx/tooltip.h" #include "wx/tooltip.h"
@@ -4891,20 +4892,9 @@ void wxWindowMSW::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
} }
} }
// Helper function to update the given coordinate by the scaling factor if it
// is set, i.e. different from wxDefaultCoord.
static void ScaleCoordIfSet(int& coord, float scaleFactor)
{
if ( coord != wxDefaultCoord )
{
const float coordScaled = coord * scaleFactor;
coord = int(scaleFactor > 1 ? std::ceil(coordScaled) : std::floor(coordScaled));
}
}
// Called from MSWUpdateonDPIChange() to recursively update the window // Called from MSWUpdateonDPIChange() to recursively update the window
// sizer and any child sizers and spacers. // sizer and any child sizers and spacers.
static void UpdateSizerOnDPIChange(wxSizer* sizer, float scaleFactor) static void UpdateSizerOnDPIChange(wxSizer* sizer, wxSize oldDPI, wxSize newDPI)
{ {
if ( !sizer ) if ( !sizer )
{ {
@@ -4919,27 +4909,25 @@ static void UpdateSizerOnDPIChange(wxSizer* sizer, float scaleFactor)
wxSizerItem* sizerItem = node->GetData(); wxSizerItem* sizerItem = node->GetData();
int border = sizerItem->GetBorder(); int border = sizerItem->GetBorder();
ScaleCoordIfSet(border, scaleFactor); border = wxRescaleCoord(border).From(oldDPI).To(newDPI);
sizerItem->SetBorder(border); sizerItem->SetBorder(border);
// only scale sizers and spacers, not windows // only scale sizers and spacers, not windows
if ( sizerItem->IsSizer() || sizerItem->IsSpacer() ) if ( sizerItem->IsSizer() || sizerItem->IsSpacer() )
{ {
wxSize min = sizerItem->GetMinSize(); wxSize min = sizerItem->GetMinSize();
ScaleCoordIfSet(min.x, scaleFactor); min = wxRescaleCoord(min).From(oldDPI).To(newDPI);
ScaleCoordIfSet(min.y, scaleFactor);
sizerItem->SetMinSize(min); sizerItem->SetMinSize(min);
if ( sizerItem->IsSpacer() ) if ( sizerItem->IsSpacer() )
{ {
wxSize size = sizerItem->GetSize(); wxSize size = sizerItem->GetSize();
ScaleCoordIfSet(size.x, scaleFactor); size = wxRescaleCoord(size).From(oldDPI).To(newDPI);
ScaleCoordIfSet(size.y, scaleFactor);
sizerItem->SetDimension(wxDefaultPosition, size); sizerItem->SetDimension(wxDefaultPosition, size);
} }
// Update any child sizers if this is a sizer // Update any child sizers if this is a sizer
UpdateSizerOnDPIChange(sizerItem->GetSizer(), scaleFactor); UpdateSizerOnDPIChange(sizerItem->GetSizer(), oldDPI, newDPI);
} }
} }
} }
@@ -4948,12 +4936,10 @@ void
wxWindowMSW::MSWUpdateOnDPIChange(const wxSize& oldDPI, const wxSize& newDPI) wxWindowMSW::MSWUpdateOnDPIChange(const wxSize& oldDPI, const wxSize& newDPI)
{ {
// update min and max size if necessary // update min and max size if necessary
const float scaleFactor = (float)newDPI.y / oldDPI.y; m_minHeight = wxRescaleCoord(m_minHeight).From(oldDPI).To(newDPI);
m_minWidth = wxRescaleCoord(m_minWidth).From(oldDPI).To(newDPI);
ScaleCoordIfSet(m_minHeight, scaleFactor); m_maxHeight = wxRescaleCoord(m_maxHeight).From(oldDPI).To(newDPI);
ScaleCoordIfSet(m_minWidth, scaleFactor); m_maxWidth = wxRescaleCoord(m_maxWidth).From(oldDPI).To(newDPI);
ScaleCoordIfSet(m_maxHeight, scaleFactor);
ScaleCoordIfSet(m_maxWidth, scaleFactor);
InvalidateBestSize(); InvalidateBestSize();
@@ -4961,7 +4947,7 @@ wxWindowMSW::MSWUpdateOnDPIChange(const wxSize& oldDPI, const wxSize& newDPI)
MSWUpdateFontOnDPIChange(newDPI); MSWUpdateFontOnDPIChange(newDPI);
// update sizers // update sizers
UpdateSizerOnDPIChange(GetSizer(), scaleFactor); UpdateSizerOnDPIChange(GetSizer(), oldDPI, newDPI);
// update children // update children
for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst(); for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();

View File

@@ -5441,7 +5441,7 @@ void wxStyledTextCtrl::OnDPIChanged(wxDPIChangedEvent& evt) {
// adjust the margins to the new DPI // adjust the margins to the new DPI
for ( int i = 0; i < SC_MAX_MARGIN; ++i ) for ( int i = 0; i < SC_MAX_MARGIN; ++i )
{ {
SetMarginWidth(i, (int)wxMulDivInt32(GetMarginWidth(i), evt.GetNewDPI().y, evt.GetOldDPI().y)); SetMarginWidth(i, evt.ScaleY(GetMarginWidth(i)));
} }
// Hide auto-complete popup, there is no (easy) way to set it to the correct size // Hide auto-complete popup, there is no (easy) way to set it to the correct size

View File

@@ -968,7 +968,7 @@ void wxStyledTextCtrl::OnDPIChanged(wxDPIChangedEvent& evt) {
// adjust the margins to the new DPI // adjust the margins to the new DPI
for ( int i = 0; i < SC_MAX_MARGIN; ++i ) for ( int i = 0; i < SC_MAX_MARGIN; ++i )
{ {
SetMarginWidth(i, (int)wxMulDivInt32(GetMarginWidth(i), evt.GetNewDPI().y, evt.GetOldDPI().y)); SetMarginWidth(i, evt.ScaleY(GetMarginWidth(i)));
} }
// Hide auto-complete popup, there is no (easy) way to set it to the correct size // Hide auto-complete popup, there is no (easy) way to set it to the correct size

View File

@@ -22,6 +22,11 @@
#include "wx/tarstrm.h" #include "wx/tarstrm.h"
#include "wx/zipstrm.h" #include "wx/zipstrm.h"
#ifdef __WINDOWS__
// Needed for wxMulDivInt32().
#include "wx/msw/wrapwin.h"
#endif
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// test class // test class
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -199,3 +204,12 @@ TEST_CASE("wxRound", "[math]")
#endif #endif
#endif // WXWIN_COMPATIBILITY_3_0 #endif // WXWIN_COMPATIBILITY_3_0
} }
TEST_CASE("wxMulDivInt32", "[math]")
{
// Check that it rounds correctly.
CHECK( wxMulDivInt32(15, 3, 2) == 23 );
// Check that it doesn't overflow.
CHECK( wxMulDivInt32((INT_MAX - 1)/2, 200, 100) == INT_MAX - 1 );
}