From 7be7f075516ef7fbe4b362b12399b918c86ade0a Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Tue, 11 Aug 2026 17:50:49 +0800 Subject: [PATCH] feat: UI for dropdown to select which plugin to show past max visible pages --- src/slic3r/GUI/Notebook.cpp | 18 +++ src/slic3r/GUI/Notebook.hpp | 5 + src/slic3r/plugin/host/PluginPages.cpp | 189 ++++++++----------------- src/slic3r/plugin/host/PluginPages.hpp | 7 +- 4 files changed, 87 insertions(+), 132 deletions(-) diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index 847996aba9..f9db689faa 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -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() diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index 859ada37a5..da90535481 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -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 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(m_bookctrl); } + void SetOverflowButton(wxWindow* button) { GetBtnsListCtrl()->SetOverflowButton(button); } int FindPageByName(const wxString& id) const { diff --git a/src/slic3r/plugin/host/PluginPages.cpp b/src/slic3r/plugin/host/PluginPages.cpp index 1a40fd8984..551e39877d 100644 --- a/src/slic3r/plugin/host/PluginPages.cpp +++ b/src/slic3r/plugin/host/PluginPages.cpp @@ -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 #include -#include +#include #include #include @@ -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(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(m_shown_index)].title; } - int current_image_id() const - { - return m_shown_index == wxNOT_FOUND ? wxBookCtrlBase::NO_IMAGE : m_entries[static_cast(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(selection)); - } - - void select_index(size_t index) - { - if (index >= m_entries.size()) - return; - - if (m_shown_index != wxNOT_FOUND) - m_entries[static_cast(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(index); - m_choice->SetSelection(static_cast(index)); - - const int tab_index = m_notebook->FindPage(this); - if (tab_index != wxNOT_FOUND) { - m_notebook->SetPageText(static_cast(tab_index), m_entries[index].title); - m_notebook->SetPageImage(static_cast(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 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(m_order.size()) > visible_slots; - const size_t individual_count = need_overflow ? static_cast(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 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(m_order.size()) <= visible_slots) + return; + + const std::vector 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(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(wxID_HIGHEST + 1 + (it - overflow_ids.begin())), true); + } + + menu.Bind(wxEVT_MENU, [this, overflow_ids](wxCommandEvent& evt) { + const size_t index = static_cast(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 diff --git a/src/slic3r/plugin/host/PluginPages.hpp b/src/slic3r/plugin/host/PluginPages.hpp index 99213b425b..cc54332dbe 100644 --- a/src/slic3r/plugin/host/PluginPages.hpp +++ b/src/slic3r/plugin/host/PluginPages.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -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 m_pages; @@ -86,7 +86,8 @@ private: std::unique_ptr m_image_list; int m_visible_page_count{0}; - PluginPagesOverflowPanel* m_overflow_panel{nullptr}; + std::optional m_swapped_in_id; + wxWindow* m_overflow_button{nullptr}; }; } // namespace Slic3r