mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Add a Dockable HTML Panel API for Plugins (#15736)
orca.host.ui.create_dock_panel(html, title, width, height, on_message, on_close, dock) hosts plugin HTML in a pane of the Plater's dock manager, next to the sidebar, and returns a UiDockPanel handle (post/show/hide/close/is_open). The arguments follow create_window(). The panel uses the window.orca bridge of plugin windows, restores its position and size from the saved window layout, hides with the Plater off the Prepare and Preview tabs when floating, and is closed with its plugin; plugin panes are removed in MainFrame::shutdown(). The web view hosting moves out of PluginPage into a shared WebPanel base: bootstrap page and swap to the plugin HTML, theme, element-default and bridge scripts, window.orca message parsing, delivery to the page, and live re-theming, also re-applied on every load after the swap. Pages tabs and docked panels both derive from it. Pages tabs now re-theme in place on a theme change instead of being reloaded, and a window.orca call a host does not support is logged. What the hosts share no longer lives in one of them: the bootstrap page, the base URL and the plugin-window bridge move to Widgets/WebHosting, used by WebDialog and WebPanel alike. The Plater restores plugin panes with a new saved-layout parser, GUI/AuiPaneLayout, kept in its own small header so slic3rutils can test it without pulling in the Plater. The web hosting classes carry no plugin name, so other hosts can reuse them: PluginWebDialog becomes WebDialog (its bootstrap page moves to resources/web/dialog/WebDialog), and destroy_for_plugin(), load_plugin_content() and plugin_defaults_user_script() become destroy_silently(), load_page_html() and element_defaults_user_script(). Includes a sample plugin (sandboxes/orca_dock_panel_plugin_any.py) and binding and layout-helper tests in slic3rutils.
This commit is contained in:
@@ -87,6 +87,7 @@
|
||||
#ifdef __WXGTK__
|
||||
#include "LinuxDisplayBackend.hpp"
|
||||
#endif
|
||||
#include "AuiPaneLayout.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "GUI_Factories.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
@@ -6748,6 +6749,14 @@ struct Plater::priv
|
||||
|
||||
// GUI elements
|
||||
AuiMgr m_aui_mgr;
|
||||
// Live dock panes. `on_close` runs when the user closes one from its close button; `shown` is
|
||||
// what the owner asked for.
|
||||
struct DockPane
|
||||
{
|
||||
std::function<void()> on_close;
|
||||
bool shown{true};
|
||||
};
|
||||
std::map<wxWindow*, DockPane> m_dock_panes;
|
||||
wxString m_default_window_layout;
|
||||
wxPanel* current_panel{ nullptr };
|
||||
std::vector<wxPanel*> panels;
|
||||
@@ -6920,6 +6929,11 @@ struct Plater::priv
|
||||
void update_sidebar(bool force_update = false);
|
||||
void reset_window_layout();
|
||||
Sidebar::DockingState get_sidebar_docking_state();
|
||||
void add_dock_pane(wxWindow* window, const std::string& name, const wxString& caption, const std::string& dock,
|
||||
const wxSize& size, std::function<void()> on_close);
|
||||
void remove_dock_pane(wxWindow* window);
|
||||
void show_dock_pane(wxWindow* window, bool show);
|
||||
bool dock_pane_visible(const DockPane& dock_pane, const wxAuiPaneInfo& pane) const;
|
||||
|
||||
bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); }
|
||||
|
||||
@@ -7500,6 +7514,18 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
panel_3d->SetSizer(panel_sizer);
|
||||
m_aui_mgr.AddPane(panel_3d, wxAuiPaneInfo().Name("main").CenterPane().PaneBorder(false));
|
||||
|
||||
q->Bind(wxEVT_AUI_PANE_CLOSE, [this](wxAuiManagerEvent& evt) {
|
||||
const wxAuiPaneInfo* pane = evt.GetPane();
|
||||
auto it = pane != nullptr ? m_dock_panes.find(pane->window) : m_dock_panes.end();
|
||||
if (it != m_dock_panes.end()) {
|
||||
const std::function<void()> on_close = std::move(it->second.on_close);
|
||||
m_dock_panes.erase(it);
|
||||
if (on_close)
|
||||
on_close();
|
||||
}
|
||||
evt.Skip();
|
||||
});
|
||||
|
||||
m_default_window_layout = m_aui_mgr.SavePerspective();
|
||||
|
||||
{
|
||||
@@ -8165,6 +8191,14 @@ void Plater::priv::update_sidebar(bool force_update) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [window, dock_pane] : m_dock_panes) {
|
||||
wxAuiPaneInfo& pane = m_aui_mgr.GetPane(window);
|
||||
if (pane.IsOk() && pane.IsShown() != dock_pane_visible(dock_pane, pane)) {
|
||||
pane.Show(!pane.IsShown());
|
||||
needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_update) {
|
||||
notification_manager->set_sidebar_collapsed(sidebar.IsShown());
|
||||
m_aui_mgr.Update();
|
||||
@@ -8174,10 +8208,96 @@ void Plater::priv::update_sidebar(bool force_update) {
|
||||
void Plater::priv::reset_window_layout()
|
||||
{
|
||||
m_aui_mgr.LoadPerspective(m_default_window_layout, false);
|
||||
// Loading a layout docks and hides every pane it does not list, and the default layout lists no
|
||||
// dock panes: a floating dock pane is docked again, like the rest of the window.
|
||||
for (const auto& [window, dock_pane] : m_dock_panes)
|
||||
if (wxAuiPaneInfo& pane = m_aui_mgr.GetPane(window); pane.IsOk())
|
||||
pane.Show(dock_pane_visible(dock_pane, pane));
|
||||
sidebar_layout.is_collapsed = false;
|
||||
update_sidebar(true);
|
||||
}
|
||||
|
||||
bool Plater::priv::dock_pane_visible(const DockPane& dock_pane, const wxAuiPaneInfo& pane) const
|
||||
{
|
||||
// A floating pane is a top-level window, so it does not hide with the Plater on other tabs.
|
||||
return dock_pane.shown && (!pane.IsFloating() || sidebar_layout.show);
|
||||
}
|
||||
|
||||
void Plater::priv::add_dock_pane(wxWindow* window, const std::string& name, const wxString& caption, const std::string& dock,
|
||||
const wxSize& size, std::function<void()> on_close)
|
||||
{
|
||||
const wxString base_name = wxString::FromUTF8(name);
|
||||
wxString unique_name = base_name;
|
||||
for (int i = 2; m_aui_mgr.GetPane(unique_name).IsOk(); ++i)
|
||||
unique_name = base_name + wxString::Format("#%d", i);
|
||||
|
||||
// A restored layout below already holds pixels.
|
||||
const wxSize pixels = q->FromDIP(size);
|
||||
wxAuiPaneInfo info;
|
||||
info.Name(unique_name).Caption(caption).BestSize(pixels).FloatingSize(pixels).DestroyOnClose(true);
|
||||
if (dock == "left")
|
||||
info.Left();
|
||||
else if (dock == "bottom")
|
||||
info.Bottom();
|
||||
else
|
||||
info.Right();
|
||||
if (dock == "float")
|
||||
info.Float();
|
||||
|
||||
// Put the pane back where it was the last time the window layout was saved with it open.
|
||||
const std::string saved = aui_pane_layout_entry(wxGetApp().app_config->get("window_layout"), unique_name.utf8_string());
|
||||
if (!saved.empty()) {
|
||||
m_aui_mgr.LoadPaneInfo(wxString::FromUTF8(saved), info);
|
||||
info.Caption(caption).DestroyOnClose(true).Show();
|
||||
}
|
||||
|
||||
// Floating is disabled on Wayland.
|
||||
if ((m_aui_mgr.GetFlags() & wxAUI_MGR_ALLOW_FLOATING) == 0) {
|
||||
info.Dock().Floatable(false);
|
||||
if (info.dock_direction == wxAUI_DOCK_NONE)
|
||||
info.Right();
|
||||
}
|
||||
|
||||
const DockPane& dock_pane = m_dock_panes[window] = DockPane{std::move(on_close)};
|
||||
info.Show(dock_pane_visible(dock_pane, info));
|
||||
m_aui_mgr.AddPane(window, info);
|
||||
|
||||
// wxAUI does not record a dragged sash in best_size, so track the docked size like the sidebar
|
||||
// does, for the saved layout.
|
||||
window->Bind(wxEVT_IDLE, [this, window](wxIdleEvent& evt) {
|
||||
wxAuiPaneInfo& pane = m_aui_mgr.GetPane(window);
|
||||
if (pane.IsOk() && pane.IsShown() && pane.IsDocked() && pane.rect.GetWidth() > 0 && pane.rect.GetHeight() > 0) {
|
||||
const bool horizontal = pane.dock_direction == wxAUI_DOCK_TOP || pane.dock_direction == wxAUI_DOCK_BOTTOM;
|
||||
pane.BestSize(horizontal ? pane.best_size.GetWidth() : pane.rect.GetWidth(),
|
||||
horizontal ? pane.rect.GetHeight() : pane.best_size.GetHeight());
|
||||
}
|
||||
evt.Skip();
|
||||
});
|
||||
|
||||
m_aui_mgr.Update();
|
||||
}
|
||||
|
||||
void Plater::priv::remove_dock_pane(wxWindow* window)
|
||||
{
|
||||
m_dock_panes.erase(window);
|
||||
if (m_aui_mgr.DetachPane(window))
|
||||
m_aui_mgr.Update();
|
||||
window->Destroy();
|
||||
}
|
||||
|
||||
void Plater::priv::show_dock_pane(wxWindow* window, bool show)
|
||||
{
|
||||
const auto it = m_dock_panes.find(window);
|
||||
wxAuiPaneInfo& pane = m_aui_mgr.GetPane(window);
|
||||
if (it == m_dock_panes.end() || !pane.IsOk())
|
||||
return;
|
||||
it->second.shown = show;
|
||||
if (pane.IsShown() == dock_pane_visible(it->second, pane))
|
||||
return;
|
||||
pane.Show(!pane.IsShown());
|
||||
m_aui_mgr.Update();
|
||||
}
|
||||
|
||||
Sidebar::DockingState Plater::priv::get_sidebar_docking_state() {
|
||||
if (!sidebar_layout.is_enabled) {
|
||||
return Sidebar::None;
|
||||
@@ -17772,6 +17892,19 @@ Sidebar::DockingState Plater::get_sidebar_docking_state() const { return p->get_
|
||||
|
||||
void Plater::reset_window_layout() { p->reset_window_layout(); }
|
||||
|
||||
void Plater::add_dock_pane(wxWindow* window, const std::string& name, const wxString& caption, const std::string& dock,
|
||||
const wxSize& size, std::function<void()> on_close)
|
||||
{
|
||||
p->add_dock_pane(window, name, caption, dock, size, std::move(on_close));
|
||||
}
|
||||
void Plater::remove_dock_pane(wxWindow* window) { p->remove_dock_pane(window); }
|
||||
void Plater::remove_dock_panes()
|
||||
{
|
||||
while (!p->m_dock_panes.empty())
|
||||
p->remove_dock_pane(p->m_dock_panes.begin()->first);
|
||||
}
|
||||
void Plater::show_dock_pane(wxWindow* window, bool show) { p->show_dock_pane(window, show); }
|
||||
|
||||
//BBS
|
||||
void Plater::select_curr_plate_all() { p->select_curr_plate_all(); }
|
||||
void Plater::remove_curr_plate_all() { p->remove_curr_plate_all(); }
|
||||
|
||||
Reference in New Issue
Block a user