Merge branch 'bmp-bundle-pref-size'

Allow wxBitmapBundle specify its preferred size and use it in wxMSW.

See https://github.com/wxWidgets/wxWidgets/pull/2554
This commit is contained in:
Vadim Zeitlin
2021-10-20 16:17:51 +01:00
11 changed files with 286 additions and 14 deletions

View File

@@ -15,6 +15,7 @@
#include "wx/vector.h"
class wxBitmapBundleImpl;
class WXDLLIMPEXP_FWD_CORE wxWindow;
// It should be possible to implement SVG rasterizing without raw bitmap
// support using wxDC::DrawSpline(), but currently we don't do it and so
@@ -96,6 +97,12 @@ public:
// default DPI, i.e. 100% scaling. Returns invalid size for empty bundle.
wxSize GetDefaultSize() const;
// Get preferred size, i.e. usually the closest size in which a bitmap is
// available to the ideal size determined from the default size and the DPI
// scaling, for the given window.
wxSize GetPreferredSizeFor(wxWindow* window) const;
wxSize GetPreferredSizeAtScale(double scale) const;
// Get bitmap of the specified size, creating a new bitmap from the closest
// available size by rescaling it if necessary.
//
@@ -177,6 +184,11 @@ public:
// Must always return a valid size.
virtual wxSize GetDefaultSize() const = 0;
// Return the preferred size that should be used at the given scale.
//
// Must always return a valid size.
virtual wxSize GetPreferredSizeAtScale(double scale) const = 0;
// Retrieve the bitmap of exactly the given size.
//
// Note that this function is non-const because it may generate the bitmap

View File

