Replace InformFirstDirection() with better API

The functions InformFirstDirection() in wxWindow and wxSizer forced the
code overriding it to be written in unnatural way, by storing the
parameters passed to this function and then using them in other
functions later.

Add new functions wxWindow::GetMinSizeFromKnownDirection() and
wxSizer::CalcMinSizeFromKnownDirection() taking the same parameters as
InformFirstDirection() but which can use them directly to return the
desired result.

Change wxStaticText and wxWrapSizer to use them instead of the old
function to simplify their code (that of wxWrapSizer could probably be
still simplified further, as it doesn't seem necessary to store these
values at all any longer, but for now keep it as is).
This commit is contained in:
Vadim Zeitlin
2025-10-14 12:41:20 +02:00
parent 5aa6ff1706
commit 11a8ed6cbb
11 changed files with 231 additions and 131 deletions

View File

@@ -624,15 +624,6 @@ public:
virtual void Clear( bool delete_windows = false );
virtual void DeleteWindows();
// Inform sizer about the first direction that has been decided (by parent item)
// Returns true if it made use of the information (and recalculated min size)
//
// Note that while this method doesn't do anything by default, it should
// almost always be overridden in the derived classes and should have been
// pure virtual if not for backwards compatibility constraints.
virtual bool InformFirstDirection( int WXUNUSED(direction), int WXUNUSED(size), int WXUNUSED(availableOtherDir) )
{ return false; }
void SetMinSize( int width, int height )
{ DoSetMinSize( width, height ); }
void SetMinSize( const wxSize& size )
@@ -669,9 +660,12 @@ public:
// this size to really update all the sizer items.
virtual wxSize CalcMin() = 0;
// Can be overridden to compute minimal size if it depends on the layout
// direction set by InformFirstDirection().
virtual wxSize CalcMinUsingLayoutDirection() { return CalcMin(); }
// Can be overridden to return adjusted minimal size when the size in the
// given direction is already known.
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir);
// This method should be overridden but isn't pure virtual for backwards
// compatibility.
@@ -709,10 +703,6 @@ public:
m_position = pos;
m_size = size;
Layout();
// This call is required for wxWrapSizer to be able to calculate its
// minimal size correctly.
InformFirstDirection(wxHORIZONTAL, size.x, size.y);
}
void SetDimension(int x, int y, int width, int height)
{ SetDimension(wxPoint(x, y), wxSize(width, height)); }
@@ -751,6 +741,11 @@ public:
// items are shown.
virtual bool AreAnyItemsShown() const;
// Don't use in the new code, override CalcMinSizeFromKnownDirection()
// instead.
virtual bool InformFirstDirection( int WXUNUSED(direction), int WXUNUSED(size), int WXUNUSED(availableOtherDir) )
{ return false; }
protected:
wxSize m_size;
wxSize m_minSize;
@@ -993,12 +988,12 @@ public:
// implementation of our resizing logic
virtual wxSize CalcMin() override;
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) override;
virtual void RepositionChildren(const wxSize& minSize) override;
virtual bool InformFirstDirection(int direction,
int size,
int availableOtherDir) override;
protected:
// Only overridden to perform extra debugging checks.
virtual wxSizerItem *DoInsert(size_t index, wxSizerItem *item) override;

View File

