mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-22 16:32:40 +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 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.
217 lines
6.6 KiB
C++
217 lines
6.6 KiB
C++
#include "WebDialog.hpp"
|
|
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/Widgets/WebHosting.hpp"
|
|
|
|
#include <wx/event.h>
|
|
|
|
#include <utility>
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
WebDialog::WebDialog(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(web_hosting::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, &WebDialog::on_bootstrap_event, this, wv->GetId());
|
|
Bind(wxEVT_WEBVIEW_ERROR, &WebDialog::on_bootstrap_event, this, wv->GetId());
|
|
Bind(wxEVT_WEBVIEW_NAVIGATED, &WebDialog::on_navigated, this, wv->GetId());
|
|
}
|
|
Bind(wxEVT_CLOSE_WINDOW, &WebDialog::on_close_window, this);
|
|
}
|
|
|
|
void WebDialog::add_user_scripts()
|
|
{
|
|
if (wxWebView* wv = browser()) {
|
|
wv->AddUserScript(wxString::FromUTF8(WebViewHostDialog::element_defaults_user_script()));
|
|
wv->AddUserScript(wxString::FromUTF8(web_hosting::orca_bridge_script()));
|
|
}
|
|
}
|
|
|
|
WebDialog::~WebDialog()
|
|
{
|
|
// 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 WebDialog::post_message(WebDialog* dialog, const nlohmann::json& data)
|
|
{
|
|
if (dialog != nullptr && dialog->is_open())
|
|
dialog->push_message(data);
|
|
}
|
|
|
|
void WebDialog::request_close(WebDialog* dialog)
|
|
{
|
|
if (dialog != nullptr)
|
|
dialog->Close();
|
|
}
|
|
|
|
void WebDialog::destroy_silently(WebDialog* 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 WebDialog::on_bootstrap_event(wxWebViewEvent& event)
|
|
{
|
|
const bool loaded = event.GetEventType() == wxEVT_WEBVIEW_LOADED;
|
|
// The first bootstrap load (or its error) triggers the swap to plugin HTML.
|
|
if (!m_content_loaded)
|
|
load_page_html();
|
|
// WebKit reloads the SetPage base URL, so a committed load of it that we did not start is a reload.
|
|
// A failed navigation is reported against the page that stayed but never commits. Edge ignores the
|
|
// base URL and restores SetPage content itself, so nothing matches there.
|
|
else if (web_hosting::is_content_url(event.GetURL())) {
|
|
if (m_own_page_load)
|
|
m_own_page_load = false;
|
|
else if (loaded && m_content_navigated)
|
|
load_page_html();
|
|
}
|
|
if (loaded)
|
|
m_content_navigated = false;
|
|
event.Skip();
|
|
}
|
|
|
|
void WebDialog::on_navigated(wxWebViewEvent& event)
|
|
{
|
|
m_content_navigated = web_hosting::is_content_url(event.GetURL());
|
|
event.Skip();
|
|
}
|
|
|
|
void WebDialog::load_page_html()
|
|
{
|
|
m_content_loaded = true;
|
|
if (wxWebView* wv = browser()) {
|
|
m_own_page_load = true;
|
|
wv->SetPage(wxString::FromUTF8(m_html), web_hosting::content_base_url());
|
|
}
|
|
}
|
|
|
|
void WebDialog::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 WebDialog::push_message(const nlohmann::json& data)
|
|
{
|
|
if (!m_open)
|
|
return;
|
|
nlohmann::json envelope;
|
|
envelope["data"] = data;
|
|
call_web_handler(envelope, wxT("__orcaDispatch"));
|
|
}
|
|
|
|
void WebDialog::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 WebDialog::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 WebDialog::fire_submit(const nlohmann::json& data)
|
|
{
|
|
if (m_on_submit) {
|
|
SubmitHandler cb = std::move(m_on_submit);
|
|
cb(data);
|
|
}
|
|
}
|
|
|
|
void WebDialog::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
|