diff --git a/.claude/skills/orca-wxwidgets/references/controls-dataview.md b/.claude/skills/orca-wxwidgets/references/controls-dataview.md index 0789f47d79..3671e05991 100644 --- a/.claude/skills/orca-wxwidgets/references/controls-dataview.md +++ b/.claude/skills/orca-wxwidgets/references/controls-dataview.md @@ -331,6 +331,10 @@ OrcaSlicer: integer spinners are `::SpinInput` ([below](#spininput)); progress b Contract: - A page must be created with the book as its parent and added once; the book owns and deletes it (`interface/wx/bookctrl.h:253-254, 273`). `RemovePage` detaches without deleting, and you then own it (`:324-330`). +- Removing the selected page selects the page before it (the new first page if it was first) through `SetSelection`, + so that page is shown and PAGE_CHANGING/CHANGED are sent; removing a page before the selection only shifts the + index. This is `wxBookCtrlBase::DoSetSelectionAfterRemoval` (`src/common/bookctrl.cpp:477-495` **[source]**), + called from `DoRemovePage` by `wxSimplebook`, `wxChoicebook`, `wxListbook`, `wxToolbook` and Orca's `Notebook`. - `GetSelection()` inside a `PAGE_CHANGED` handler may return the old or the new page depending on the platform; use `event.GetSelection()` (`interface/wx/bookctrl.h:160-166`). - `wxSimplebook` has no UI; switch with `ChangeSelection()`. `SetSelection()` sends PAGE_CHANGING/CHANGED diff --git a/.claude/skills/orca-wxwidgets/references/orca-architecture.md b/.claude/skills/orca-wxwidgets/references/orca-architecture.md index 801494d802..6c23122910 100644 --- a/.claude/skills/orca-wxwidgets/references/orca-architecture.md +++ b/.claude/skills/orca-wxwidgets/references/orca-architecture.md @@ -332,7 +332,9 @@ indices: pages come and go per printer and per feature flag. pages depending on the printer and on `use_printer_agents`; a removed page stays registered but is not prebuilt (its `LazyPage::in_book()` is false). - Plugin pages are appended by `PluginPages::initialize` (`plugin/host/PluginPages.hpp`) with - namespaced ids (`plugin..`) that cannot collide with `TAB_ID_*`. + namespaced ids (`plugin..`) that cannot collide with `TAB_ID_*`. Each is a + `LazyPage` with order −1, destroyed when its capability goes away. + → [Deferred construction](#deferred-construction-lazy-lazypage-stagedbuild-idlescheduler) ### Preset tabs @@ -529,6 +531,32 @@ the main frame does nothing to a panel after creating it. m_idle.add(m_diff_dialog); ``` Cite: `IdleScheduler::tick`, `docs/HLSD/deferred-page-construction.md`. +- **Rule:** A lazy page that can be destroyed while the main frame lives takes a negative order and + stays out of `m_lazy_pages`. + **Why:** `m_lazy_pages` and `PrebuildQueue` hold raw `LazyBase*` and nothing removes one + (`PrebuildQueue` has only `add` and `clear`). The queue calls `pending()` on every task each slice, + and `prebuild_pages_when_idle` reads every entry of `m_lazy_pages`, so a page destroyed while still + listed can be read after it is freed. A page only taken out of the book is fine: it stays registered + and its `pending()` is false (`MainFrame::show_device`). + ```cpp + // Right (PluginPages::create_page): order -1, and no m_lazy_pages.push_back + auto* page = new GUI::LazyPage(m_parent, name, -1, [capability](wxWindow* parent) { + return new PluginPage(parent, capability); + }); + ``` + Cite: `PluginPages::create_page`, `PluginPages::remove_page`. +- **Rule:** Remove several lazy pages from a book left to right. + **Why:** removing the selected page selects and shows the page before it + (`references/controls-dataview.md` §Book controls), and showing an unbuilt `LazyPage` while the frame + is shown builds it. In any other order the page before the selected one can be one removed next, + built only to be destroyed; left to right it is one that stays (unless the selected page is the + book's first). + ```cpp + // Right (PluginPages::shutdown): m_order is the tabs' left-to-right order + for (const PluginCapabilityId& id : std::vector(m_order)) + remove_page(id); + ``` + Cite: `PluginPages::shutdown`, `PluginPages::relayout`, `PluginPages::on_plugin_deregister`. ## Plater and Sidebar diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8c4fc58d03..8c25b31770 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -184,7 +184,8 @@ class MainFrame : public DPIFrame std::string m_name{ "prepare_layout" }; wxSize m_laid_out_size; } m_prepare_layout_prebuild{ *this }; - // Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them. + // Every built-in LazyPage, in and out of the book; prebuild_pages_when_idle() registers them. + // Plugin pages stay out: PluginPages destroys them at runtime. std::vector m_lazy_pages; // The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built. wxString m_printer_url; diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp index 4ea2ba0a72..1993748cc6 100644 --- a/src/slic3r/GUI/PrinterWebView.cpp +++ b/src/slic3r/GUI/PrinterWebView.cpp @@ -119,33 +119,13 @@ PrinterWebView::PrinterWebView(wxWindow *parent) wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); // Create the webview - m_browser = WebView::CreateWebView(this, ""); - if (m_browser == nullptr) { - wxLogError("Could not init m_browser"); - return; - } - -#ifdef __linux__ - inject_vue_resize_workaround(m_browser); - - auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db"); - auto wv = static_cast(m_browser->GetNativeBackend()); - auto wv_ctx = webkit_web_view_get_context(wv); - auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx); - webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE); -#endif - - m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this); - m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this); - m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this); - m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this); + create_browser(); + m_reset_on_show = WebView::NeedsRecreateOnShow(); SetSizer(topsizer); topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); - update_mode(); - // Log backend information /* m_browser->GetUserAgent() may lead crash if (wxGetApp().get_mode() == comDevelop) { @@ -177,12 +157,44 @@ PrinterWebView::~PrinterWebView() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End"; } +void PrinterWebView::create_browser() +{ + m_browser = WebView::CreateWebView(this, ""); + +#ifdef __linux__ + inject_vue_resize_workaround(m_browser); + + auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db"); + auto wv = static_cast(m_browser->GetNativeBackend()); + auto wv_ctx = webkit_web_view_get_context(wv); + auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx); + webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE); +#endif + + m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this); + m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this); + m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this); + m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this); + update_mode(); +} + +void PrinterWebView::reset_browser() +{ + m_browser->Destroy(); // also removes it from the sizer + create_browser(); + GetSizer()->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); + Layout(); + + // OnLoaded may have cleared m_url_deferred already, so requeue the last url for Show(). + m_apikey_sent = false; + m_url_deferred = m_url; +} + void PrinterWebView::load_url(wxString& url, wxString apikey) { // this->Show(); // this->Raise(); - if (m_browser == nullptr) - return; + m_url = url; m_apikey = apikey; m_apikey_sent = false; m_handler = create_printer_webview_handler(*this); @@ -200,6 +212,8 @@ void PrinterWebView::load_url(wxString& url, wxString apikey) bool PrinterWebView::Show(bool show) { + if (show && std::exchange(m_reset_on_show, false)) + reset_browser(); if (show && !m_url_deferred.empty()) { m_browser->LoadURL(m_url_deferred); //ORCA: m_url_deferred will be cleared on load success diff --git a/src/slic3r/GUI/PrinterWebView.hpp b/src/slic3r/GUI/PrinterWebView.hpp index 9bd8ff51a8..53c4f30557 100644 --- a/src/slic3r/GUI/PrinterWebView.hpp +++ b/src/slic3r/GUI/PrinterWebView.hpp @@ -58,13 +58,18 @@ private: friend class PrinterWebViewHandler; void SendAPIKey(); + void create_browser(); + void reset_browser(); wxWebView* m_browser; long m_zoomFactor; wxString m_apikey; bool m_apikey_sent; + // Last url passed to load_url(), reloaded after reset_browser(). + wxString m_url; wxString m_url_deferred; std::unique_ptr m_handler; + bool m_reset_on_show{false}; // DECLARE_EVENT_TABLE() }; diff --git a/src/slic3r/GUI/Project.cpp b/src/slic3r/GUI/Project.cpp index 47945e1179..d8e7d6b806 100644 --- a/src/slic3r/GUI/Project.cpp +++ b/src/slic3r/GUI/Project.cpp @@ -80,16 +80,10 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); - m_browser = WebView::CreateWebView(this, m_project_home_url); - if (m_browser == nullptr) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("load web view of project page failed"); - return; - } + create_browser(); + m_reset_on_show = WebView::NeedsRecreateOnShow(); //m_browser->Hide(); main_sizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); - m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this); - m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId()); - Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this, m_browser->GetId()); Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this); @@ -117,6 +111,23 @@ void ProjectPanel::shutdown() m_reload_task.reset(); } +void ProjectPanel::create_browser() +{ + m_browser = WebView::CreateWebView(this, m_project_home_url); + m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this); + m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId()); + m_browser->Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this); +} + +void ProjectPanel::reset_browser() +{ + m_browser->Destroy(); // also removes it from the sizer + create_browser(); + GetSizer()->Insert(0, m_browser, wxSizerFlags().Expand().Proportion(1)); + Layout(); + m_web_init_completed.store(false, std::memory_order_release); +} + // Helper to convert newlines to
static std::string convert_newlines_to_br(const std::string& text) { std::string result = text; @@ -281,12 +292,13 @@ void ProjectPanel::on_reload(wxCommandEvent& evt) wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore)); - if (m_web_init_completed.load(std::memory_order_acquire) && - !cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) { + if (!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) { wxGetApp().CallAfter([this, cancel_token, strJS] { if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing()) return; - RunScript(strJS.ToStdString()); + m_info_script = strJS.ToStdString(); + if (m_web_init_completed.load(std::memory_order_acquire)) + RunScript(m_info_script); }); } }); @@ -327,6 +339,11 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt) } else if (strCmd == "request_3mf_info") { m_web_init_completed.store(true, std::memory_order_release); + // Replay the stored info after each page load. + CallAfter([this] { + if (!m_info_script.empty()) + RunScript(m_info_script); + }); } else if (strCmd == "edit_project_info") { show_info_editor(true); @@ -383,6 +400,8 @@ void ProjectPanel::clear_model_info() wxGetApp().CallAfter([this, cancel_token, strJS] { if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing()) return; + // Runs after any store queued by an earlier reload pass, so stale info is never replayed. + m_info_script.clear(); RunScript(strJS.ToStdString()); }); } @@ -523,10 +542,12 @@ void ProjectPanel::RunScript(std::string content) WebView::RunScript(m_browser, content); } -bool ProjectPanel::Show(bool show) +bool ProjectPanel::Show(bool show) { + if (show && std::exchange(m_reset_on_show, false)) + reset_browser(); if (show) update_model_data(); - return wxPanel::Show(show); + return wxPanel::Show(show); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Project.hpp b/src/slic3r/GUI/Project.hpp index c7a85bce74..90e6eda1f1 100644 --- a/src/slic3r/GUI/Project.hpp +++ b/src/slic3r/GUI/Project.hpp @@ -80,9 +80,14 @@ private: AuxiliaryPanel* m_auxiliary{nullptr}; wxString m_project_home_url; wxString m_root_dir; + // Last show_3mf_info script, also sent whenever the page asks for it. + std::string m_info_script; + bool m_reset_on_show{false}; static inline std::atomic m_sequence_id{8000}; void show_info_editor(bool show); + void create_browser(); + void reset_browser(); public: diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 02fc7b0877..0101c83534 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -62,10 +62,10 @@ namespace GUI { WebViewPanel::WebViewPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) { - wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html"); + m_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html"); wxString strlang = wxGetApp().current_language_code_safe(); if (strlang != "") - url += "?lang=" + strlang; + m_home_url += "?lang=" + strlang; wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); @@ -109,12 +109,8 @@ WebViewPanel::WebViewPanel(wxWindow *parent) m_info = new wxInfoBar(this); topsizer->Add(m_info, wxSizerFlags().Expand()); // Create the webview - m_browser = WebView::CreateWebView(this, url); - if (m_browser == nullptr) { - wxLogError("Could not init m_browser"); - return; - } - m_browser->Hide(); + create_browser(); + m_reset_on_show = WebView::NeedsRecreateOnShow(); SetSizer(topsizer); topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); @@ -264,6 +260,27 @@ WebViewPanel::~WebViewPanel() } +void WebViewPanel::create_browser() +{ + m_browser = WebView::CreateWebView(this, m_home_url); + m_browser->Hide(); +} + +void WebViewPanel::reset_browser() +{ + m_browser->Destroy(); // also removes it from the sizer + create_browser(); + GetSizer()->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); + Layout(); +} + +bool WebViewPanel::Show(bool show) +{ + if (show && std::exchange(m_reset_on_show, false)) + reset_browser(); + return wxPanel::Show(show); +} + void WebViewPanel::load_url(wxString& url) { this->Show(); diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 96d18f23c0..8a4ecbf35d 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -113,9 +113,15 @@ public: int get_model_mall_detail_url(std::string *url, std::string id); void update_mode(); + + bool Show(bool show = true) override; private: + void create_browser(); + void reset_browser(); wxWebView* m_browser; + wxString m_home_url; + bool m_reset_on_show{false}; wxButton * m_button_stop; wxTextCtrl *m_url; #if !BBL_RELEASE_TO_PUBLIC diff --git a/src/slic3r/GUI/Widgets/WebView.cpp b/src/slic3r/GUI/Widgets/WebView.cpp index 9a4d5f90b0..c446875d1f 100644 --- a/src/slic3r/GUI/Widgets/WebView.cpp +++ b/src/slic3r/GUI/Widgets/WebView.cpp @@ -381,6 +381,17 @@ void WebView::MarkScriptMessageHandlerAdded(wxWebView * webView) if (WebViewRef *ref = webview_ref(webView)) ref->m_script_handler_added = true; } + +bool WebView::NeedsRecreateOnShow() +{ + const bool recreating = Slic3r::GUI::wxGetApp().is_recreating_gui(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": is_recreating_gui = " << recreating; +#ifdef __WIN32__ + return recreating; +#else + return false; +#endif +} #if wxUSE_WEBVIEW_EDGE bool WebView::CheckWebViewRuntime() { diff --git a/src/slic3r/GUI/Widgets/WebView.hpp b/src/slic3r/GUI/Widgets/WebView.hpp index 2dc126643d..0172f3f8ac 100644 --- a/src/slic3r/GUI/Widgets/WebView.hpp +++ b/src/slic3r/GUI/Widgets/WebView.hpp @@ -23,6 +23,10 @@ public: // Marks "wx" as registered so CreateWebView's deferred add skips the duplicate. static void MarkScriptMessageHandlerAdded(wxWebView * webView); + // On Windows, a WebView2 backend created during a GUI rebuild (language switch) can come up + // ignoring every navigation. A panel that gets true here recreates its view on first Show(). + static bool NeedsRecreateOnShow(); + static void RecreateAll(); }; diff --git a/src/slic3r/plugin/host/PluginPages.cpp b/src/slic3r/plugin/host/PluginPages.cpp index 680b573bd7..36b4cd10b4 100644 --- a/src/slic3r/plugin/host/PluginPages.cpp +++ b/src/slic3r/plugin/host/PluginPages.cpp @@ -1,6 +1,7 @@ #include "PluginPages.hpp" #include "libslic3r/AppConfig.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/Notebook.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Widgets/Button.hpp" @@ -183,8 +184,10 @@ void PluginPages::initialize(Notebook* parent) void PluginPages::shutdown() { - while (!m_pages.empty()) - remove_page(m_pages.begin()->first); + // Removing the selected tab selects the tab to its left. In tab order that is a built-in tab, + // never an unbuilt plugin page that is removed next and would be built only to be destroyed. + for (const PluginCapabilityId& id : std::vector(m_order)) + remove_page(id); m_parent = nullptr; } @@ -225,8 +228,7 @@ bool PluginPages::create_page(const PluginCapabilityId& id) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key; } - auto* page = new PluginPage(m_parent, std::move(capability)); - + wxBitmap bitmap; if (!icon.empty()) { try { boost::filesystem::path icon_path(icon); @@ -234,7 +236,7 @@ bool PluginPages::create_page(const PluginCapabilityId& id) if (extension == ".svg" || extension == ".png") icon_path.replace_extension(); - page->set_icon(create_scaled_bitmap(icon_path.string(), m_parent, 20)); + bitmap = create_scaled_bitmap(icon_path.string(), m_parent, 20); } catch (const std::exception& error) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what(); } catch (...) { @@ -242,7 +244,15 @@ bool PluginPages::create_page(const PluginCapabilityId& id) } } - m_pages.emplace(id, page); + // Built the first time its tab is shown, so a language switch never creates the page's browser + // while the main window is being rebuilt. Never prebuilt: the idle queue cannot drop a page + // that remove_page() destroys. + auto* page = new GUI::LazyPage(m_parent, GUI::into_u8(page_tab_id(id)), -1, + [capability = std::move(capability)](wxWindow* parent) { + return new PluginPage(parent, capability); + }); + + m_pages.emplace(id, Page{page, bitmap}); m_order.push_back(id); return true; } @@ -271,16 +281,10 @@ void PluginPages::on_plugin_register(const std::string& plugin_key) void PluginPages::on_plugin_deregister(const std::string& plugin_key) { - for (auto it = m_pages.begin(); it != m_pages.end();) { - if (it->first.plugin_key != plugin_key) { - ++it; - continue; - } - - const PluginCapabilityId id = it->first; - ++it; - remove_page(id); - } + // In tab order, as in shutdown(). + for (const PluginCapabilityId& id : std::vector(m_order)) + if (id.plugin_key == plugin_key) + remove_page(id); } void PluginPages::remove_page(const PluginCapabilityId& id) @@ -289,8 +293,10 @@ void PluginPages::remove_page(const PluginCapabilityId& id) if (it == m_pages.end()) return; - PluginPage* page = it->second; - page->detach_capability(); + GUI::LazyPage* page = it->second.page; + // Only a built page has installed a message sender on the capability. + if (PluginPage* built = page->get()) + built->detach_capability(); m_pages.erase(it); m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end()); @@ -348,26 +354,27 @@ void PluginPages::relayout() bool up_to_date = page_count >= tab_ids.size(); for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i) up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]); - for (const auto& [id, page] : m_pages) { + for (const auto& [id, entry] : m_pages) { if (!up_to_date) break; const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end(); - up_to_date = (m_parent->FindPage(page) != wxNOT_FOUND) == wanted; + up_to_date = entry.page->in_book() == wanted; } if (!up_to_date) { const wxString id_to_reselect = m_parent->GetSelectedPageName(); - for (const auto& [id, page] : m_pages) { - const int idx = m_parent->FindPage(page); + // In tab order, as in shutdown(). + for (const auto& id : m_order) { + const int idx = m_parent->FindPage(m_pages.at(id).page); if (idx != wxNOT_FOUND) m_parent->RemovePage(idx); } 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), "", - false, page->icon()); + const Page& entry = m_pages.at(id); + m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), entry.page, wxString::FromUTF8(id.name), "", + false, entry.icon); } if (!id_to_reselect.empty()) diff --git a/src/slic3r/plugin/host/PluginPages.hpp b/src/slic3r/plugin/host/PluginPages.hpp index c0dd74b94d..90d8c28a03 100644 --- a/src/slic3r/plugin/host/PluginPages.hpp +++ b/src/slic3r/plugin/host/PluginPages.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -29,8 +30,6 @@ public: void detach_capability(); void push_message(const std::string& message); - void set_icon(const wxBitmap& icon) { m_icon = icon; } - const wxBitmap& icon() const { return m_icon; } protected: std::optional page_html() override; @@ -41,7 +40,6 @@ private: std::shared_ptr m_cap; std::shared_ptr> m_lifetime; - wxBitmap m_icon; }; class PluginPages @@ -73,7 +71,13 @@ private: void show_overflow_menu(); static wxString page_tab_id(const PluginCapabilityId& id); - std::map m_pages; + struct Page + { + GUI::LazyPage* page{nullptr}; + wxBitmap icon; + }; + + std::map m_pages; std::vector m_order; Notebook* m_parent{nullptr};