@@ -40,15 +40,17 @@ public:
// This function will modify the value returned by GetLabel()!
void Wrap(int width);
virtual wxSize GetMinSizeUsingLayoutDirection() const override;
virtual bool
InformFirstDirection(int direction, int size, int availableOtherDir) override;
// overridden base virtuals
virtual wxSize
GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) override;
virtual bool AcceptsFocus() const override { return false; }
virtual bool HasTransparentBackground() override { return true; }
virtual void SetWindowStyleFlag(long style) override;
bool IsEllipsized() const
{
return (GetWindowStyle() & wxST_ELLIPSIZE_MASK) != 0;

View File

@@ -480,9 +480,22 @@ public:
int GetMaxWidth() const { return GetMaxSize().x; }
int GetMaxHeight() const { return GetMaxSize().y; }
// Can be overridden to compute minimal size if it depends on the
// layout direction set by InformFirstDirection().
virtual wxSize GetMinSizeUsingLayoutDirection() const { return GetMinSize(); }
// This function may be overridden to return the minimal size if it
// depends on the size known to be available in some direction, e.g.
// for text wrapping controls to compute the number of lines needed to
// fit all their text in the given width.
//
// It must return wxDefaultSize if the size of this window doesn't
// depend on the size in the given direction to avoid unnecessary
// relayouts.
//
// Default implementation calls InformFirstDirection() for
// compatibility and then returns either GetEffectiveMinSize() or
// wxDefaultSize depending on InformFirstDirection() return value.
virtual wxSize
GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir);
// Methods for accessing the virtual size of a window. For most
// windows this is just the client area of the window, but for

View File

@@ -36,13 +36,12 @@ public:
// override base class virtual methods
virtual wxSize CalcMin() override;
virtual wxSize CalcMinUsingLayoutDirection() override;
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) override;
virtual void RepositionChildren(const wxSize& minSize) override;
virtual bool InformFirstDirection(int direction,
int size,
int availableOtherDir) override;
protected:
// This method is called to decide if an item represents empty space or
// not. We do this to avoid having space-only items first or last on a

View File

@@ -337,25 +337,44 @@ public:
virtual wxSize CalcMin() = 0;
/**
This method may be overridden by sizers whose minimal size depends on
the layout direction.
May be overridden by sizers whose minimal size depends on the layout
direction.
It is called after InformFirstDirection(), so the implementation may
use the direction provided to this function to decide which size to
return.
It may be useful to override it if the sizer minimal size varies
depending on its size in some direction. For example, wxWrapSizer uses
it to determine the smallest size it can use and still show all of its
items when the size in some direction is fixed, e.g. it returns the
width of the widest control when @a direction is wxVERTICAL or the
total height of all controls wrapped at the given width when @a
direction is wxHORIZONTAL.
The typical and almost only example of a sizer needing to override this
function is wxWrapSizer which can stretch in either direction.
If the sizer minimal size doesn't depend on the size known in the given
direction, this function should return wxDefaultSize to avoid
unnecessary re-layouts.
The method must return the smallest possible size that this sizer can be
shrunk to, e.g. the width of a single control when the major layout
direction is vertical.
The default implementation simply returns wxDefaultSize (after calling
InformFirstDirection() for backward compatibility).
By default, this method simply returns the result of CalcMin().
@param direction
The direction in which the size is fixed, either ::wxHORIZONTAL or
::wxVERTICAL.
@param size
The size in the direction given by the @a direction parameter,
always valid, i.e. positive.
@param availableOtherDir
The size available in the other direction, may be -1 if the
available size is not known.
@return
The minimal size of the sizer when its size in the given
@a direction is fixed to @a size or ::wxDefaultSize if the minimum
size doesn't depend on the layout direction and is always the same.
@since 3.3.2
*/
virtual wxSize CalcMinUsingLayoutDirection() const;
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir);
/**
Detaches all children from the sizer.
@@ -454,11 +473,10 @@ public:
void FitInside(wxWindow* window);
/**
Inform sizer about the first direction that has been decided (by
parent item). Returns true if it made use of the information (and
recalculated min size).
Compatibility function called by CalcMinSizeFromKnownDirection().
@see wxWindow::GetMinSizeUsingLayoutDirection(), CalcMinUsingLayoutDirection()
This function shouldn't be used in the new code, please override
CalcMinSizeFromKnownDirection() instead.
*/
virtual bool InformFirstDirection(int direction, int size, int availableOtherDir);

View File

