mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-20 07:22:35 +00:00
Add a Dockable HTML Panel API for Plugins
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.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
#include "PluginWebPanel.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Widgets/PluginWebContent.hpp"
|
||||
#include "Widgets/WebView.hpp"
|
||||
#include "Widgets/WebViewHostDialog.hpp"
|
||||
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
PluginWebPanel::PluginWebPanel(wxWindow* parent, const char* bridge_script)
|
||||
: wxPanel(parent, wxID_ANY)
|
||||
{
|
||||
SetBackgroundColour(wxGetApp().get_window_default_clr());
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
SetSizer(sizer);
|
||||
|
||||
// Never null: WebView::CreateWebView substitutes a placeholder view when no backend is available.
|
||||
const std::string bootstrap = (boost::filesystem::path(resources_dir()) / plugin_web::BOOTSTRAP_PAGE).make_preferred().string();
|
||||
m_browser = WebView::CreateWebView(this, wxString("file://") + from_u8(bootstrap));
|
||||
m_browser->SetBackgroundColour(GetBackgroundColour());
|
||||
m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::theme_user_script()));
|
||||
m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::plugin_defaults_user_script()));
|
||||
m_browser->AddUserScript(wxString::FromUTF8(bridge_script));
|
||||
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PluginWebPanel::on_load_event, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PluginWebPanel::on_load_event, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &PluginWebPanel::on_navigated, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PluginWebPanel::on_script_message, this);
|
||||
m_browser->Bind(EVT_WEBVIEW_RECREATED, &PluginWebPanel::on_webview_recreated, this);
|
||||
sizer->Add(m_browser, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
void PluginWebPanel::on_load_event(wxWebViewEvent& event)
|
||||
{
|
||||
const bool loaded = event.GetEventType() == wxEVT_WEBVIEW_LOADED;
|
||||
if (!m_content_loaded) {
|
||||
// The first bootstrap load (or its error) triggers the swap to the plugin HTML.
|
||||
m_content_loaded = true;
|
||||
load_page_html();
|
||||
} else if (!plugin_web::is_content_url(event.GetURL())) {
|
||||
// Every document but ours — a page a link loaded, an error page a backend substituted —
|
||||
// reports its own URL. Not ours to reload, but the app theme still applies to it.
|
||||
if (loaded)
|
||||
apply_theme();
|
||||
} else if (m_own_page_load) {
|
||||
m_own_page_load = false;
|
||||
// The document-start theme script keeps the theme the web view was created with, so bring
|
||||
// the swapped-in plugin page onto the app theme.
|
||||
if (loaded)
|
||||
apply_theme();
|
||||
} else if (loaded && m_content_navigated) {
|
||||
// WebKit reloads the SetPage base URL rather than the injected page, and the injected
|
||||
// document reports that same URL, so a load of it that is not the swap we started is a
|
||||
// browser reload and the plugin HTML has to be put back. A navigation that fails is reported
|
||||
// against the document that stayed, base URL and all, so the reload also has to have
|
||||
// committed; nothing commits for a failure. The Edge backend ignores the base URL, so nothing
|
||||
// matches there.
|
||||
load_page_html();
|
||||
}
|
||||
if (loaded)
|
||||
m_content_navigated = false;
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PluginWebPanel::on_navigated(wxWebViewEvent& event)
|
||||
{
|
||||
m_content_navigated = plugin_web::is_content_url(event.GetURL());
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PluginWebPanel::load_page_html()
|
||||
{
|
||||
if (const std::optional<std::string> html = page_html()) {
|
||||
m_own_page_load = true;
|
||||
m_browser->SetPage(wxString::FromUTF8(*html), plugin_web::content_base_url());
|
||||
}
|
||||
}
|
||||
|
||||
void PluginWebPanel::on_script_message(wxWebViewEvent& event)
|
||||
{
|
||||
const nlohmann::json payload = nlohmann::json::parse(event.GetString().utf8_string(), nullptr, false);
|
||||
if (!payload.is_object() || payload.value("channel", std::string()) != "orca")
|
||||
return;
|
||||
|
||||
const std::string kind = payload.value("kind", std::string());
|
||||
if (!on_page_message(kind, payload.contains("data") ? payload["data"] : nlohmann::json()))
|
||||
BOOST_LOG_TRIVIAL(warning) << "Plugin web panel ignored a window.orca '" << kind << "' call; this host does not support it";
|
||||
}
|
||||
|
||||
void PluginWebPanel::on_webview_recreated(wxCommandEvent&)
|
||||
{
|
||||
SetBackgroundColour(wxGetApp().get_window_default_clr());
|
||||
m_browser->SetBackgroundColour(GetBackgroundColour());
|
||||
Refresh();
|
||||
// Handled without Skip(), so WebView::RecreateAll() does not reload the plugin page.
|
||||
apply_theme();
|
||||
}
|
||||
|
||||
void PluginWebPanel::apply_theme()
|
||||
{
|
||||
WebView::RunScript(m_browser, wxString::FromUTF8(WebViewHostDialog::theme_apply_script()));
|
||||
}
|
||||
|
||||
void PluginWebPanel::post_to_page(const std::string& json)
|
||||
{
|
||||
WebView::RunScript(m_browser, wxString::Format(
|
||||
"(function dispatch(payload, attempts) {\n"
|
||||
" if (typeof window.__orcaDispatch === 'function') { window.__orcaDispatch(payload); return; }\n"
|
||||
" if (attempts < 100) window.setTimeout(function() { dispatch(payload, attempts + 1); }, 25);\n"
|
||||
"})({data: %s}, 0);",
|
||||
wxString::FromUTF8(json)));
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
Reference in New Issue
Block a user