mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
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 PluginWebPanel 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/PluginWebContent, used by PluginWebDialog and PluginWebPanel alike, and the saved-layout parser moves to Widgets/AuiPaneLayout, so the Plater does not depend on a plugin widget. Includes a sample plugin (sandboxes/orca_dock_panel_plugin_any.py) and binding and layout-helper tests in slic3rutils.
198 lines
6.1 KiB
C++
198 lines
6.1 KiB
C++
#include "PluginWebDialog.hpp"
|
|
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/Widgets/PluginWebContent.hpp"
|
|
|
|
#include <wx/event.h>
|
|
|
|
#include <utility>
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
PluginWebDialog::PluginWebDialog(wxWindow* parent,
|
|
const wxString& title,
|
|
const std::string& html,
|
|
const wxSize& size,
|
|
MessageHandler on_message,
|
|
SubmitHandler on_submit,
|
|
CloseHandler on_close,
|
|
CloseHandler on_destroyed,
|
|
long wx_style)
|
|
: WebViewHostDialog(parent, wxID_ANY, title, wxDefaultPosition, size, wx_style)
|
|
, m_html(html)
|
|
, m_on_message(std::move(on_message))
|
|
, m_on_submit(std::move(on_submit))
|
|
, m_on_close(std::move(on_close))
|
|
, m_on_destroyed(std::move(on_destroyed))
|
|
{
|
|
// A tiny bundled bootstrap page brings the webview up; the real plugin HTML
|
|
// is swapped in via SetPage once the bootstrap finishes loading.
|
|
create_webview(plugin_web::BOOTSTRAP_PAGE, title, size, wxSize(320, 240));
|
|
|
|
// Paint the window/webview in the themed background so there is no white
|
|
// flash before the (transparent) bootstrap page and plugin HTML render.
|
|
SetBackgroundColour(wxGetApp().get_window_default_clr());
|
|
|
|
if (wxWebView* wv = browser()) {
|
|
wv->SetBackgroundColour(wxGetApp().get_window_default_clr());
|
|
// Theme contract + plugin defaults + bridge are registered by the base
|
|
// create_webview() via add_user_scripts(); nothing to add here.
|
|
// Swap in the plugin HTML once the bootstrap page settles. Bind ERROR too so a
|
|
// missing/blocked bootstrap resource (e.g. a packaged build) still triggers it.
|
|
Bind(wxEVT_WEBVIEW_LOADED, &PluginWebDialog::on_bootstrap_event, this, wv->GetId());
|
|
Bind(wxEVT_WEBVIEW_ERROR, &PluginWebDialog::on_bootstrap_event, this, wv->GetId());
|
|
}
|
|
Bind(wxEVT_CLOSE_WINDOW, &PluginWebDialog::on_close_window, this);
|
|
}
|
|
|
|
void PluginWebDialog::add_user_scripts()
|
|
{
|
|
if (wxWebView* wv = browser()) {
|
|
wv->AddUserScript(wxString::FromUTF8(WebViewHostDialog::plugin_defaults_user_script()));
|
|
wv->AddUserScript(wxString::FromUTF8(plugin_web::orca_bridge_script()));
|
|
}
|
|
}
|
|
|
|
PluginWebDialog::~PluginWebDialog()
|
|
{
|
|
// Runs on every destruction path. Deliberately NOT a wxEVT_DESTROY handler:
|
|
// that event is sent from the base ~wxDialog(), after this subclass's members
|
|
// are already destroyed. Here the members are still alive, and the callback
|
|
// only touches the host-side registry (no Python), so this is safe.
|
|
if (m_on_destroyed)
|
|
m_on_destroyed();
|
|
}
|
|
|
|
void PluginWebDialog::post_message(PluginWebDialog* dialog, const nlohmann::json& data)
|
|
{
|
|
if (dialog != nullptr && dialog->is_open())
|
|
dialog->push_message(data);
|
|
}
|
|
|
|
void PluginWebDialog::request_close(PluginWebDialog* dialog)
|
|
{
|
|
if (dialog != nullptr)
|
|
dialog->Close();
|
|
}
|
|
|
|
void PluginWebDialog::destroy_for_plugin(PluginWebDialog* dialog)
|
|
{
|
|
if (dialog == nullptr)
|
|
return;
|
|
|
|
// Forced plugin teardown must not invoke Python close callbacks. End a modal
|
|
// loop first, otherwise destroying the window can leave ShowModal() running.
|
|
if (dialog->IsModal()) {
|
|
dialog->m_open = false;
|
|
dialog->EndModal(wxID_CANCEL);
|
|
}
|
|
dialog->Destroy();
|
|
}
|
|
|
|
void PluginWebDialog::on_bootstrap_event(wxWebViewEvent& event)
|
|
{
|
|
// The first bootstrap load (or its error) triggers the swap to plugin HTML;
|
|
// the resulting plugin-page load is ignored (guarded by m_content_loaded).
|
|
load_plugin_content();
|
|
event.Skip();
|
|
}
|
|
|
|
void PluginWebDialog::load_plugin_content()
|
|
{
|
|
if (m_content_loaded)
|
|
return;
|
|
m_content_loaded = true;
|
|
if (wxWebView* wv = browser())
|
|
wv->SetPage(wxString::FromUTF8(m_html), plugin_web::content_base_url());
|
|
}
|
|
|
|
void PluginWebDialog::on_script_message(const nlohmann::json& payload)
|
|
{
|
|
if (payload.value("channel", std::string()) == "orca") {
|
|
const std::string kind = payload.value("kind", std::string());
|
|
const nlohmann::json data = payload.contains("data") ? payload["data"] : nlohmann::json();
|
|
if (kind == "message") {
|
|
if (m_on_message)
|
|
m_on_message(data);
|
|
} else if (kind == "submit") {
|
|
finish(true, data);
|
|
} else if (kind == "close") {
|
|
finish(false, nlohmann::json());
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Fall back to the shared shell commands (e.g. "close_page").
|
|
handle_common_script_command(payload);
|
|
}
|
|
|
|
void PluginWebDialog::push_message(const nlohmann::json& data)
|
|
{
|
|
if (!m_open)
|
|
return;
|
|
nlohmann::json envelope;
|
|
envelope["data"] = data;
|
|
call_web_handler(envelope, wxT("__orcaDispatch"));
|
|
}
|
|
|
|
void PluginWebDialog::finish(bool submitted, const nlohmann::json& data)
|
|
{
|
|
if (!m_open)
|
|
return;
|
|
m_open = false;
|
|
if (submitted) {
|
|
m_result = data;
|
|
fire_submit(data);
|
|
} else {
|
|
m_result.reset();
|
|
fire_close();
|
|
}
|
|
|
|
if (IsModal())
|
|
EndModal(submitted ? wxID_OK : wxID_CANCEL);
|
|
else
|
|
Close();
|
|
}
|
|
|
|
void PluginWebDialog::on_close_window(wxCloseEvent&)
|
|
{
|
|
if (!m_open) {
|
|
// finish() already dispatched submit/close and requested the close.
|
|
// Modeless windows still need to be destroyed after that request.
|
|
if (!IsModal())
|
|
Destroy();
|
|
return;
|
|
}
|
|
|
|
m_open = false;
|
|
m_result.reset();
|
|
fire_close();
|
|
if (IsModal()) {
|
|
EndModal(wxID_CANCEL);
|
|
return;
|
|
}
|
|
Destroy();
|
|
}
|
|
|
|
void PluginWebDialog::fire_submit(const nlohmann::json& data)
|
|
{
|
|
if (m_on_submit) {
|
|
SubmitHandler cb = std::move(m_on_submit);
|
|
cb(data);
|
|
}
|
|
}
|
|
|
|
void PluginWebDialog::fire_close()
|
|
{
|
|
if (m_close_fired)
|
|
return;
|
|
m_close_fired = true;
|
|
if (m_on_close) {
|
|
CloseHandler cb = m_on_close;
|
|
m_on_close = nullptr;
|
|
cb();
|
|
}
|
|
}
|
|
|
|
}} // namespace Slic3r::GUI
|