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,
int min_width_medium, int min_width_large);
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 *GetHoveredItem() const;
@@ -183,7 +187,9 @@ protected:
void CommonInit(long style);
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,
const wxBitmap& bitmap_large,
const wxBitmap& bitmap_large_disabled,

View File

@@ -568,6 +568,38 @@ public:
*/
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.
The active button is the one being clicked.

View File

@@ -75,10 +75,16 @@ public:
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;
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;
}
wxASSERT(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported);
return wxRIBBON_BUTTONBAR_BUTTON_SMALL;
}
@@ -91,14 +97,16 @@ public:
switch(*size)
{
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;
break;
}
wxFALLTHROUGH;
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;
break;
@@ -120,6 +128,8 @@ public:
wxBitmap bitmap_small_disabled;
wxCoord text_min_width[3];
wxRibbonButtonBarButtonSizeInfo sizes[3];
wxRibbonButtonBarButtonState min_size_class;
wxRibbonButtonBarButtonState max_size_class;
wxClientDataContainer client_data;
int id;
wxRibbonButtonKind kind;
@@ -332,6 +342,8 @@ wxRibbonButtonBarButtonBase* wxRibbonButtonBar::InsertButton(
base->text_min_width[0] = 0;
base->text_min_width[1] = 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);
FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc);
@@ -635,6 +647,36 @@ void wxRibbonButtonBar::SetButtonTextMinWidth(
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)
{
if(art == m_art)
@@ -905,8 +947,27 @@ void wxRibbonButtonBar::MakeLayouts()
}
size_t btn_count = m_buttons.Count();
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;
wxPoint cursor(0, 0);
layout->overall_size.SetHeight(0);
@@ -917,54 +978,111 @@ void wxRibbonButtonBar::MakeLayouts()
instance.position = cursor;
instance.size = button->GetLargestSize();
wxSize& size = button->sizes[instance.size].size;
if(instance.size < wxRIBBON_BUTTONBAR_BUTTON_LARGE)
{
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->overall_size.SetHeight(wxMax(layout->overall_size.GetHeight(),
size.GetHeight()));
}
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);
}
if(btn_count >= 2)
{
// Collapse the rightmost buttons and stack them vertically
size_t iLast = btn_count - 1;
while(TryCollapseLayout(m_layouts.Last(), iLast, &iLast) && iLast > 0)
// if they are not already small. If rightmost buttons can't
// 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,
size_t first_btn, size_t* last_button)
void wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
size_t first_btn, size_t* last_button,
wxRibbonButtonBarButtonState target_size)
{
size_t btn_count = m_buttons.Count();
size_t btn_i;
int used_height = 0;
int used_width = 0;
int original_column_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 */)
{
--btn_i;
wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i);
wxRibbonButtonBarButtonState large_size_class = button->GetLargestSize();
wxSize large_size = button->sizes[large_size_class].size;
int t_available_height = wxMax(available_height,
large_size.GetHeight());
int t_available_width = available_width + large_size.GetWidth();
wxRibbonButtonBarButtonState small_size_class = large_size_class;
if(!button->GetSmallerSize(&small_size_class))
int t_available_width = available_width;
original_column_width = wxMax(original_column_width,
large_size.GetWidth());
// 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;
int t_used_height = used_height + small_size.GetHeight();
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;
break;
@@ -974,13 +1092,13 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
used_height = t_used_height;
used_width = t_used_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)
{
return false;
return;
}
if(last_button != NULL)
{
@@ -990,27 +1108,23 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout;
WX_APPEND_ARRAY(layout->buttons, original->buttons);
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)
{
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;
cursor.y += instance.base->sizes[instance.size].size.GetHeight();
}
int x_adjust = available_width - used_width;
// Adjust x coords of buttons right of shrinked column
for(; btn_i < btn_count; ++btn_i)
{
wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i);
@@ -1025,16 +1139,19 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original,
{
delete layout;
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 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);
return true;
}
void wxRibbonButtonBar::MakeBitmaps(wxRibbonButtonBarButtonBase* base,