@@ -252,6 +252,31 @@ public:
*/
wxSize GetDefaultSize() const;
/**
Get the size that would be best to use for this bundle at the given DPI
scaling factor.
For bundles containing some number of the fixed-size bitmaps, this
function returns the size of an existing bitmap closest to the ideal
size at the given scale, i.e. GetDefaultSize() multiplied by @a scale.
Passing a size returned by this function to GetBitmap() ensures that
bitmap doesn't need to be rescaled, which typically significantly
lowers its quality.
*/
wxSize GetPreferredSizeAtScale(double scale) const;
/**
Get the size that would be best to use for this bundle at the DPI
scaling factor used by the given window.
This is just a convenient wrapper for GetPreferredSizeAtScale() calling
that function with the result of wxWindow::GetDPIScaleFactor().
@param window Non-null and fully created window.
*/
wxSize GetPreferredSizeFor(wxWindow* window) const;
/**
Get bitmap of the specified size, creating a new bitmap from the closest
available size by rescaling it if necessary.
@@ -285,6 +310,16 @@ public:
... determine the minimum/default size for bitmap to use ...
}
wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE
{
// If it's ok to scale the bitmap, just use the standard size
// at the given scale:
return GetDefaultSize()*scale;
... otherwise, an existing bitmap of the size closest to the
one above would need to be found and its size returned ...
}
wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE
{
... get the bitmap of the requested size from somewhere and
@@ -314,6 +349,13 @@ public:
*/
virtual wxSize GetDefaultSize() const = 0;
/**
Return the preferred size that should be used at the given scale.
Must always return a valid size.
*/
virtual wxSize GetPreferredSizeAtScale(double scale) const = 0;
/**
Retrieve the bitmap of exactly the given size.

View File

@@ -520,6 +520,13 @@ void MyFrame::PopulateToolbar(wxToolBarBase* toolBar)
return m_sizeDef;
}
wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE
{
// We just scale the bitmap to fit the requested size, so
// we don't really have any preferences.
return m_sizeDef*scale;
}
wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE
{
// In this simple implementation we don't bother caching

View File

@@ -23,6 +23,7 @@
#include "wx/bmpbndl.h"
#include "wx/icon.h"
#include "wx/window.h"
#include "wx/private/bmpbndl.h"
@@ -64,6 +65,7 @@ public:
}
virtual wxSize GetDefaultSize() const wxOVERRIDE;
virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
#ifdef __WXOSX__
@@ -157,6 +159,47 @@ wxSize wxBitmapBundleImplSet::GetDefaultSize() const
return m_entries[0].bitmap.GetSize();
}
wxSize wxBitmapBundleImplSet::GetPreferredSizeAtScale(double scale) const
{
// Target size is the ideal size we'd like the bitmap to have at this scale.
const wxSize sizeTarget = GetDefaultSize()*scale;
const size_t n = m_entries.size();
for ( size_t i = 0; i < n; ++i )
{
const wxSize sizeThis = m_entries[i].bitmap.GetSize();
// Keep looking for the exact match which we still can hope to find
// while the current bitmap is smaller.
if ( sizeThis.y < sizeTarget.y )
continue;
// If we've found the exact match, just return it.
if ( sizeThis.y == sizeTarget.y )
return sizeThis;
// We've found the closest bigger bitmap.
// If there is no smaller bitmap, we have no choice but to use this one.
if ( i == 0 )
return sizeThis;
// Decide whether we should use this one or the previous smaller one
// depending on which of them is closer to the target size, breaking
// the tie in favour of the bigger size.
const wxSize sizeLast = m_entries[i - 1].bitmap.GetSize();
return sizeThis.y - sizeTarget.y <= sizeTarget.y - sizeLast.y
? sizeThis
: sizeLast;
}
// We only get here if the target size is bigger than all the available
// sizes, in which case we have no choice but to use the biggest bitmap.
return m_entries.back().bitmap.GetSize();
}
wxBitmap wxBitmapBundleImplSet::GetBitmap(const wxSize& size)
{
// We use linear search instead if binary one because it's simpler and the
@@ -326,6 +369,21 @@ wxSize wxBitmapBundle::GetDefaultSize() const
return m_impl->GetDefaultSize();
}
wxSize wxBitmapBundle::GetPreferredSizeFor(wxWindow* window) const
{
wxCHECK_MSG( window, wxDefaultSize, "window must be valid" );
return GetPreferredSizeAtScale(window->GetDPIScaleFactor());
}
wxSize wxBitmapBundle::GetPreferredSizeAtScale(double scale) const
{
if ( !m_impl )
return wxDefaultSize;
return m_impl->GetPreferredSizeAtScale(scale);
}
wxBitmap wxBitmapBundle::GetBitmap(const wxSize& size) const
{
if ( !m_impl )

View File

@@ -32,6 +32,7 @@
#include "wx/image.h"
#endif // WXWIN_COMPATIBILITY_2_8
#include "wx/menu.h"
#include "wx/vector.h"
#endif
extern WXDLLEXPORT_DATA(const char) wxToolBarNameStr[] = "toolbar";
@@ -432,6 +433,39 @@ void wxToolBarBase::ClearTools()
}
}
namespace
{
// Struct containing the number of tools preferring to use the given size.
struct SizePrefWithCount
{
SizePrefWithCount() : count(0) { }
wxSize size;
int count;
};
typedef wxVector<SizePrefWithCount> SizePrefs;
void RecordSizePref(SizePrefs& prefs, const wxSize& size)
{
for ( size_t n = 0; n < prefs.size(); ++n )
{
if ( prefs[n].size == size )
{
prefs[n].count++;
return;
}
}
SizePrefWithCount pref;
pref.size = size;
pref.count++;
prefs.push_back(pref);
}
} // anonymous namespace
void wxToolBarBase::AdjustToolBitmapSize()
{
if ( HasFlag(wxTB_NOICONS) )
@@ -442,8 +476,10 @@ void wxToolBarBase::AdjustToolBitmapSize()
const wxSize sizeOrig(m_defaultWidth, m_defaultHeight);
wxSize sizeActual(sizeOrig);
// We want to use preferred bitmap size, but the preferred sizes can be
// different for different bitmap bundles, so record all their preferences
// first.
SizePrefs prefs;
const double scale = GetDPIScaleFactor();
for ( wxToolBarToolsList::const_iterator i = m_tools.begin();
i != m_tools.end();
@@ -451,11 +487,48 @@ void wxToolBarBase::AdjustToolBitmapSize()
{
const wxBitmapBundle& bmp = (*i)->GetNormalBitmapBundle();
if ( bmp.IsOk() )
sizeActual.IncTo(bmp.GetDefaultSize()*scale);
RecordSizePref(prefs, bmp.GetPreferredSizeAtScale(scale));
}
if ( sizeActual != sizeOrig )
SetToolBitmapSize(sizeActual);
// Now find the size preferred by most tools.
int countMax = 0;
wxSize sizePreferred;
for ( size_t n = 0; n < prefs.size(); ++n )
{
const int countThis = prefs[n].count;
const wxSize sizeThis = prefs[n].size;
if ( countThis > countMax )
{
countMax = countThis;
sizePreferred = sizeThis;
}
else if ( countThis == countMax )
{
// We have a tie between different sizes, choose the one
// corresponding to the current scale factor, if possible, as this
// is the ideal bitmap size that should be consistent with all the
// other bitmaps.
if ( sizePreferred != sizeOrig*scale )
{
if ( sizeThis == sizeOrig*scale )
{
sizePreferred = sizeThis;
}
else // Neither of the sizes is the ideal one.
{
// Choose the larger one as like this some bitmaps will be
// downscaled, which should look better than upscaling some
// (other) ones.
if ( sizeThis.y > sizePreferred.y )
sizePreferred = sizeThis;
}
}
}
}
if ( sizePreferred != sizeOrig )
SetToolBitmapSize(sizePreferred);
}
bool wxToolBarBase::Realize()

View File

@@ -93,6 +93,7 @@ public:
}
virtual wxSize GetDefaultSize() const wxOVERRIDE;
virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
private:
@@ -126,6 +127,12 @@ wxSize wxBitmapBundleImplSVG::GetDefaultSize() const
return m_sizeDef;
}
wxSize wxBitmapBundleImplSVG::GetPreferredSizeAtScale(double scale) const
{
// We consider that we can render at any scale.
return m_sizeDef*scale;
}
wxBitmap wxBitmapBundleImplSVG::GetBitmap(const wxSize& size)
{
if ( !m_cachedBitmap.IsOk() || m_cachedBitmap.GetSize() != size )

View File

@@ -107,7 +107,7 @@ class wxButtonImageData: public wxObject
public:
wxButtonImageData(wxWindow* btn, const wxBitmapBundle& normalBundle)
{
m_bitmapSize = normalBundle.GetDefaultSize() * btn->GetDPIScaleFactor();
m_bitmapSize = normalBundle.GetPreferredSizeFor(btn);
m_bitmapBundles[wxAnyButton::State_Normal] = normalBundle;
}
@@ -254,7 +254,7 @@ public:
// the image list
wxXPButtonImageData(wxAnyButton *btn, const wxBitmapBundle& bitmapBundle)
: wxButtonImageData(btn, bitmapBundle),
m_hwndBtn(GetHwndOf(btn))
m_btn(btn)
{
InitImageList();
@@ -403,7 +403,7 @@ private:
void UpdateImageInfo()
{
if ( !::SendMessage(m_hwndBtn, BCM_SETIMAGELIST, 0, (LPARAM)&m_data) )
if ( !::SendMessage(GetHwndOf(m_btn), BCM_SETIMAGELIST, 0, (LPARAM)&m_data) )
{
wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
}
@@ -415,7 +415,7 @@ private:
// We need to recreate the image list using the new size and re-add all
// bitmaps to it.
m_bitmapSize = event.Scale(m_bitmapSize);
m_bitmapSize = m_bitmapBundles[wxAnyButton::State_Normal].GetPreferredSizeFor(m_btn);
m_iml.Destroy();
InitImageList();
@@ -431,7 +431,7 @@ private:
BUTTON_IMAGELIST m_data;
// the button we're associated with
const HWND m_hwndBtn;
wxWindow* const m_btn;
wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData);

View File

@@ -138,6 +138,7 @@ public:
const wxBitmap& bitmap);
virtual wxSize GetDefaultSize() const wxOVERRIDE;
virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
private:
@@ -185,6 +186,52 @@ wxSize wxBitmapBundleImplRC::GetDefaultSize() const
return m_bitmaps[0].GetSize();
}
wxSize wxBitmapBundleImplRC::GetPreferredSizeAtScale(double scale) const
{
// Optimistically assume we're going to use this exact scale by default.
double scalePreferred = scale;
for ( size_t i = 0; ; ++i )
{
if ( i == m_resourceInfos.size() )
{
// The requested scale is bigger than anything we have, so use the
// biggest available one.
scalePreferred = m_resourceInfos[i - 1].scale;
break;
}
const double scaleThis = m_resourceInfos[i].scale;
// Keep looking for the exact match which we still can hope to find
// while the current scale is smaller.
if ( scaleThis < scale )
continue;
// If we've found the exact match, just use it.
if ( scaleThis == scale )
break;
// We've found the closest bigger scale.
// If there is no smaller one, we have no choice but to use this one.
if ( i == 0 )
break;
// Decide whether we should use this one or the previous smaller one
// depending on which of them is closer to the target scale, breaking
// the tie in favour of the bigger one.
const double scaleLast = m_resourceInfos[i - 1].scale;
scalePreferred = scaleThis - scale <= scale - scaleLast
? scaleThis
: scaleLast;
break;
}
return GetDefaultSize()*scalePreferred;
}
wxBitmap
wxBitmapBundleImplRC::AddBitmap(const ResourceInfo& info,
const wxSize& sizeExpected,

View File

@@ -1949,10 +1949,6 @@ void wxToolBar::RealizeHelper()
void wxToolBar::OnDPIChanged(wxDPIChangedEvent& event)
{
// Ensure that when Realize() is called, the bitmaps size corresponding to
// the new resolution will be used.
SetToolBitmapSize(event.Scale(wxSize(m_defaultWidth, m_defaultHeight)));
// Manually scale the size of the controls. Even though the font has been
// updated, the internal size of the controls does not.
wxToolBarToolsList::compatibility_iterator node;

View File

@@ -46,6 +46,7 @@ public:
~wxOSXImageBundleImpl();
virtual wxSize GetDefaultSize() const wxOVERRIDE;
virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
virtual WXImage OSXGetImage() const wxOVERRIDE;
@@ -76,6 +77,14 @@ wxSize wxOSXImageBundleImpl::GetDefaultSize() const
return wxSize(sz.width, sz.height);
}
wxSize wxOSXImageBundleImpl::GetPreferredSizeAtScale(double scale) const
{
// The system always performs scaling, as the scaling factor is integer and
// so it doesn't make sense to round it up or down, hence we should use the
// theoretical best size for given scale.
return GetDefaultSize()*scale;
}
wxBitmap wxOSXImageBundleImpl::GetBitmap(const wxSize& size)
{
return wxBitmap();

View File

@@ -47,6 +47,27 @@ TEST_CASE("BitmapBundle::FromBitmaps", "[bmpbundle]")
CHECK( b.GetBitmap(wxSize(24, 24)).GetSize() == wxSize(24, 24) );
}
TEST_CASE("BitmapBundle::GetPreferredSize", "[bmpbundle]")
{
CHECK( wxBitmapBundle().GetPreferredSizeAtScale(1) == wxDefaultSize );
const wxSize normal(32, 32);
const wxSize bigger(64, 64);
const wxBitmapBundle
b = wxBitmapBundle::FromBitmaps(wxBitmap(normal), wxBitmap(bigger));
CHECK( b.GetPreferredSizeAtScale(0 ) == normal );
CHECK( b.GetPreferredSizeAtScale(1 ) == normal );
CHECK( b.GetPreferredSizeAtScale(1.25) == normal );
CHECK( b.GetPreferredSizeAtScale(1.4 ) == normal );
CHECK( b.GetPreferredSizeAtScale(1.5 ) == bigger );
CHECK( b.GetPreferredSizeAtScale(1.75) == bigger );
CHECK( b.GetPreferredSizeAtScale(2 ) == bigger );
CHECK( b.GetPreferredSizeAtScale(2.5 ) == bigger );
CHECK( b.GetPreferredSizeAtScale(3 ) == bigger );
}
#ifdef wxHAS_SVG
TEST_CASE("BitmapBundle::FromSVG", "[bmpbundle][svg]")