wxRibbonButtonBarButton: Implemented manual button size class control

Added new functions SetButtonMinSizeClass() and
SetButtonMaxSizeClass() that set a button's minimum
and maximum allowed size class during ribbon layout collapsing.
Size classes are values from wxRibbonButtonBarButtonState enum.

wxRibbonButtonBar::MakeLayouts() and
wxRibbonButtonBar::TryCollapseLayout() are modified to support
this new behaviour.

The modified wxRibbonButtonBar provate layout routines are
aware of wxRIBBON_BUTTONBAR_BUTTON_SMALL which is not implemented
yet in MSW wxRibbonMSWArtProvider::DrawButtonBarButtonForeground.
This commit is contained in:
Max Maisel
2018-03-15 16:29:35 +01:00
parent 7aed0e547b
commit 2cbcf939df
3 changed files with 197 additions and 42 deletions

View File

@@ -150,6 +150,10 @@ public:
virtual void SetButtonTextMinWidth(int button_id, virtual void SetButtonTextMinWidth(int button_id,
int min_width_medium, int min_width_large); int min_width_medium, int min_width_large);
virtual void SetButtonTextMinWidth(int button_id, const wxString& label); virtual void SetButtonTextMinWidth(int button_id, const wxString& label);
virtual void SetButtonMinSizeClass(int button_id,
wxRibbonButtonBarButtonState min_size_class);
virtual void SetButtonMaxSizeClass(int button_id,
wxRibbonButtonBarButtonState max_size_class);
virtual wxRibbonButtonBarButtonBase *GetActiveItem() const; virtual wxRibbonButtonBarButtonBase *GetActiveItem() const;
virtual wxRibbonButtonBarButtonBase *GetHoveredItem() const; virtual wxRibbonButtonBarButtonBase *GetHoveredItem() const;
@@ -183,7 +187,9 @@ protected:
void CommonInit(long style); void CommonInit(long style);
void MakeLayouts(); void MakeLayouts();
bool TryCollapseLayout(wxRibbonButtonBarLayout* original, size_t first_btn, size_t* last_button); void TryCollapseLayout(wxRibbonButtonBarLayout* original,
size_t first_btn, size_t* last_button,
wxRibbonButtonBarButtonState target_size);
void MakeBitmaps(wxRibbonButtonBarButtonBase* base, void MakeBitmaps(wxRibbonButtonBarButtonBase* base,
const wxBitmap& bitmap_large, const wxBitmap& bitmap_large,
const wxBitmap& bitmap_large_disabled, const wxBitmap& bitmap_large_disabled,

View File

@@ -568,6 +568,38 @@ public:
*/ */
virtual void SetButtonTextMinWidth(int button_id, const wxString& label); virtual void SetButtonTextMinWidth(int button_id, const wxString& label);
/**
Sets the minimum size class of a ribbon button.
You have to call Realize() after calling this function to
apply the given minimum size.
@param button_id
ID of the button to manipulate.
@param min_size_class
The minimum size-class of the button. Buttons on a button bar
can have three distinct sizes: wxRIBBON_BUTTONBAR_BUTTON_SMALL,
wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, and wxRIBBON_BUTTONBAR_BUTTON_LARGE.
*/
virtual void SetButtonMinSizeClass(int button_id,
wxRibbonButtonBarButtonState min_size_class);
/**
Sets the maximum size class of a ribbon button.
You have to call Realize() after calling this function to
apply the given maximum size.
@param button_id
ID of the button to manipulate.
@param max_size_class
The maximum size-class of the button. Buttons on a button bar
can have three distinct sizes: wxRIBBON_BUTTONBAR_BUTTON_SMALL,
wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, and wxRIBBON_BUTTONBAR_BUTTON_LARGE.
*/
virtual void SetButtonMaxSizeClass(int button_id,
wxRibbonButtonBarButtonState max_size_class);
/** /**
Returns the active item of the button bar or NULL if there is none. Returns the active item of the button bar or NULL if there is none.
The active button is the one being clicked. The active button is the one being clicked.

View File

@@ -75,10 +75,16 @@ public:
wxRibbonButtonBarButtonState GetLargestSize() wxRibbonButtonBarButtonState GetLargestSize()
{ {
if(sizes[wxRIBBON_BUTTONBAR_BUTTON_LARGE].is_supported) if(sizes[wxRIBBON_BUTTONBAR_BUTTON_LARGE].is_supported
&& max_size_class >= wxRIBBON_BUTTONBAR_BUTTON_LARGE)
{
return wxRIBBON_BUTTONBAR_BUTTON_LARGE; return wxRIBBON_BUTTONBAR_BUTTON_LARGE;
if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported) }
if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported
&& max_size_class >= wxRIBBON_BUTTONBAR_BUTTON_MEDIUM)
{
return wxRIBBON_BUTTONBAR_BUTTON_MEDIUM; return wxRIBBON_BUTTONBAR_BUTTON_MEDIUM;
}
wxASSERT(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported); wxASSERT(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported);
return wxRIBBON_BUTTONBAR_BUTTON_SMALL; return wxRIBBON_BUTTONBAR_BUTTON_SMALL;
} }
@@ -91,14 +97,16 @@ public:
switch(*size) switch(*size)
{ {
case wxRIBBON_BUTTONBAR_BUTTON_LARGE: case wxRIBBON_BUTTONBAR_BUTTON_LARGE:
if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported) if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported
&& min_size_class <= wxRIBBON_BUTTONBAR_BUTTON_MEDIUM)
{ {
*size = wxRIBBON_BUTTONBAR_BUTTON_MEDIUM; *size = wxRIBBON_BUTTONBAR_BUTTON_MEDIUM;
break; break;
} }
wxFALLTHROUGH; wxFALLTHROUGH;
case wxRIBBON_BUTTONBAR_BUTTON_MEDIUM: case wxRIBBON_BUTTONBAR_BUTTON_MEDIUM:
if(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported) if(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported
&& min_size_class <= wxRIBBON_BUTTONBAR_BUTTON_SMALL)
{ {
*size = wxRIBBON_BUTTONBAR_BUTTON_SMALL; *size = wxRIBBON_BUTTONBAR_BUTTON_SMALL;
break; break;
@@ -120,6 +128,8 @@ public:
wxBitmap bitmap_small_disabled; wxBitmap bitmap_small_disabled;
wxCoord text_min_width[3]; wxCoord text_min_width[3];
wxRibbonButtonBarButtonSizeInfo sizes[3]; wxRibbonButtonBarButtonSizeInfo sizes[3];
wxRibbonButtonBarButtonState min_size_class;
wxRibbonButtonBarButtonState max_size_class;
wxClientDataContainer client_data; wxClientDataContainer client_data;
int id; int id;
wxRibbonButtonKind kind; wxRibbonButtonKind kind;
@@ -332,6 +342,8 @@ wxRibbonButtonBarButtonBase* wxRibbonButtonBar::InsertButton(
base->text_min_width[0] = 0; base->text_min_width[0] = 0;
base->text_min_width[1] = 0; base->text_min_width[1] = 0;
base->text_min_width[2] = 0; base->text_min_width[2] = 0;
base->min_size_class = wxRIBBON_BUTTONBAR_BUTTON_SMALL;
base->max_size_class = wxRIBBON_BUTTONBAR_BUTTON_LARGE;
wxClientDC temp_dc(this); wxClientDC temp_dc(this);
FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc); FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc);
@@ -635,6 +647,36 @@ void wxRibbonButtonBar::SetButtonTextMinWidth(
m_layouts_valid = false; m_layouts_valid = false;
} }
void wxRibbonButtonBar::SetButtonMinSizeClass(int button_id,
wxRibbonButtonBarButtonState min_size_class)
{
wxRibbonButtonBarButtonBase* base = GetItemById(button_id);
if(base == NULL)
return;
if(base->max_size_class < min_size_class)
{
wxFAIL_MSG("Button minimum size is larger than maximum size");
return;
}
base->min_size_class = min_size_class;
m_layouts_valid = false;
}
void wxRibbonButtonBar::SetButtonMaxSizeClass(int button_id,
wxRibbonButtonBarButtonState max_size_class)
{
wxRibbonButtonBarButtonBase* base = GetItemById(button_id);
if(base == NULL)
return;
if(base->min_size_class > max_size_class)
{
wxFAIL_MSG("Button maximum size is smaller than minimum size");
return;
}
base->max_size_class = max_size_class;
m_layouts_valid = false;
}
void wxRibbonButtonBar::SetArtProvider(wxRibbonArtProvider* art) void wxRibbonButtonBar::SetArtProvider(wxRibbonArtProvider* art)
{ {
if(art == m_art) if(art == m_art)
@@ -905,8 +947,27 @@ void wxRibbonButtonBar::MakeLayouts()
} }
size_t btn_count = m_buttons.Count(); size_t btn_count = m_buttons.Count();
size_t btn_i; size_t btn_i;
// Determine available height:
// 1 large button or, if not found, 3 medium or small buttons
int available_height = 0;
bool large_button_found = false;
for(btn_i = 0; btn_i < btn_count; ++btn_i)
{ {
// Best layout : all buttons large, stacking horizontally wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i);
wxRibbonButtonBarButtonState size_class = button->GetLargestSize();
available_height = wxMax(available_height,
button->sizes[size_class].size.GetHeight());
if(size_class == wxRIBBON_BUTTONBAR_BUTTON_LARGE)
large_button_found = true;
}
if(!large_button_found)
available_height *= 3;
int stacked_width = 0;
{
// Best layout : all buttons large, stacking horizontally,
// small buttons small, stacked vertically
wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout; wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout;
wxPoint cursor(0, 0); wxPoint cursor(0, 0);
layout->overall_size.SetHeight(0); layout->overall_size.SetHeight(0);
@@ -917,54 +978,111 @@ void wxRibbonButtonBar::MakeLayouts()
instance.position = cursor; instance.position = cursor;
instance.size = button->GetLargestSize(); instance.size = button->GetLargestSize();
wxSize& size = button->sizes[instance.size].size; wxSize& size = button->sizes[instance.size].size;
cursor.x += size.GetWidth();
layout->overall_size.SetHeight(wxMax(layout->overall_size.GetHeight(), if(instance.size < wxRIBBON_BUTTONBAR_BUTTON_LARGE)
size.GetHeight())); {
stacked_width = wxMax(stacked_width, size.GetWidth());
if(cursor.y + size.GetHeight() >= available_height)
{
cursor.y = 0;
cursor.x += stacked_width;
stacked_width = 0;
}
else
{
cursor.y += size.GetHeight();
}
}
else
{
if(cursor.y != 0)
{
cursor.y = 0;
cursor.x += stacked_width;
stacked_width = 0;
instance.position = cursor;
}
cursor.x += size.GetWidth();
}
layout->buttons.Add(instance); layout->buttons.Add(instance);
} }
layout->overall_size.SetWidth(cursor.x); layout->overall_size.SetHeight(available_height);
layout->overall_size.SetWidth(cursor.x + stacked_width);
m_layouts.Add(layout); m_layouts.Add(layout);
} }
if(btn_count >= 2) if(btn_count >= 2)
{ {
// Collapse the rightmost buttons and stack them vertically // Collapse the rightmost buttons and stack them vertically
size_t iLast = btn_count - 1; // if they are not already small. If rightmost buttons can't
while(TryCollapseLayout(m_layouts.Last(), iLast, &iLast) && iLast > 0) // be collapsed because "min_size_class" is set, try it again
// starting from second rightmost button and so on.
size_t iLast = btn_count;
while(iLast-- > 0)
{ {
--iLast; TryCollapseLayout(m_layouts.Last(), iLast, &iLast,
wxRIBBON_BUTTONBAR_BUTTON_MEDIUM);
} }
// TODO: small buttons are not implemented yet in
// art_msw.cpp:2581 and will be invisible
/*iLast = btn_count;
while(iLast-- > 0)
{
TryCollapseLayout(m_layouts.Last(), iLast, &iLast,
wxRIBBON_BUTTONBAR_BUTTON_SMALL);
}*/
} }
} }
bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original, void wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
size_t first_btn, size_t* last_button) size_t first_btn, size_t* last_button,
wxRibbonButtonBarButtonState target_size)
{ {
size_t btn_count = m_buttons.Count(); size_t btn_count = m_buttons.Count();
size_t btn_i; size_t btn_i;
int used_height = 0; int used_height = 0;
int used_width = 0; int used_width = 0;
int original_column_width = 0;
int available_width = 0; int available_width = 0;
int available_height = 0; int available_height = original->overall_size.GetHeight();
// Search for button range from right which should be
// collapsed into a column of small buttons.
for(btn_i = first_btn + 1; btn_i > 0; /* decrement is inside loop */) for(btn_i = first_btn + 1; btn_i > 0; /* decrement is inside loop */)
{ {
--btn_i; --btn_i;
wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i); wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i);
wxRibbonButtonBarButtonState large_size_class = button->GetLargestSize(); wxRibbonButtonBarButtonState large_size_class = button->GetLargestSize();
wxSize large_size = button->sizes[large_size_class].size; wxSize large_size = button->sizes[large_size_class].size;
int t_available_height = wxMax(available_height, int t_available_width = available_width;
large_size.GetHeight());
int t_available_width = available_width + large_size.GetWidth(); original_column_width = wxMax(original_column_width,
wxRibbonButtonBarButtonState small_size_class = large_size_class; large_size.GetWidth());
if(!button->GetSmallerSize(&small_size_class))
// Top button in column: add column width to available width
if(original->buttons.Item(btn_i).position.y == 0)
{ {
return false; t_available_width += original_column_width;
original_column_width = 0;
}
wxRibbonButtonBarButtonState small_size_class = large_size_class;
if(large_size_class > target_size)
{
if(!button->GetSmallerSize(&small_size_class,
small_size_class - target_size))
{
// Large button that cannot shrink: stop search
++btn_i;
break;
}
} }
wxSize small_size = button->sizes[small_size_class].size; wxSize small_size = button->sizes[small_size_class].size;
int t_used_height = used_height + small_size.GetHeight(); int t_used_height = used_height + small_size.GetHeight();
int t_used_width = wxMax(used_width, small_size.GetWidth()); int t_used_width = wxMax(used_width, small_size.GetWidth());
if(t_used_height > t_available_height) // Height is full: stop search
if(t_used_height > available_height)
{ {
++btn_i; ++btn_i;
break; break;
@@ -974,13 +1092,13 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
used_height = t_used_height; used_height = t_used_height;
used_width = t_used_width; used_width = t_used_width;
available_width = t_available_width; available_width = t_available_width;
available_height = t_available_height;
} }
} }
// Layout got wider than before or no suitable button found: abort
if(btn_i >= first_btn || used_width >= available_width) if(btn_i >= first_btn || used_width >= available_width)
{ {
return false; return;
} }
if(last_button != NULL) if(last_button != NULL)
{ {
@@ -990,27 +1108,23 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout; wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout;
WX_APPEND_ARRAY(layout->buttons, original->buttons); WX_APPEND_ARRAY(layout->buttons, original->buttons);
wxPoint cursor(layout->buttons.Item(btn_i).position); wxPoint cursor(layout->buttons.Item(btn_i).position);
bool preserve_height = false;
if(btn_i == 0)
{
// If height isn't preserved (i.e. it is reduced), then the minimum
// size for the button bar will decrease, preventing the original
// layout from being used (in some cases).
// It may be a good idea to always preserve the height, but for now
// it is only done when the first button is involved in a collapse.
preserve_height = true;
}
cursor.y = 0;
for(; btn_i <= first_btn; ++btn_i) for(; btn_i <= first_btn; ++btn_i)
{ {
wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i); wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i);
instance.base->GetSmallerSize(&instance.size); if(instance.size > target_size)
{
instance.base->GetSmallerSize(&instance.size,
instance.size - target_size);
}
instance.position = cursor; instance.position = cursor;
cursor.y += instance.base->sizes[instance.size].size.GetHeight(); cursor.y += instance.base->sizes[instance.size].size.GetHeight();
} }
int x_adjust = available_width - used_width; int x_adjust = available_width - used_width;
// Adjust x coords of buttons right of shrinked column
for(; btn_i < btn_count; ++btn_i) for(; btn_i < btn_count; ++btn_i)
{ {
wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i); wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i);
@@ -1025,16 +1139,19 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
{ {
delete layout; delete layout;
wxFAIL_MSG("Layout collapse resulted in increased size"); wxFAIL_MSG("Layout collapse resulted in increased size");
return false; return;
} }
if(preserve_height) // If height isn't preserved (i.e. it is reduced), then the minimum
{ // size for the button bar will decrease, preventing the original
layout->overall_size.SetHeight(original->overall_size.GetHeight()); // layout from being used (in some cases).
} // If neither "min_size_class" nor "max_size_class" is set, this is
// only required when the first button is involved in a collapse but
// if small, medium and large buttons as well as min/max size classes
// are involved this is always a good idea.
layout->overall_size.SetHeight(original->overall_size.GetHeight());
m_layouts.Add(layout); m_layouts.Add(layout);
return true;
} }
void wxRibbonButtonBar::MakeBitmaps(wxRibbonButtonBarButtonBase* base, void wxRibbonButtonBar::MakeBitmaps(wxRibbonButtonBarButtonBase* base,