diff --git a/include/wx/event.h b/include/wx/event.h index ff7a38b48f..49f2bce205 100644 --- a/include/wx/event.h +++ b/include/wx/event.h @@ -3140,6 +3140,13 @@ public: wxSize GetOldDPI() const { return m_oldDPI; } 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); } private: diff --git a/include/wx/private/rescale.h b/include/wx/private/rescale.h new file mode 100644 index 0000000000..d5a8b81831 --- /dev/null +++ b/include/wx/private/rescale.h @@ -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 +// 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 class wxRescaleCoordWithValue; + +template +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; +}; + +// Specialization for just a single value. +template <> +class wxRescaleCoordWithFrom +{ +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; +}; + +template +class wxRescaleCoordWithValue +{ +public: + explicit wxRescaleCoordWithValue(T value) + : m_value(value) + { + } + + wxRescaleCoordWithFrom From(wxSize oldScale) + { + return wxRescaleCoordWithFrom(m_value, oldScale); + } + + wxRescaleCoordWithFrom From(int oldScaleX, int oldScaleY) + { + return From(wxSize(oldScaleX, oldScaleY)); + } + +private: + const T m_value; +}; + +} // namespace wxPrivate + +inline wxPrivate::wxRescaleCoordWithValue wxRescaleCoord(int coord) +{ + return wxPrivate::wxRescaleCoordWithValue(coord); +} + +inline wxPrivate::wxRescaleCoordWithValue wxRescaleCoord(wxSize sz) +{ + return wxPrivate::wxRescaleCoordWithValue(sz); +} + +inline wxPrivate::wxRescaleCoordWithValue wxRescaleCoord(int x, int y) +{ + return wxPrivate::wxRescaleCoordWithValue(wxSize(x, y)); +} + +inline wxPrivate::wxRescaleCoordWithValue wxRescaleCoord(wxPoint pt) +{ + return wxPrivate::wxRescaleCoordWithValue(pt); +} + +#endif // _WX_PRIVATE_RESCALE_H_ diff --git a/interface/wx/event.h b/interface/wx/event.h index 8113b1b9a9..d9bfe9b417 100644 --- a/interface/wx/event.h +++ b/interface/wx/event.h @@ -3444,6 +3444,39 @@ public: Returns the new DPI. */ 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; }; diff --git a/interface/wx/gdicmn.h b/interface/wx/gdicmn.h index 32a128eaff..bccaa6888f 100644 --- a/interface/wx/gdicmn.h +++ b/interface/wx/gdicmn.h @@ -1344,4 +1344,3 @@ void wxDisplaySizeMM(int* width, int* height); */ wxSize wxGetDisplaySizeMM(); //@} - diff --git a/interface/wx/math.h b/interface/wx/math.h index 5e0b6e3d2c..6043de64c9 100644 --- a/interface/wx/math.h +++ b/interface/wx/math.h @@ -115,5 +115,15 @@ bool wxIsSameDouble(double x, double y); */ 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); + //@} diff --git a/src/common/event.cpp b/src/common/event.cpp index 4b1409cb24..2ac6d35fde 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -51,6 +51,10 @@ wxDEFINE_SCOPED_PTR(wxEvent, wxEventPtr) #endif // wxUSE_BASE +#if wxUSE_GUI + #include "wx/private/rescale.h" +#endif + // ---------------------------------------------------------------------------- // wxWin macros // ---------------------------------------------------------------------------- @@ -933,6 +937,15 @@ wxHelpEvent::Origin wxHelpEvent::GuessOrigin(Origin origin) return origin; } +// ---------------------------------------------------------------------------- +// wxDPIChangedEvent +// ---------------------------------------------------------------------------- + +wxSize wxDPIChangedEvent::Scale(wxSize sz) const +{ + return wxRescaleCoord(sz).From(m_oldDPI).To(m_newDPI); +} + #endif // wxUSE_GUI diff --git a/src/common/wincmn.cpp b/src/common/wincmn.cpp index 5771b95c6a..bb8127670c 100644 --- a/src/common/wincmn.cpp +++ b/src/common/wincmn.cpp @@ -74,12 +74,9 @@ #include "wx/display.h" #include "wx/platinfo.h" #include "wx/recguard.h" +#include "wx/private/rescale.h" #include "wx/private/window.h" -#ifdef __WINDOWS__ - #include "wx/msw/wrapwin.h" -#endif - // Windows List WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; @@ -2905,12 +2902,9 @@ wxWindowBase::FromDIP(const wxSize& sz, const wxWindowBase* 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 - // "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)); + return wxRescaleCoord(sz).From(baseline).To(dpi); } /* static */ @@ -2919,12 +2913,9 @@ wxWindowBase::ToDIP(const wxSize& sz, const wxWindowBase* 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 - // "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)); + return wxRescaleCoord(sz).From(dpi).To(baseline); } #endif // !wxHAVE_DPI_INDEPENDENT_PIXELS @@ -2963,28 +2954,14 @@ wxPoint wxWindowBase::ConvertPixelsToDialog(const wxPoint& pt) const { const wxSize base = GetDlgUnitBase(); - // NB: wxMulDivInt32() is used, because it correctly rounds the result - - 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; + return wxRescaleCoord(pt).From(base).To(4, 8); } wxPoint wxWindowBase::ConvertDialogToPixels(const wxPoint& pt) const { const wxSize base = GetDlgUnitBase(); - wxPoint pt2 = wxDefaultPosition; - 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; + return wxRescaleCoord(pt).From(4, 8).To(base); } // ---------------------------------------------------------------------------- diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index a51b48190b..43c9facee3 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -5669,12 +5669,12 @@ void wxDataViewCtrl::OnDPIChanged(wxDPIChangedEvent& event) { int minWidth = m_cols[i]->GetMinWidth(); if ( minWidth > 0 ) - minWidth = minWidth * event.GetNewDPI().x / event.GetOldDPI().x; + minWidth = event.ScaleX(minWidth); m_cols[i]->SetMinWidth(minWidth); int width = m_cols[i]->WXGetSpecifiedWidth(); if ( width > 0 ) - width = width * event.GetNewDPI().x / event.GetOldDPI().x; + width = event.ScaleX(width); m_cols[i]->SetWidth(width); } } diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index b4b4fcdcc8..9e99ef2d74 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -5781,7 +5781,7 @@ void wxGrid::OnDPIChanged(wxDPIChangedEvent& event) if ( height <= 0 ) continue; - height = height * event.GetNewDPI().x / event.GetOldDPI().x; + height = event.ScaleY(height); total += height; m_rowHeights[i] = height; @@ -5804,7 +5804,7 @@ void wxGrid::OnDPIChanged(wxDPIChangedEvent& event) if ( width <= 0 ) continue; - width = width * event.GetNewDPI().x / event.GetOldDPI().x; + width = event.ScaleX(width); total += width; m_colWidths[i] = width; diff --git a/src/msw/button.cpp b/src/msw/button.cpp index f49b974640..42408a4a32 100644 --- a/src/msw/button.cpp +++ b/src/msw/button.cpp @@ -42,6 +42,7 @@ #include "wx/stockitem.h" #include "wx/msw/private/button.h" #include "wx/msw/private/dc.h" +#include "wx/private/rescale.h" #include "wx/private/window.h" #if wxUSE_MARKUP @@ -185,11 +186,8 @@ wxSize wxButtonBase::GetDefaultSize(wxWindow* win) // character width metadata stored in the font; see // http://support.microsoft.com/default.aspx/kb/145994 for detailed // discussion. - // - // NB: wxMulDivInt32() is used, because it correctly rounds the result - s_sizeBtn.SetAtNewDPI(wxSize(wxMulDivInt32(50, base.x, 4), - wxMulDivInt32(14, base.y, 8))); + s_sizeBtn.SetAtNewDPI(wxRescaleCoord(50, 14).From(4, 8).To(base)); } return s_sizeBtn.Get(); diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index 3e0b0ebcec..f48ad7e3e9 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -446,9 +446,10 @@ void wxListCtrl::OnDPIChanged(wxDPIChangedEvent &event) for ( int i = 0; i < numCols; ++i ) { int width = GetColumnWidth(i); - if ( width > 0 ) - width = width * event.GetNewDPI().x / event.GetOldDPI().x; - SetColumnWidth(i, width); + if ( width <= 0 ) + continue; + + SetColumnWidth(i, event.ScaleX(width)); } } diff --git a/src/msw/slider.cpp b/src/msw/slider.cpp index ccc1f5df95..6334a23fde 100644 --- a/src/msw/slider.cpp +++ b/src/msw/slider.cpp @@ -653,11 +653,7 @@ void wxSlider::OnDPIChanged(wxDPIChangedEvent& event) { int thumbLen = GetThumbLength(); - const double scaleFactor = (double)event.GetNewDPI().x / event.GetOldDPI().x; - const double thumbLenScaled = thumbLen * scaleFactor; - thumbLen = (int)(scaleFactor > 1.0 ? ceil(thumbLenScaled) : floor(thumbLenScaled)); - - SetThumbLength(thumbLen); + SetThumbLength(event.ScaleX(thumbLen)); } // ---------------------------------------------------------------------------- diff --git a/src/msw/toolbar.cpp b/src/msw/toolbar.cpp index af6dbc1ca1..07ccd29108 100644 --- a/src/msw/toolbar.cpp +++ b/src/msw/toolbar.cpp @@ -1949,8 +1949,6 @@ void wxToolBar::OnDPIChanged(wxDPIChangedEvent& event) { // Manually scale the size of the controls. Even though the font has been // updated, the internal size of the controls does not. - const float scaleFactor = (float)event.GetNewDPI().y / event.GetOldDPI().y; - wxToolBarToolsList::compatibility_iterator node; for ( node = m_tools.GetFirst(); node; node = node->GetNext() ) { @@ -1961,7 +1959,7 @@ void wxToolBar::OnDPIChanged(wxDPIChangedEvent& event) if ( wxControl* const control = tool->GetControl() ) { const wxSize oldSize = control->GetSize(); - wxSize newSize = oldSize * scaleFactor; + wxSize newSize = event.Scale(oldSize); // Use the best height for choice-based controls. // Scaling the current size does not work, because the control diff --git a/src/msw/window.cpp b/src/msw/window.cpp index e869d58539..cba433d6be 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -82,6 +82,7 @@ #include "wx/msw/dcclient.h" #include "wx/msw/seh.h" #include "wx/private/textmeasure.h" +#include "wx/private/rescale.h" #if wxUSE_TOOLTIPS #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 // 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 ) { @@ -4919,27 +4909,25 @@ static void UpdateSizerOnDPIChange(wxSizer* sizer, float scaleFactor) wxSizerItem* sizerItem = node->GetData(); int border = sizerItem->GetBorder(); - ScaleCoordIfSet(border, scaleFactor); + border = wxRescaleCoord(border).From(oldDPI).To(newDPI); sizerItem->SetBorder(border); // only scale sizers and spacers, not windows if ( sizerItem->IsSizer() || sizerItem->IsSpacer() ) { wxSize min = sizerItem->GetMinSize(); - ScaleCoordIfSet(min.x, scaleFactor); - ScaleCoordIfSet(min.y, scaleFactor); + min = wxRescaleCoord(min).From(oldDPI).To(newDPI); sizerItem->SetMinSize(min); if ( sizerItem->IsSpacer() ) { wxSize size = sizerItem->GetSize(); - ScaleCoordIfSet(size.x, scaleFactor); - ScaleCoordIfSet(size.y, scaleFactor); + size = wxRescaleCoord(size).From(oldDPI).To(newDPI); sizerItem->SetDimension(wxDefaultPosition, size); } // 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) { // update min and max size if necessary - const float scaleFactor = (float)newDPI.y / oldDPI.y; - - ScaleCoordIfSet(m_minHeight, scaleFactor); - ScaleCoordIfSet(m_minWidth, scaleFactor); - ScaleCoordIfSet(m_maxHeight, scaleFactor); - ScaleCoordIfSet(m_maxWidth, scaleFactor); + m_minHeight = wxRescaleCoord(m_minHeight).From(oldDPI).To(newDPI); + m_minWidth = wxRescaleCoord(m_minWidth).From(oldDPI).To(newDPI); + m_maxHeight = wxRescaleCoord(m_maxHeight).From(oldDPI).To(newDPI); + m_maxWidth = wxRescaleCoord(m_maxWidth).From(oldDPI).To(newDPI); InvalidateBestSize(); @@ -4961,7 +4947,7 @@ wxWindowMSW::MSWUpdateOnDPIChange(const wxSize& oldDPI, const wxSize& newDPI) MSWUpdateFontOnDPIChange(newDPI); // update sizers - UpdateSizerOnDPIChange(GetSizer(), scaleFactor); + UpdateSizerOnDPIChange(GetSizer(), oldDPI, newDPI); // update children for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst(); diff --git a/src/stc/stc.cpp b/src/stc/stc.cpp index 752a69c70a..a917e39432 100644 --- a/src/stc/stc.cpp +++ b/src/stc/stc.cpp @@ -5441,7 +5441,7 @@ void wxStyledTextCtrl::OnDPIChanged(wxDPIChangedEvent& evt) { // adjust the margins to the new DPI 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 diff --git a/src/stc/stc.cpp.in b/src/stc/stc.cpp.in index 81b5c7bf3f..7022ba7056 100644 --- a/src/stc/stc.cpp.in +++ b/src/stc/stc.cpp.in @@ -968,7 +968,7 @@ void wxStyledTextCtrl::OnDPIChanged(wxDPIChangedEvent& evt) { // adjust the margins to the new DPI 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 diff --git a/tests/misc/misctests.cpp b/tests/misc/misctests.cpp index 328c502f31..7ec98633b8 100644 --- a/tests/misc/misctests.cpp +++ b/tests/misc/misctests.cpp @@ -22,6 +22,11 @@ #include "wx/tarstrm.h" #include "wx/zipstrm.h" +#ifdef __WINDOWS__ + // Needed for wxMulDivInt32(). + #include "wx/msw/wrapwin.h" +#endif + // ---------------------------------------------------------------------------- // test class // ---------------------------------------------------------------------------- @@ -199,3 +204,12 @@ TEST_CASE("wxRound", "[math]") #endif #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 ); +}