feat: UI for dropdown to select which plugin to show past max visible pages

This commit is contained in:
Ian Chua
2026-08-11 17:50:49 +08:00
parent 54fe28ab08
commit 7be7f07551
4 changed files with 87 additions and 132 deletions

View File

@@ -272,6 +272,24 @@ wxString ButtonsListCtrl::GetPageText(size_t n) const
return btn->GetLabel();
}
// ORCA
void ButtonsListCtrl::SetOverflowButton(wxWindow* button)
{
if (m_overflow_button == button)
return;
if (m_overflow_button != nullptr)
m_sizer->Detach(m_overflow_button);
m_overflow_button = button;
if (m_overflow_button != nullptr)
// Right after the tab buttons (index 0), ahead of any stretch spacer / side_tools.
m_sizer->Insert(1, m_overflow_button, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin);
m_sizer->Layout();
}
//#endif // _WIN32
void Notebook::Init()

View File

@@ -36,6 +36,9 @@ public:
void SetCompact(size_t n, bool compact); // ORCA
wxString GetPageText(size_t n) const;
wxFlexGridSizer* GetBtnsSizer(){return m_buttons_sizer;}; // ORCA
// ORCA: a companion widget shown right after the tab buttons (before any side_tools), e.g.
// an overflow indicator. Pass nullptr to remove it; ownership stays with the caller.
void SetOverflowButton(wxWindow* button);
private:
wxFlexGridSizer* m_buttons_sizer;
@@ -47,6 +50,7 @@ private:
int m_line_margin;
std::vector<wxString> m_pageLabels; // ORCA
wxImageList* m_imageList{nullptr};
wxWindow* m_overflow_button{nullptr}; // ORCA
};
class Notebook : public wxBookCtrlBase
@@ -324,6 +328,7 @@ public:
}
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(m_bookctrl); }
void SetOverflowButton(wxWindow* button) { GetBtnsListCtrl()->SetOverflowButton(button); }
int FindPageByName(const wxString& id) const
{

View File

@@ -4,6 +4,7 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Notebook.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/WebView.hpp"
#include "slic3r/GUI/Widgets/WebViewHostDialog.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
@@ -19,7 +20,7 @@
#include <stdexcept>
#include <wx/bookctrl.h>
#include <wx/choice.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <utility>
@@ -198,110 +199,6 @@ void PluginPage::push_message(const std::string& message)
WebView::RunScript(m_browser, script);
}
class PluginPagesOverflowPanel : public wxPanel
{
public:
explicit PluginPagesOverflowPanel(Notebook* parent)
: wxPanel(parent, wxID_ANY)
, m_notebook(parent)
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_choice = new wxChoice(this, wxID_ANY);
m_choice->Bind(wxEVT_CHOICE, &PluginPagesOverflowPanel::on_choice, this);
sizer->Add(m_choice, wxSizerFlags().Expand().Border(wxALL, FromDIP(4)));
m_content_sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_content_sizer, wxSizerFlags().Expand().Proportion(1));
SetSizer(sizer);
}
void add_entry(const PluginCapabilityId& id, PluginPage* page, const wxString& title)
{
page->Reparent(this);
page->Hide();
m_entries.push_back({id, page, title});
m_choice->Append(title);
if (m_entries.size() == 1)
select_index(0);
}
void select_entry(const PluginCapabilityId& id)
{
for (size_t i = 0; i < m_entries.size(); ++i) {
if (m_entries[i].id == id) {
select_index(i);
return;
}
}
}
void clear()
{
if (m_shown_index != wxNOT_FOUND)
m_entries[static_cast<size_t>(m_shown_index)].page->Hide();
m_content_sizer->Clear(false);
for (const Entry& entry : m_entries)
entry.page->Reparent(m_notebook);
m_entries.clear();
m_choice->Clear();
m_shown_index = wxNOT_FOUND;
}
wxString current_title() const { return m_shown_index == wxNOT_FOUND ? wxString() : m_entries[static_cast<size_t>(m_shown_index)].title; }
int current_image_id() const
{
return m_shown_index == wxNOT_FOUND ? wxBookCtrlBase::NO_IMAGE : m_entries[static_cast<size_t>(m_shown_index)].page->get_icon_image_id();
}
private:
struct Entry
{
PluginCapabilityId id;
PluginPage* page;
wxString title;
};
void on_choice(wxCommandEvent&)
{
const int selection = m_choice->GetSelection();
if (selection != wxNOT_FOUND)
select_index(static_cast<size_t>(selection));
}
void select_index(size_t index)
{
if (index >= m_entries.size())
return;
if (m_shown_index != wxNOT_FOUND)
m_entries[static_cast<size_t>(m_shown_index)].page->Hide();
m_content_sizer->Clear(false);
m_content_sizer->Add(m_entries[index].page, wxSizerFlags().Expand().Proportion(1));
m_entries[index].page->Show();
Layout();
m_shown_index = static_cast<int>(index);
m_choice->SetSelection(static_cast<int>(index));
const int tab_index = m_notebook->FindPage(this);
if (tab_index != wxNOT_FOUND) {
m_notebook->SetPageText(static_cast<size_t>(tab_index), m_entries[index].title);
m_notebook->SetPageImage(static_cast<size_t>(tab_index), m_entries[index].page->get_icon_image_id());
}
}
Notebook* m_notebook{nullptr};
wxChoice* m_choice{nullptr};
wxBoxSizer* m_content_sizer{nullptr};
std::vector<Entry> m_entries;
int m_shown_index{wxNOT_FOUND};
};
PluginPages::~PluginPages()
{
shutdown();
@@ -499,44 +396,78 @@ void PluginPages::relayout()
while (m_parent->GetPageCount() > m_notebook_base_index)
m_parent->RemovePage(m_parent->GetPageCount() - 1);
if (m_overflow_panel != nullptr)
m_overflow_panel->clear();
const int visible_slots = std::max(1, m_visible_page_count);
const bool need_overflow = static_cast<int>(m_order.size()) > visible_slots;
const size_t individual_count = need_overflow ? static_cast<size_t>(visible_slots - 1) : m_order.size();
for (size_t i = 0; i < individual_count; ++i) {
const PluginCapabilityId& id = m_order[i];
// Every visible slot is a normal, individual tab hosting its own page. When there's
// overflow, the last slot's page is swappable via m_overflow_button/show_overflow_menu()
// rather than being a fixed page — m_swapped_in_id tracks which one currently sits there.
std::vector<PluginCapabilityId> tab_ids;
if (!need_overflow) {
tab_ids = m_order;
m_swapped_in_id.reset();
} else {
const auto overflow_begin = m_order.begin() + (visible_slots - 1);
tab_ids.assign(m_order.begin(), overflow_begin);
if (!m_swapped_in_id || std::find(overflow_begin, m_order.end(), *m_swapped_in_id) == m_order.end())
m_swapped_in_id = *overflow_begin;
tab_ids.push_back(*m_swapped_in_id);
}
for (const auto& id : tab_ids) {
PluginPage* page = m_pages.at(id);
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), page->get_icon_image_id());
}
if (need_overflow) {
if (m_overflow_panel == nullptr)
m_overflow_panel = new PluginPagesOverflowPanel(m_parent);
bool reselecting_overflow_entry = false;
for (size_t i = individual_count; i < m_order.size(); ++i) {
const PluginCapabilityId& id = m_order[i];
m_overflow_panel->add_entry(id, m_pages.at(id), wxString::FromUTF8(id.name));
if (page_tab_id(id) == id_to_reselect) {
m_overflow_panel->select_entry(id);
reselecting_overflow_entry = true;
}
if (m_overflow_button == nullptr) {
auto* btn = new Button(m_parent->GetBtnsListCtrl(), wxString(L"\u25BE"), wxString(), wxNO_BORDER);
btn->SetCornerRadius(0);
const int em = em_unit(m_parent);
btn->SetMinSize({40 * em / 10, 36 * em / 10});
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { show_overflow_menu(); });
GUI::wxGetApp().UpdateDarkUI(btn);
m_overflow_button = btn;
}
if (reselecting_overflow_entry)
id_to_reselect = "plugin.__overflow__";
m_parent->InsertPage(m_parent->GetPageCount(), "plugin.__overflow__", m_overflow_panel,
m_overflow_panel->current_title(), m_overflow_panel->current_image_id());
} else if (m_overflow_panel != nullptr) {
m_overflow_panel->Destroy();
m_overflow_panel = nullptr;
m_parent->SetOverflowButton(m_overflow_button);
} else if (m_overflow_button != nullptr) {
m_parent->SetOverflowButton(nullptr);
m_overflow_button->Destroy();
m_overflow_button = nullptr;
}
if (!id_to_reselect.empty())
m_parent->SelectPageByName(id_to_reselect);
}
void PluginPages::show_overflow_menu()
{
const int visible_slots = std::max(1, m_visible_page_count);
if (m_overflow_button == nullptr || static_cast<int>(m_order.size()) <= visible_slots)
return;
const std::vector<PluginCapabilityId> overflow_ids(m_order.begin() + (visible_slots - 1), m_order.end());
wxMenu menu;
for (size_t i = 0; i < overflow_ids.size(); ++i)
menu.AppendRadioItem(static_cast<int>(wxID_HIGHEST + 1 + i), wxString::FromUTF8(overflow_ids[i].name));
if (m_swapped_in_id) {
const auto it = std::find(overflow_ids.begin(), overflow_ids.end(), *m_swapped_in_id);
if (it != overflow_ids.end())
menu.Check(static_cast<int>(wxID_HIGHEST + 1 + (it - overflow_ids.begin())), true);
}
menu.Bind(wxEVT_MENU, [this, overflow_ids](wxCommandEvent& evt) {
const size_t index = static_cast<size_t>(evt.GetId() - (wxID_HIGHEST + 1));
if (index >= overflow_ids.size())
return;
m_swapped_in_id = overflow_ids[index];
relayout();
m_parent->SelectPageByName(page_tab_id(*m_swapped_in_id));
});
m_overflow_button->PopupMenu(&menu);
}
} // namespace Slic3r

View File

@@ -6,6 +6,7 @@
#include <atomic>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@@ -48,8 +49,6 @@ private:
int m_icon_image_id = wxBookCtrlBase::NO_IMAGE;
};
class PluginPagesOverflowPanel;
class PluginPages
{
public:
@@ -76,6 +75,7 @@ private:
void remove_page(const PluginCapabilityId& id);
void relayout();
void show_overflow_menu();
static wxString page_tab_id(const PluginCapabilityId& id);
std::map<PluginCapabilityId, PluginPage*> m_pages;
@@ -86,7 +86,8 @@ private:
std::unique_ptr<wxImageList> m_image_list;
int m_visible_page_count{0};
PluginPagesOverflowPanel* m_overflow_panel{nullptr};
std::optional<PluginCapabilityId> m_swapped_in_id;
wxWindow* m_overflow_button{nullptr};
};
} // namespace Slic3r