From 2cbcf939df38a65d294b2a3bd775f97ec7d8e42c Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Thu, 15 Mar 2018 16:29:35 +0100 Subject: [PATCH] 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. --- include/wx/ribbon/buttonbar.h | 8 +- interface/wx/ribbon/buttonbar.h | 32 +++++ src/ribbon/buttonbar.cpp | 199 +++++++++++++++++++++++++------- 3 files changed, 197 insertions(+), 42 deletions(-) diff --git a/include/wx/ribbon/buttonbar.h b/include/wx/ribbon/buttonbar.h index 8bc27d74ef..17ba124e57 100644 --- a/include/wx/ribbon/buttonbar.h +++ b/include/wx/ribbon/buttonbar.h @@ -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, diff --git a/interface/wx/ribbon/buttonbar.h b/interface/wx/ribbon/buttonbar.h index a0e95396af..f95455943b 100644 --- a/interface/wx/ribbon/buttonbar.h +++ b/interface/wx/ribbon/buttonbar.h @@ -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. diff --git a/src/ribbon/buttonbar.cpp b/src/ribbon/buttonbar.cpp index 5b18727d07..5be028a710 100644 --- a/src/ribbon/buttonbar.cpp +++ b/src/ribbon/buttonbar.cpp @@ -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; - cursor.x += size.GetWidth(); - layout->overall_size.SetHeight(wxMax(layout->overall_size.GetHeight(), - size.GetHeight())); + + 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->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) - { - layout->overall_size.SetHeight(original->overall_size.GetHeight()); - } + // 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,