mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-09 10:16:50 +00:00
unify orca web dialog style
This commit is contained in:
@@ -407,6 +407,12 @@ void WebView::RecreateAll()
|
||||
for (auto webView : g_webviews) {
|
||||
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
|
||||
Slic3r::GUI::wxGetApp().get_bbl_client_version(), dark ? "dark" : "light", language_code.mb_str()));
|
||||
webView->Reload();
|
||||
// A host-themed WebViewHostDialog re-themes in place (no reload). If it handles
|
||||
// the event, skip the reload; legacy pages fall through and reload as before
|
||||
// (their own dark.css swap re-themes them on reload).
|
||||
wxCommandEvent evt(EVT_WEBVIEW_RECREATED);
|
||||
evt.SetEventObject(webView);
|
||||
if (!webView->GetEventHandler()->ProcessEvent(evt))
|
||||
webView->Reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#define slic3r_GUI_WebView_hpp_
|
||||
|
||||
#include <wx/webview.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
wxDECLARE_EVENT(EVT_WEBVIEW_RECREATED, wxCommandEvent);
|
||||
|
||||
class WebView
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "WebView.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Widgets/StateColor.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -10,8 +12,104 @@
|
||||
#include <wx/log.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
// CSS "#rrggbb" for a wxColour (portable accessor used throughout the codebase).
|
||||
std::string css_color(const wxColour& c) { return c.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); }
|
||||
|
||||
// "dark"/"light" for the live app theme — the value of both data-orca-theme and color-scheme.
|
||||
std::string host_theme_name() { return wxGetApp().dark_mode() ? "dark" : "light"; }
|
||||
|
||||
// The host theme "contract": CSS custom properties filled from the LIVE app theme,
|
||||
// plus color-scheme. Consumed by resources/web/dialog/css/theme.css and by plugin
|
||||
// content. Variables only — no element styling — so it never fights a page's CSS.
|
||||
std::string host_theme_vars_css()
|
||||
{
|
||||
GUI_App& app = wxGetApp();
|
||||
const wxColour bg = app.get_window_default_clr();
|
||||
const wxColour fg = app.get_label_clr_default();
|
||||
const wxColour muted = app.get_label_clr_sys();
|
||||
const wxColour border = app.get_highlight_default_clr();
|
||||
const wxColour accent = StateColor::darkModeColorFor(wxColour("#009688"));
|
||||
std::string font = app.normal_font().GetFaceName().ToStdString();
|
||||
// Strip characters that could break out of the CSS value / <style> block.
|
||||
font.erase(std::remove_if(font.begin(), font.end(), [](char c) {
|
||||
return c == '\'' || c == '"' || c == '<' || c == '>' || c == '{' || c == '}' || c == ';';
|
||||
}),
|
||||
font.end());
|
||||
|
||||
std::string s;
|
||||
s += ":root{";
|
||||
s += "--orca-bg:" + css_color(bg) + ";";
|
||||
s += "--orca-fg:" + css_color(fg) + ";";
|
||||
s += "--orca-muted:" + css_color(muted) + ";";
|
||||
s += "--orca-border:" + css_color(border) + ";";
|
||||
s += "--orca-accent:" + css_color(accent) + ";";
|
||||
s += "--orca-accent-fg:#ffffff;";
|
||||
s += "--orca-font:" + (font.empty() ? std::string() : "'" + font + "',") +
|
||||
"system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;";
|
||||
s += "color-scheme:" + host_theme_name() + ";";
|
||||
s += "}";
|
||||
return s;
|
||||
}
|
||||
|
||||
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
|
||||
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
|
||||
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
|
||||
std::string host_theme_user_script()
|
||||
{
|
||||
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
|
||||
return WebViewHostDialog::document_start_injector(
|
||||
style, "orca-host-theme-vars", "afterbegin",
|
||||
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
|
||||
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
|
||||
}
|
||||
|
||||
// JS to re-theme an already-loaded document live (no reload): replace the injected
|
||||
// style's contents and update data-orca-theme. Everything downstream (theme.css
|
||||
// tokens, plugin element defaults, page layout) re-cascades from these values.
|
||||
std::string host_theme_apply_js()
|
||||
{
|
||||
const std::string vars_literal = nlohmann::json(host_theme_vars_css()).dump();
|
||||
const std::string theme = host_theme_name();
|
||||
return "(function(){var css=" + vars_literal + ";var theme=\"" + theme + "\";" + R"JS(
|
||||
var el=document.getElementById('orca-host-theme-vars');
|
||||
if(el){el.textContent=css;}
|
||||
else if(document.head){document.head.insertAdjacentHTML('afterbegin','<style id="orca-host-theme-vars"></style>');var e2=document.getElementById('orca-host-theme-vars');if(e2)e2.textContent=css;}
|
||||
if(document.documentElement)
|
||||
document.documentElement.setAttribute('data-orca-theme',theme);
|
||||
})();)JS";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string WebViewHostDialog::document_start_injector(const std::string& markup,
|
||||
const char* dom_id,
|
||||
const char* position,
|
||||
const std::string& prelude,
|
||||
const std::string& on_inject)
|
||||
{
|
||||
const std::string literal = nlohmann::json(markup).dump();
|
||||
std::string s;
|
||||
s += "(function(){";
|
||||
s += prelude;
|
||||
s += "var css=" + literal + ";";
|
||||
s += "function inject(){";
|
||||
s += "var root=document.head||document.documentElement;if(!root)return false;";
|
||||
s += "if(!document.getElementById('" + std::string(dom_id) + "'))root.insertAdjacentHTML('" +
|
||||
std::string(position) + "',css);";
|
||||
s += on_inject;
|
||||
s += "return true;}";
|
||||
s += "if(inject())return;";
|
||||
s += "var obs=new MutationObserver(function(){if(inject())obs.disconnect();});";
|
||||
s += "obs.observe(document,{childList:true});})();";
|
||||
return s;
|
||||
}
|
||||
|
||||
WebViewHostDialog::WebViewHostDialog(wxWindow* parent,
|
||||
wxWindowID id,
|
||||
const wxString& title,
|
||||
@@ -49,6 +147,11 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path,
|
||||
|
||||
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewHostDialog::on_script_message_event, this, m_browser->GetId());
|
||||
|
||||
// Inject the shared host theme contract BEFORE the first load so the page paints in
|
||||
// the app theme with no flash, and re-theme live when the app theme toggles.
|
||||
register_theme_user_scripts();
|
||||
m_browser->Bind(EVT_WEBVIEW_RECREATED, &WebViewHostDialog::on_webview_recreated, this);
|
||||
|
||||
load_url(target_url);
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
return true;
|
||||
@@ -130,4 +233,33 @@ void WebViewHostDialog::on_script_message_event(wxWebViewEvent& event)
|
||||
}
|
||||
}
|
||||
|
||||
void WebViewHostDialog::register_theme_user_scripts()
|
||||
{
|
||||
if (!m_browser)
|
||||
return;
|
||||
// Added once, at creation. Deliberately no RemoveAllUserScripts() here: the "wx"
|
||||
// script message handler is registered separately (AddScriptMessageHandler), but on
|
||||
// some backends RemoveAllUserScripts() drops it too, which would break
|
||||
// window.wx.postMessage / HandleStudio. Live re-theme goes through apply_theme_live().
|
||||
m_browser->AddUserScript(wxString::FromUTF8(host_theme_user_script()));
|
||||
add_user_scripts();
|
||||
}
|
||||
|
||||
void WebViewHostDialog::apply_theme_live()
|
||||
{
|
||||
if (!m_browser)
|
||||
return;
|
||||
// Update the already-loaded document in place (no reload, no flash) by rewriting the
|
||||
// injected :root variables + data-orca-theme; the whole cascade re-flows from these.
|
||||
// The document-start script keeps the creation-time theme for any later reload, and
|
||||
// these dialogs are not reloaded on a theme toggle (see WebView::RecreateAll).
|
||||
run_script(wxString::FromUTF8(host_theme_apply_js()));
|
||||
}
|
||||
|
||||
void WebViewHostDialog::on_webview_recreated(wxCommandEvent&)
|
||||
{
|
||||
// Handled: do NOT Skip(), so WebView::RecreateAll skips the redundant reload.
|
||||
apply_theme_live();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -33,6 +33,20 @@ public:
|
||||
bool run_script(const wxString& script);
|
||||
void call_web_handler(const nlohmann::json& payload, const wxString& handler = wxT("HandleStudio"));
|
||||
|
||||
// Wraps `markup` (an HTML fragment, usually a <style> block) in a document-start user
|
||||
// script that inserts it once — guarded by element id `dom_id`, at `position` (an
|
||||
// insertAdjacentHTML target such as "afterbegin"/"beforeend") — retrying via a
|
||||
// MutationObserver until a root node exists. On WebView2 a document-start script can run
|
||||
// before <html> exists (document.head and document.documentElement both null), so a bare
|
||||
// insert would throw and silently never apply. `prelude` is emitted once before the
|
||||
// injector (extra var/flag declarations); `on_inject` runs inside inject() after each
|
||||
// successful insert. Both default to empty.
|
||||
static std::string document_start_injector(const std::string& markup,
|
||||
const char* dom_id,
|
||||
const char* position,
|
||||
const std::string& prelude = {},
|
||||
const std::string& on_inject = {});
|
||||
|
||||
protected:
|
||||
wxWebView* browser() const { return m_browser; }
|
||||
|
||||
@@ -45,8 +59,24 @@ protected:
|
||||
virtual void on_script_message_parse_error(const wxString& payload, const std::exception& error);
|
||||
virtual bool append_language_to_url() const { return true; }
|
||||
|
||||
// Registers all document-start user scripts: the shared host theme contract first,
|
||||
// then subclass scripts from add_user_scripts(). Called ONCE, at creation. Live
|
||||
// re-theme goes through apply_theme_live() (RunScript), not a re-registration —
|
||||
// calling this again would append duplicate scripts.
|
||||
void register_theme_user_scripts();
|
||||
|
||||
// Subclasses override to add page-specific document-start user scripts (e.g. the
|
||||
// plugin bridge / unstyled-content defaults). Called AFTER the theme contract is
|
||||
// added, by register_theme_user_scripts(). Default: none.
|
||||
virtual void add_user_scripts() {}
|
||||
|
||||
// Pushes the current app theme into the already-loaded document without a reload
|
||||
// (updates the injected :root variables and the data-orca-theme attribute).
|
||||
void apply_theme_live();
|
||||
|
||||
private:
|
||||
void on_script_message_event(wxWebViewEvent& event);
|
||||
void on_webview_recreated(wxCommandEvent& event);
|
||||
|
||||
wxWebView* m_browser{nullptr};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user