Add multi-variant support to the app

This commit is contained in:
SoftFever
2026-09-28 19:08:35 +08:00
parent 2ccabb5695
commit 71bb400e46
27 changed files with 846 additions and 195 deletions
+110 -11
View File
@@ -4,6 +4,8 @@
#include "StaticBox.hpp"
#include "../wxExtensions.hpp"
#include <wx/settings.h>
#include "../GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "../Utils/MacDarkMode.hpp"
@@ -620,19 +622,34 @@ MultiSwitchButton::MultiSwitchButton(wxWindow *parent, wxWindowID id, const wxPo
std::make_pair(0x6B6B6B, (int) StateColor::NotChecked),
std::make_pair(0xFFFFFE, (int) StateColor::Normal)))
, m_button_radius(10.0)
, m_button_padding(10, 6)
, m_button_padding(FromDIP(wxSize(11, 3)))
{
SetCornerRadius(m_button_radius);
SetBorderWidth(0);
sizer = new wxBoxSizer(wxHORIZONTAL);
auto *hsizer = new wxBoxSizer(wxVERTICAL);
hsizer->Add(sizer, 1, wxEXPAND);
SetSizer(hsizer);
SetMinSize(wxSize(-1, 20));
// Orca: a switch can hold more buttons than the layout has room for (a toolchanger lists one per
// tool), so they live in a scrolled area: the caller caps the switch at its natural width and
// this scrolls horizontally instead of clipping the last buttons.
m_scroll = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxBORDER_NONE);
m_scroll->SetBackgroundColour(GetBackgroundColour());
// The buttons are a single row, so only the horizontal bar may ever appear: a vertical one would
// eat into the row's height.
m_scroll->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_NEVER);
m_scroll->EnableScrolling(true, false);
m_scroll->SetScrollRate(FromDIP(10), 0);
sizer = new wxBoxSizer(wxHORIZONTAL);
m_scroll->SetSizer(sizer);
auto *hsizer = new wxBoxSizer(wxVERTICAL);
hsizer->Add(m_scroll, 1, wxEXPAND);
SetSizer(hsizer);
SetMinSize(wxSize(-1, options_height()));
Bind(wxEVT_SIZE, &MultiSwitchButton::on_size, this);
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MultiSwitchButton::button_clicked, this);
SetFont(Label::Body_12);
// The tags name a tool and its volume type only, so they stay compact.
SetFont(Label::Body_10);
}
MultiSwitchButton::~MultiSwitchButton()
@@ -640,10 +657,88 @@ MultiSwitchButton::~MultiSwitchButton()
DeleteAllOptions();
}
int MultiSwitchButton::options_height() const
{
// With no button to measure yet, keep the placeholder height the switch starts with.
return btns.empty() ? FromDIP(20) : btns.front()->GetMinSize().y;
}
void MultiSwitchButton::update_scroll_range()
{
// The scrollbar range is measured against the virtual size, so it has to follow the buttons
// whenever their labels or count change.
m_scroll->InvalidateBestSize();
m_scroll->FitInside();
// A scrolled window reports its min size plus a scrollbar as its best size, never the width of
// the buttons it holds, so the layout has to be given that width explicitly. It is also the
// widest this switch wants to be: a row with less room squeezes it below this and it scrolls.
const wxSize content = sizer->CalcMin();
SetMinSize(wxSize(content.x, options_height() + scrollbar_height(content.x)));
SetMaxSize(m_fit_to_options ? wxSize(content.x, -1) : wxDefaultSize);
InvalidateBestSize();
}
int MultiSwitchButton::scrollbar_height(int options_width) const
{
// The bar is drawn inside the switch, so while the buttons need more width than the row gave us
// the switch has to be taller by the bar's height, or the bar sits on top of the buttons.
const int width = GetClientSize().x;
if (width <= 0 || options_width <= width)
return 0;
const int bar = wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, this);
return bar > 0 ? bar : 0;
}
void MultiSwitchButton::on_size(wxSizeEvent &evt)
{
evt.Skip();
// The row resized, so the buttons may now overflow it (or no longer fit in it) and the room the
// bar needs changed with that. Width does not depend on height, so this settles in one pass.
const int height = options_height() + scrollbar_height(sizer->CalcMin().x);
if (GetMinSize().y != height) {
SetMinSize(wxSize(GetMinSize().x, height));
// The switch sits in a row of this tab, and the tab in a panel that shares its height
// with the page view, so both have to lay out again for the taller row to get its room.
if (wxWindow *tab = GetParent()) {
tab->Layout();
if (wxWindow *panel = tab->GetParent())
panel->Layout();
}
}
}
void MultiSwitchButton::scroll_option_into_view(Button *btn)
{
const int width = m_scroll->GetClientSize().x;
const int view_x = m_scroll->GetViewStartPixels().x;
if (width <= 0) // not laid out yet: there is no view to scroll
return;
const wxRect rect = btn->GetRect();
const int right = rect.GetRight() + 1;
int target = view_x;
if (rect.x < view_x)
target = rect.x;
else if (right > view_x + width)
target = right - width;
if (target == view_x)
return;
// Scroll() counts scroll units; round up so the whole button ends up inside the view rather
// than a few pixels short of it.
int step = 1;
m_scroll->GetScrollPixelsPerUnit(&step, nullptr);
m_scroll->Scroll((target + step - 1) / step, -1);
}
int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
{
Button *btn = new Button();
btn->Create(this, option, "", wxBORDER_NONE);
btn->Create(m_scroll, option, "", wxBORDER_NONE);
btn->SetFont(GetFont());
btn->SetBackgroundColor(m_bg_color);
btn->SetTextColor(m_text_color);
@@ -654,9 +749,6 @@ int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
btns.push_back(btn);
sizer->Add(btn, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
wxSize text_size = btn->GetTextExtent(option);
btn->SetMinSize(wxSize(text_size.x + m_button_padding.x * 2 + 6, -1));
return int(btns.size()) - 1;
}
@@ -666,6 +758,7 @@ void MultiSwitchButton::SetOptions(const std::vector<wxString> &options)
for (const auto &option : options)
AppendOption(option);
update_scroll_range();
Layout();
Refresh();
}
@@ -700,6 +793,8 @@ void MultiSwitchButton::SetSelection(int index)
sel = index;
update_button_styles();
send_selection_event();
// The selected button may be scrolled out of sight, e.g. when the tab restores the active tool.
scroll_option_into_view(btns[sel]);
Refresh();
}
@@ -718,6 +813,7 @@ void MultiSwitchButton::SetOptionText(unsigned int index, const wxString &text)
if (index >= btns.size())
return;
btns[index]->SetLabel(text);
update_scroll_range();
}
void *MultiSwitchButton::GetOptionData(unsigned int index) const
@@ -769,6 +865,7 @@ void MultiSwitchButton::SetButtonPadding(const wxSize &padding)
m_button_padding = padding;
for (auto *btn : btns)
btn->SetPaddingSize(padding);
update_scroll_range();
Layout();
Refresh();
}
@@ -777,6 +874,8 @@ void MultiSwitchButton::Rescale()
{
for (auto *btn : btns)
btn->Rescale();
// Rescaling can change how the labels measure, and the scrollbar range follows the buttons.
update_scroll_range();
}
void MultiSwitchButton::button_clicked(wxCommandEvent &event)