@@ -1405,19 +1405,43 @@ public:
May be overridden if the control minimal size depends on the layout
direction.
This method is called after InformFirstDirection() and so its
implementation can rely on the values passed to that method.
This function is called when using sizers for the layout to request
minimum controls size once its size in the specified @a direction is
fixed by the layout algorithm and known to be equal to @a size.
For example, a multi-line wxStaticText returns the minimum size at
which it can be wrapped when the major layout direction is vertical.
It may be useful to override it if the control minimal size varies
depending on its size in some direction. For example, controls showing
multi-line text may return the size needed to show their text after
wrapping the contents to fit the given width when @a direction is
wxHORIZONTAL and @a size is the available width.
The default implementation of this method just calls GetMinSize().
The default implementation of this method returns wxDefaultSize
(to be precise, it may return GetEffectiveMinSize() if the deprecated
InformFirstDirection() is overridden and returns @true, but this
shouldn't be done in the new code).
@param direction
The direction in which the size is fixed, either ::wxHORIZONTAL or
::wxVERTICAL.
@param size
The size in the direction given by the @a direction parameter,
always valid, i.e. positive.
@param availableOtherDir
The size available in the other direction, may be -1 if the
available size is not known.
@return
The minimal size of the window when its size in the given
@a direction is fixed to @a size or ::wxDefaultSize if the minimum
size doesn't depend on the layout direction and is always the same.
@since 3.3.2
@see InformFirstDirection(), wxSizer::CalcMinUsingLayoutDirection()
@see wxSizer::CalcMinSizeFromKnownDirection()
*/
virtual wxSize GetMinSizeUsingLayoutDirection() const;
virtual wxSize
GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) const;
/**
Returns the maximum size of window's client area.
@@ -1608,14 +1632,10 @@ public:
virtual wxSize GetWindowBorderSize() const;
/**
wxSizer and friends use this to give a chance to a component to recalc
its min size once one of the final size components is known. Override
this function when that is useful (such as for wxStaticText which can
stretch over several lines). Parameter availableOtherDir
tells the item how much more space there is available in the opposite
direction (-1 if unknown).
Compatibility function called by GetMinSizeFromKnownDirection().
@see GetMinSizeUsingLayoutDirection(), wxSizer::CalcMinUsingLayoutDirection()
This function shouldn't be used in the new code, please override
GetMinSizeFromKnownDirection() instead.
*/
virtual bool
InformFirstDirection(int direction,

View File

@@ -538,15 +538,33 @@ bool wxSizerItem::InformFirstDirection(int direction, int size, int availableOth
// Pass the information along to the held object
if (IsSizer())
{
didUse = GetSizer()->InformFirstDirection(direction,size,availableOtherDir);
if (didUse)
m_minSize = GetSizer()->CalcMinUsingLayoutDirection();
const wxSize minSize = GetSizer()->CalcMinSizeFromKnownDirection
(
direction,
size,
availableOtherDir
);
if (minSize != wxDefaultSize)
{
m_minSize = minSize;
didUse = true;
}
}
else if (IsWindow())
{
didUse = GetWindow()->InformFirstDirection(direction,size,availableOtherDir);
if (didUse)
m_minSize = m_window->GetMinSizeUsingLayoutDirection();
const wxSize minSize = GetWindow()->GetMinSizeFromKnownDirection
(
direction,
size,
availableOtherDir
);
if (minSize != wxDefaultSize)
{
m_minSize = minSize;
didUse = true;
}
// This information is useful for items with wxSHAPED flag, since
// we can request an optimal min size for such an item. Even if
@@ -556,7 +574,7 @@ bool wxSizerItem::InformFirstDirection(int direction, int size, int availableOth
{
if ( m_ratio != 0 )
{
wxCHECK_MSG( m_proportion==0, false, wxT("Shaped item, non-zero proportion in wxSizerItem::InformFirstDirection()") );
wxCHECK_MSG( m_proportion==0, false, wxT("Shaped item, non-zero proportion in wxSizerItem::CalcMinSizeFromKnownDirection()") );
if ( direction == wxHORIZONTAL )
{
// Clip size so that we don't take too much
@@ -594,10 +612,7 @@ wxSize wxSizerItem::CalcMin()
{
// Since the size of the window may change during runtime, we
// should use the current minimal/best size.
m_minSize = m_window->GetMinSizeUsingLayoutDirection();
if ( !m_minSize.IsFullySpecified() )
m_minSize.SetDefaults(m_window->GetBestSize());
m_minSize = m_window->GetEffectiveMinSize();
}
return GetMinSizeWithBorder();
@@ -1232,6 +1247,21 @@ wxSize wxSizer::GetMinSize()
return ret;
}
wxSize
wxSizer::CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
// For compatibility, call InformFirstDirection().
if ( !InformFirstDirection(direction, size, availableOtherDir) )
return wxDefaultSize;
// Old code overriding InformFirstDirection() must have stored the values
// passed to it internally, so call its CalcMin() again to recalculate the
// minimal size using them.
return CalcMin();
}
void wxSizer::DoSetMinSize( int width, int height )
{
m_minSize.x = width;
@@ -2666,17 +2696,12 @@ wxSize wxBoxSizer::CalcMin()
return minSize;
}
bool
wxBoxSizer::InformFirstDirection(int direction, int size, int availableOtherDir)
{
// In principle, we could propagate the information about the size in the
// sizer major direction too, but this would require refactoring CalcMin()
// to determine the actual sizes all our items would have with the given
// size and we don't do this yet, so for now handle only the simpler case
// of informing all our items about their size in the orthogonal direction.
if ( direction == GetOrientation() )
return false;
wxSize
wxBoxSizer::CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
bool didUse = false;
for ( wxSizerItem* item: m_children )
@@ -2684,7 +2709,11 @@ wxBoxSizer::InformFirstDirection(int direction, int size, int availableOtherDir)
didUse |= item->InformFirstDirection(direction, size, availableOtherDir);
}
return didUse;
if ( !didUse )
return wxDefaultSize;
// Recalculate the min size now that items had a chance to adjust.
return CalcMin();
}
//---------------------------------------------------------------------------

View File

@@ -277,11 +277,25 @@ void wxStaticTextBase::Wrap(int width)
InvalidateBestSize();
}
wxSize wxStaticTextBase::GetMinSizeUsingLayoutDirection() const
wxSize
wxStaticTextBase::GetMinSizeFromKnownDirection(int direction,
int size,
int WXUNUSED(availableOtherDir))
{
if ( !m_currentWrap )
return GetMinSize();
if ( !HasFlag(wxST_WRAP) || direction != wxHORIZONTAL )
return wxDefaultSize;
// Wrap at the given width to compute the required size.
const int style = GetWindowStyleFlag();
if ( !(style & wxST_NO_AUTORESIZE) )
SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );
Wrap( size );
if ( !(style & wxST_NO_AUTORESIZE) )
SetWindowStyleFlag( style );
// Now compute the best size for the wrapped label.
int numLines = 0;
int maxLineWidth = 0;
for ( auto line : wxSplit(GetLabel(), '\n', '\0') )
@@ -296,34 +310,21 @@ wxSize wxStaticTextBase::GetMinSizeUsingLayoutDirection() const
return wxSize( maxLineWidth, numLines*GetCharHeight() );
}
bool wxStaticTextBase::InformFirstDirection(int direction, int size, int WXUNUSED(availableOtherDir))
void wxStaticTextBase::SetWindowStyleFlag(long style)
{
if ( !HasFlag(wxST_WRAP) || direction != wxHORIZONTAL )
// Check if wxST_WRAP is being cleared.
if ( HasFlag(wxST_WRAP) && !(style & wxST_WRAP) )
{
// If we had wrapped the control before, don't do it any longer.
// And unwrap the label in this case.
if ( m_currentWrap )
{
SetLabel(m_unwrappedLabel);
m_unwrappedLabel.clear();
m_currentWrap = 0;
}
return false;
}
// In the second pass, this control has been given "size" amount of
// space in the horizontal direction. Wrap there and report a new
// GetEffectiveMinSize() from then on.
const int style = GetWindowStyleFlag();
if ( !(style & wxST_NO_AUTORESIZE) )
SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );
Wrap( size );
if ( !(style & wxST_NO_AUTORESIZE) )
SetWindowStyleFlag( style );
return true;
wxControl::SetWindowStyleFlag(style);
}
void wxStaticTextBase::AutoResizeIfNecessary()

View File

@@ -854,6 +854,17 @@ wxWindowBase::InformFirstDirection(int direction,
availableOtherDir);
}
wxSize
wxWindowBase::GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
if ( !InformFirstDirection(direction, size, availableOtherDir) )
return wxDefaultSize;
return GetEffectiveMinSize();
}
wxSize wxWindowBase::GetEffectiveMinSize() const
{
// merge the best size with the min size, giving priority to the min size

View File

@@ -127,23 +127,6 @@ wxSizer *wxWrapSizer::GetRowSizer(size_t n)
return sizer;
}
bool wxWrapSizer::InformFirstDirection(int direction,
int size,
int availableOtherDir)
{
if ( !direction )
return false;
// Store the values for later use
m_availSize = size;
m_availableOtherDir = availableOtherDir +
(direction == wxHORIZONTAL ? m_calculatedMinSize.y
: m_calculatedMinSize.x);
m_dirInform = direction;
return true;
}
void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast)
{
if ( !itemLast || !(m_flags & wxEXTEND_LAST_ON_EACH_LINE) )
@@ -159,13 +142,31 @@ void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast)
item->SetUserData(new wxPropChanger(*this, *itemLast));
}
wxSize wxWrapSizer::CalcMinUsingLayoutDirection()
wxSize
wxWrapSizer::CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
if ( m_children.empty() )
return wxSize();
return wxDefaultSize;
// We're called after InformFirstDirection() to find a min size that
// uses one dimension maximally and the other direction minimally.
// Store the parameters for use in CalcMin() later.
m_availSize = size;
if ( availableOtherDir == -1 )
{
m_availableOtherDir = -1;
}
else
{
m_availableOtherDir = availableOtherDir +
(direction == wxHORIZONTAL ? m_calculatedMinSize.y
: m_calculatedMinSize.x);
}
m_dirInform = direction;
// We're called to find a min size that uses one dimension maximally and
// the other direction minimally.
//
// There are two different algorithms for doing it, depending on whether
// the first reported size component is the opposite as our own orientation

