diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 2411d6ee69..7ed404e40e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3171,6 +3171,32 @@ bool GUI_App::on_init_inner() refresh_plugins_dialog(); }); + // why: keep the speed-dial ActionRegistry always-clean. Each loader callback marshals + // a TARGETED refresh onto the UI thread (the callback may fire on a worker thread, so + // it must not touch the registry's vector directly). No custom event - CallAfter is + // the pubsub channel. + auto refresh_pkg = [](const std::string& key, ActionChange change) { + if (!wxTheApp || wxGetApp().is_closing()) + return; + wxGetApp().CallAfter([key, change] { + if (!wxGetApp().is_closing()) + wxGetApp().action_registry().refresh_package(key, change); + }); + }; + auto refresh_cap = [](const PluginCapabilityIdentifier& cap, ActionChange change) { + if (cap.type != PluginCapabilityType::Script || !wxTheApp || wxGetApp().is_closing()) + return; + const std::string key = cap.plugin_key, name = cap.name; + wxGetApp().CallAfter([key, name, change] { + if (!wxGetApp().is_closing()) + wxGetApp().action_registry().refresh_capability(key, name, change); + }); + }; + plugin_mgr.get_loader().subscribe_on_load_callback([refresh_pkg](const std::string& key) { refresh_pkg(key, ActionChange::Added); }); + plugin_mgr.get_loader().subscribe_on_unload_callback([refresh_pkg](const std::string& key) { refresh_pkg(key, ActionChange::Removed); }); + plugin_mgr.get_loader().subscribe_on_capability_load_callback([refresh_cap](const PluginCapabilityIdentifier& c) { refresh_cap(c, ActionChange::Added); }); + plugin_mgr.get_loader().subscribe_on_capability_unload_callback([refresh_cap](const PluginCapabilityIdentifier& c) { refresh_cap(c, ActionChange::Removed); }); + for (const std::string& plugin_key : plugin_mgr.get_catalog().get_enabled_plugin_keys()) { if (!plugin_mgr.get_loader().is_plugin_loaded(plugin_key)) { plugin_mgr.get_loader().load_plugin(plugin_mgr.get_catalog(), plugin_key, false); @@ -3178,6 +3204,11 @@ bool GUI_App::on_init_inner() } } + // why: seed the registry once on the UI thread after startup auto-load. The deferred + // per-plugin callbacks above are idempotent (upsert by id), so re-processing the same + // packages is harmless; this guarantees a populated set even before any callback lands. + m_action_registry.build(); + if (m_agent) plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast(m_agent->get_cloud_agent())); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 48789f3247..323fff5be3 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -3,6 +3,7 @@ #include #include +#include "ActionRegistry.hpp" #include "ImGuiWrapper.hpp" #include "ConfigWizard.hpp" #include "OpenGLManager.hpp" @@ -554,6 +555,7 @@ public: PresetBundleDialog* m_preset_bundle_dlg{nullptr}; PluginsDialog* m_plugins_dlg{nullptr}; TerminalDialog* m_terminal_dlg{nullptr}; + ActionRegistry m_action_registry; void start_http_server(const std::string& provider = ORCA_CLOUD_PROVIDER); @@ -625,6 +627,7 @@ public: void open_plugins_dialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); void open_terminal_dialog(); void open_speed_dial(); + ActionRegistry& action_registry() { return m_action_registry; } void open_exportpresetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); virtual bool OnExceptionInMainLoop() override; // Calls wxLaunchDefaultBrowser if user confirms in dialog.