View File

@@ -33,6 +33,17 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
wxSize sizeMinExpected;
auto const checkSizeIsExpected = [&](const char* desc)
{
INFO(desc);
CHECK( sizer->CalcMinSizeFromKnownDirection
(
wxHORIZONTAL,
win->GetClientSize().x,
win->GetClientSize().y
) == sizeMinExpected );
};
// With a single child the min size must be the same as child size.
const wxSize sizeChild1 = wxSize(80, 60);
sizeMinExpected = sizeChild1;
@@ -43,7 +54,7 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
sizer->Add(child1);
win->Layout();
CHECK( sizeMinExpected == sizer->CalcMinUsingLayoutDirection() );
checkSizeIsExpected("Single child");
// If both children can fit in the same row, the minimal size of the sizer
// is determined by the sum of their minimal horizontal dimensions and
@@ -58,7 +69,7 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
sizer->Add(child2);
win->Layout();
CHECK( sizeMinExpected == sizer->CalcMinUsingLayoutDirection() );
checkSizeIsExpected("Two children in one row");
// Three children will take at least two rows so the minimal size in
// vertical direction must increase.
@@ -71,7 +82,7 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
sizer->Add(child3);
win->Layout();
CHECK( sizeMinExpected == sizer->CalcMinUsingLayoutDirection() );
checkSizeIsExpected("Three children in two rows");
}
TEST_CASE("wxWrapSizer::CalcMinFromMinor", "[wxWrapSizer]")