From 5d8aa9610ae9c874c91f929c09e7d8415649de6f Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 5 Jul 2026 01:17:17 +0800 Subject: [PATCH] unify orca web dialog style --- .../web/dialog/ExportPresetDialog/index.html | 2 +- .../web/dialog/ExportPresetDialog/styles.css | 26 +--- resources/web/dialog/PluginsDialog/index.html | 2 +- resources/web/dialog/PluginsDialog/styles.css | 37 +---- .../web/dialog/PresetBundleDialog/index.html | 2 +- .../web/dialog/PresetBundleDialog/styles.css | 23 --- .../web/dialog/TerminalDialog/index.html | 2 +- resources/web/dialog/TerminalDialog/index.js | 58 ++++--- .../web/dialog/TerminalDialog/styles.css | 20 +-- resources/web/dialog/css/dark.css | 116 -------------- resources/web/dialog/css/theme.css | 100 ++++++++++++ resources/web/dialog/js/globalapi.js | 2 +- src/slic3r/GUI/ExportPresetBundleDialog.cpp | 112 +++---------- src/slic3r/GUI/ExportPresetBundleDialog.hpp | 13 +- src/slic3r/GUI/PluginWebDialog.cpp | 135 +++++----------- src/slic3r/GUI/PluginWebDialog.hpp | 1 + src/slic3r/GUI/PresetBundleDialog.cpp | 147 +++++------------- src/slic3r/GUI/PresetBundleDialog.hpp | 12 +- src/slic3r/GUI/Widgets/WebView.cpp | 8 +- src/slic3r/GUI/Widgets/WebView.hpp | 3 + src/slic3r/GUI/Widgets/WebViewHostDialog.cpp | 132 ++++++++++++++++ src/slic3r/GUI/Widgets/WebViewHostDialog.hpp | 30 ++++ 22 files changed, 439 insertions(+), 544 deletions(-) delete mode 100644 resources/web/dialog/css/dark.css create mode 100644 resources/web/dialog/css/theme.css diff --git a/resources/web/dialog/ExportPresetDialog/index.html b/resources/web/dialog/ExportPresetDialog/index.html index e46147f236..8d262b7af9 100644 --- a/resources/web/dialog/ExportPresetDialog/index.html +++ b/resources/web/dialog/ExportPresetDialog/index.html @@ -7,7 +7,7 @@ - + diff --git a/resources/web/dialog/ExportPresetDialog/styles.css b/resources/web/dialog/ExportPresetDialog/styles.css index fd4d14946b..78bf8d6f27 100644 --- a/resources/web/dialog/ExportPresetDialog/styles.css +++ b/resources/web/dialog/ExportPresetDialog/styles.css @@ -1,23 +1,11 @@ :root { - --cbr-border-color: #d2d2d7; - --cbr-header-bg: #f6f7f9; - --cbr-panel-bg: #ffffff; - --cbr-input-bg: #ffffff; - --cbr-input-focus-bg: #f2f8f7; - --cbr-label-color: #7b7b84; - --cbr-icon-color: #75757f; -} - -@media (prefers-color-scheme: dark) { - :root { - --cbr-border-color: #4a4a51; - --cbr-header-bg: #2f2f34; - --cbr-panel-bg: #2d2d31; - --cbr-input-bg: #2d2d31; - --cbr-input-focus-bg: #3b3b41; - --cbr-label-color: #b9b9bc; - --cbr-icon-color: #b9b9bc; - } + --cbr-border-color: var(--border); + --cbr-header-bg: var(--panel); + --cbr-panel-bg: var(--bg); + --cbr-input-bg: var(--bg); + --cbr-input-focus-bg: var(--row-hover); + --cbr-label-color: var(--muted); + --cbr-icon-color: var(--muted); } .cbr-browser-container { diff --git a/resources/web/dialog/PluginsDialog/index.html b/resources/web/dialog/PluginsDialog/index.html index 916eae3232..5b647f3d3b 100644 --- a/resources/web/dialog/PluginsDialog/index.html +++ b/resources/web/dialog/PluginsDialog/index.html @@ -7,7 +7,7 @@ - + diff --git a/resources/web/dialog/PluginsDialog/styles.css b/resources/web/dialog/PluginsDialog/styles.css index acd567d450..ce9d4f3a3d 100644 --- a/resources/web/dialog/PluginsDialog/styles.css +++ b/resources/web/dialog/PluginsDialog/styles.css @@ -1,36 +1,3 @@ -:root { - --bg: #ffffff; - --panel: #ffffff; - --border: #d8d8d8; - --border-strong: #e6e6e6; - --col-sep: #e1e1e1; - --text: #1f2328; - --muted: #6f7782; - --row-hover: #f7f9fb; - --row-selected: #eaf2ff; - --row-selected-outline: #b7d0ff; - --border-soft: #f0f0f0; - --danger: #b42318; - --ok: #137333; - --warn: #a15c00; - --plugin-status-danger: var(--danger); - --plugin-status-ok: var(--ok); - --plugin-status-warn: var(--warn); - --plugin-status-inactive: var(--muted); - --plugin-status-danger-bg: rgba(180, 35, 24, 0.12); - --plugin-status-ok-bg: rgba(19, 115, 51, 0.12); - --plugin-status-warn-bg: rgba(199, 122, 22, 0.12); - --plugin-status-inactive-bg: #edf1f5; - --plugin-status-inactive-text: #44505c; - --plugin-source-mine-bg: rgba(0, 137, 123, 0.12); - --plugin-source-mine-text: #00897b; - --plugin-source-neutral-bg: #edf1f5; - --plugin-source-neutral-text: #44505c; - --plugin-source-subscribed-bg: rgba(37, 99, 170, 0.12); - --plugin-source-subscribed-text: #2563aa; - --plugin-link-text: #00796b; -} - html, body { width: 100% !important; @@ -929,9 +896,9 @@ body { width: 12px; height: 12px; box-sizing: border-box; - border: 1px solid #4a4a51; + border: 1px solid var(--border); border-radius: 2px; - background: #2d2d31; + background: var(--bg); } .plugin-checkbox-input:checked + .plugin-checkbox-mark { diff --git a/resources/web/dialog/PresetBundleDialog/index.html b/resources/web/dialog/PresetBundleDialog/index.html index db0b45eeb1..6a2d39ae57 100644 --- a/resources/web/dialog/PresetBundleDialog/index.html +++ b/resources/web/dialog/PresetBundleDialog/index.html @@ -8,7 +8,7 @@ - + diff --git a/resources/web/dialog/PresetBundleDialog/styles.css b/resources/web/dialog/PresetBundleDialog/styles.css index aa6ad9138b..1baecde655 100644 --- a/resources/web/dialog/PresetBundleDialog/styles.css +++ b/resources/web/dialog/PresetBundleDialog/styles.css @@ -1,26 +1,3 @@ -:root { - --bg: #ffffff; - --panel: #ffffff; - --border: #d8d8d8; - --border-strong: #e6e6e6; - --border-soft: #f0f0f0; - --col-sep: #e1e1e1; - - --text: #1f2328; - --row-hover: #f7f9fb; - --row-selected: #eaf2ff; - --row-selected-outline: #b7d0ff; - - --footer-bg: #fafafa; - --btn-bg: #ffffff; - --btn-border: #cccccc; - --btn-hover: #f0f0f0; - - --ctx-bg: #ffffff; - --ctx-border: #cccccc; - --ctx-hover: #efefef; -} - html, body { height: 100%; margin: 0; diff --git a/resources/web/dialog/TerminalDialog/index.html b/resources/web/dialog/TerminalDialog/index.html index 291910dbd7..5476b0ea51 100644 --- a/resources/web/dialog/TerminalDialog/index.html +++ b/resources/web/dialog/TerminalDialog/index.html @@ -7,7 +7,7 @@ - + diff --git a/resources/web/dialog/TerminalDialog/index.js b/resources/web/dialog/TerminalDialog/index.js index 3d2505f773..c4e09bcb64 100644 --- a/resources/web/dialog/TerminalDialog/index.js +++ b/resources/web/dialog/TerminalDialog/index.js @@ -1,6 +1,40 @@ let term = null; let fitAddon = null; +// ANSI palettes for the xterm theme — the standard VS Code dark/light sets, picked by +// data-orca-theme. Constant data, so defined once at module scope. +const ANSI_DARK = { + black:"#000000", red:"#cd3131", green:"#0dbc79", yellow:"#e5e510", blue:"#2472c8", + magenta:"#bc3fbc", cyan:"#11a8cd", white:"#e5e5e5", + brightBlack:"#666666", brightRed:"#f14c4c", brightGreen:"#23d18b", brightYellow:"#f5f543", + brightBlue:"#3b8eea", brightMagenta:"#d670d6", brightCyan:"#29b8db", brightWhite:"#ffffff" +}; +const ANSI_LIGHT = { + black:"#000000", red:"#cd3131", green:"#107c10", yellow:"#949800", blue:"#0451a5", + magenta:"#bc05bc", cyan:"#0598bc", white:"#555555", + brightBlack:"#8a8a8a", brightRed:"#cd3131", brightGreen:"#14ce14", brightYellow:"#b5ba00", + brightBlue:"#0451a5", brightMagenta:"#bc05bc", brightCyan:"#0598bc", brightWhite:"#a5a5a5" +}; + +// The xterm.js theme is a plain JS object (it cannot read CSS variables), so build it +// from the host contract: background/foreground come from the injected --orca-* colors; +// the ANSI palette is the fixed light or dark set chosen by data-orca-theme. +function XtermTheme() { + var cs = getComputedStyle(document.documentElement); + var bg = (cs.getPropertyValue('--orca-bg') || '#1e1e1e').trim(); + var fg = (cs.getPropertyValue('--orca-fg') || '#d4d4d4').trim(); + var dark = document.documentElement.getAttribute('data-orca-theme') !== 'light'; + return Object.assign({ background: bg, foreground: fg, cursor: fg }, dark ? ANSI_DARK : ANSI_LIGHT); +} + +// Re-apply the xterm theme when the host flips data-orca-theme (live re-theme). +function WatchXtermTheme() { + var obs = new MutationObserver(function () { + if (term) term.options.theme = XtermTheme(); + }); + obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-orca-theme'] }); +} + function OnInit() { if (typeof TranslatePage === "function") TranslatePage(); @@ -9,30 +43,12 @@ function OnInit() { cursorBlink: true, fontSize: 13, fontFamily: '"Cascadia Code", "Fira Code", "JetBrains Mono", monospace', - theme: { - background: "#1e1e1e", - foreground: "#d4d4d4", - cursor: "#ffffff", - black: "#000000", - red: "#cd3131", - green: "#0dbc79", - yellow: "#e5e510", - blue: "#2472c8", - magenta: "#bc3fbc", - cyan: "#11a8cd", - white: "#e5e5e5", - brightBlack: "#666666", - brightRed: "#f14c4c", - brightGreen: "#23d18b", - brightYellow: "#f5f543", - brightBlue: "#3b8eea", - brightMagenta: "#d670d6", - brightCyan: "#29b8db", - brightWhite: "#ffffff" - }, + theme: XtermTheme(), allowProposedApi: true }); + WatchXtermTheme(); + fitAddon = new (FitAddon.FitAddon || FitAddon)(); term.loadAddon(fitAddon); diff --git a/resources/web/dialog/TerminalDialog/styles.css b/resources/web/dialog/TerminalDialog/styles.css index aa69780457..64b4b3e5b5 100644 --- a/resources/web/dialog/TerminalDialog/styles.css +++ b/resources/web/dialog/TerminalDialog/styles.css @@ -7,8 +7,8 @@ html, body { height: 100%; overflow: hidden; - background: #1e1e1e; - color: #d4d4d4; + background: var(--bg); + color: var(--text); } .app { @@ -41,34 +41,34 @@ html, body { #cmd-input { flex: 1; padding: 6px 10px; - border: 1px solid #444; + border: 1px solid var(--border); border-radius: 4px; - background: #2d2d2d; - color: #d4d4d4; + background: var(--bg); + color: var(--text); font-family: inherit; font-size: 13px; outline: none; } #cmd-input:focus { - border-color: #666; + border-color: var(--orca-accent); } #run-btn { padding: 6px 20px; border: none; border-radius: 4px; - background: #0e639c; - color: #fff; + background: var(--orca-accent); + color: var(--orca-accent-fg); font-size: 13px; cursor: pointer; } #run-btn:hover { - background: #1177bb; + filter: brightness(1.1); } #run-btn:disabled { - background: #555; + opacity: .5; cursor: not-allowed; } diff --git a/resources/web/dialog/css/dark.css b/resources/web/dialog/css/dark.css deleted file mode 100644 index 6bec2895f4..0000000000 --- a/resources/web/dialog/css/dark.css +++ /dev/null @@ -1,116 +0,0 @@ -:root { - --bg: #1b1f24; - --panel: #242a31; - --border: #3a424d; - --border-strong: #3a424d; - --border-soft: #313843; - --col-sep: #3a424d; - - --text: #e6ebf0; - --muted: #b9c0c8; - --row-hover: #2b3340; - --row-selected: #244945; - --row-selected-outline: #00bfa5; - - --plugin-status-danger: #ff7b72; - --plugin-status-ok: #37c871; - --plugin-status-warn: #f0b45a; - --plugin-status-inactive: #b9c0c8; - --plugin-status-danger-bg: rgba(255, 123, 114, 0.16); - --plugin-status-ok-bg: rgba(55, 200, 113, 0.16); - --plugin-status-warn-bg: rgba(240, 180, 90, 0.18); - --plugin-status-inactive-bg: #36363b; - --plugin-status-inactive-text: #c7ccd2; - --plugin-source-mine-bg: rgba(0, 150, 136, 0.18); - --plugin-source-mine-text: #8de5d6; - --plugin-source-neutral-bg: #36363b; - --plugin-source-neutral-text: #c7ccd2; - --plugin-source-subscribed-bg: rgba(88, 166, 255, 0.18); - --plugin-source-subscribed-text: #8fc0f0; - --plugin-link-text: #62d8ca; - - --footer-bg: #20262d; - --btn-bg: #2a313a; - --btn-border: #4b5664; - --btn-hover: #333c47; - - --ctx-bg: #2a313a; - --ctx-border: #4b5664; - --ctx-hover: #3a4451; -} - -* -{ - color: #efeff0; - border-color: #B9B9BC; -} - -body -{ - background-color:#2D2D31; /* ORCA match background color */ - color: #efeff0; -} - -.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/ - background-color: #939594; -} - -.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/ - background: #161817; -} - -#Title div -{ - color: #009688; -} - -.search>input[type=text]{ - background-color:#2D2D31; -} - -/*---Checkboxes ORCA---*/ -input[type=checkbox]{ - background-color:#2D2D31; - border-color:#4A4A51; -} - -input[type=checkbox]:checked{ - background-color:#009688; -} - -/*-------Text------*/ - -.TextS1 -{ - -} - -.TextS2 -{ - color:#B9B9BC; -} - -/*---Policy---*/ -.TextArea1 -{ - background-color: #4A4A51; - color: #BEBEC0; -} - -/*----Region---*/ -.RegionItem:hover -{ - background-color:#4C4C55; -} - -.RegionSelected:hover -{ - background-color:#009688; - color: #fff; -} - -/*----Menu----*/ -#Title div.TitleUnselected -{ - color: #BEBEC0; -} diff --git a/resources/web/dialog/css/theme.css b/resources/web/dialog/css/theme.css new file mode 100644 index 0000000000..665f0c941a --- /dev/null +++ b/resources/web/dialog/css/theme.css @@ -0,0 +1,100 @@ +/* Shared theme for resources/web/dialog/* pages. + * + * App-matched roles are pulled from the host theme "contract" (:root{--orca-*}), + * injected from live C++ app colors by WebViewHostDialog. Web-only semantics (row + * selection, status/source badges, footer/button/context surfaces) have no native + * equivalent, so they are curated here for light and selected for dark by the + * [data-orca-theme="dark"] attribute the host sets — the single authoritative theme + * signal on all platforms (prefers-color-scheme is intentionally not used). + * + * This file is linked AFTER common.css so its element rules re-theme the shared chrome + * via variables (replacing the retired dark.css). It replaces each page's own :root + * palette and the dark.css . */ + +:root { + /* --- app-matched roles (from the injected contract) --- */ + --bg: var(--orca-bg); + --panel: var(--orca-bg); + --text: var(--orca-fg); + --muted: var(--orca-muted); + --border: var(--orca-border); + --border-strong: var(--orca-border); + --border-soft: var(--orca-border); + --col-sep: var(--orca-border); + --row-selected-outline: var(--orca-accent); + --main-color: var(--orca-accent); + --main-color-hover: var(--orca-accent); + --plugin-link-text: var(--orca-accent); + + /* --- web-only semantics: LIGHT --- */ + --row-hover: #f7f9fb; + --row-selected: #eaf2ff; + --plugin-status-danger: #b42318; + --plugin-status-ok: #137333; + --plugin-status-warn: #a15c00; + --plugin-status-inactive: var(--muted); + --plugin-status-danger-bg: rgba(180, 35, 24, 0.12); + --plugin-status-ok-bg: rgba(19, 115, 51, 0.12); + --plugin-status-warn-bg: rgba(199, 122, 22, 0.12); + --plugin-status-inactive-bg: #edf1f5; + --plugin-status-inactive-text: #44505c; + --plugin-source-mine-bg: rgba(0, 137, 123, 0.12); + --plugin-source-mine-text: #00897b; + --plugin-source-neutral-bg: #edf1f5; + --plugin-source-neutral-text: #44505c; + --plugin-source-subscribed-bg: rgba(37, 99, 170, 0.12); + --plugin-source-subscribed-text: #2563aa; + --footer-bg: #fafafa; + --btn-bg: #ffffff; + --btn-border: #cccccc; + --btn-hover: #f0f0f0; + --ctx-bg: #ffffff; + --ctx-border: #cccccc; + --ctx-hover: #efefef; +} + +:root[data-orca-theme="dark"] { + /* The app-injected border (var(--orca-border) ≈ #36363B in dark) sits almost on top of + the dark panels; lift the border roles a touch so dividers/outlines stay visible. */ + --border: #4a4a51; + --border-strong: #4a4a51; + --border-soft: #4a4a51; + --col-sep: #4a4a51; + --row-hover: #2b3340; + --row-selected: #244945; + --plugin-status-danger: #ff7b72; + --plugin-status-ok: #37c871; + --plugin-status-warn: #f0b45a; + --plugin-status-inactive: #b9c0c8; + --plugin-status-danger-bg: rgba(255, 123, 114, 0.16); + --plugin-status-ok-bg: rgba(55, 200, 113, 0.16); + --plugin-status-warn-bg: rgba(240, 180, 90, 0.18); + --plugin-status-inactive-bg: #36363b; + --plugin-status-inactive-text: #c7ccd2; + --plugin-source-mine-bg: rgba(0, 150, 136, 0.18); + --plugin-source-mine-text: #8de5d6; + --plugin-source-neutral-bg: #36363b; + --plugin-source-neutral-text: #c7ccd2; + --plugin-source-subscribed-bg: rgba(88, 166, 255, 0.18); + --plugin-source-subscribed-text: #8fc0f0; + --footer-bg: #20262d; + --btn-bg: #2a313a; + --btn-border: #4b5664; + --btn-hover: #333c47; + --ctx-bg: #2a313a; + --ctx-border: #4b5664; + --ctx-hover: #3a4451; +} + +/* Re-theme the shared common.css chrome through variables (replaces dark.css's + * hardcoded element overrides). Placed after common.css in every page's . */ +html { background: var(--bg); } +body { background: var(--bg); color: var(--text); } +#Content { color: var(--text); } +#Title div, #Title div.TitleUnselected { color: var(--orca-accent); } +.HyperLink { color: var(--orca-accent); } +.ZScrol::-webkit-scrollbar-thumb { background-color: var(--border); } +.ZScrol::-webkit-scrollbar-track { background: var(--bg); } +input[type="checkbox"] { background-color: var(--bg); border-color: var(--border); } +input[type="checkbox"]:checked { background-color: var(--orca-accent); border-color: var(--orca-accent); } +input[type="checkbox"]::before { box-shadow: inset 1em 1em var(--orca-accent-fg); } diff --git a/resources/web/dialog/js/globalapi.js b/resources/web/dialog/js/globalapi.js index 2463dbe537..f78f1e928b 100644 --- a/resources/web/dialog/js/globalapi.js +++ b/resources/web/dialog/js/globalapi.js @@ -336,4 +336,4 @@ function ExecuteDarkMode( DarkCssPath ) } } -SwitchDarkMode( "../css/dark.css" ); \ No newline at end of file +// Dialog pages are themed by the injected --orca-* contract + theme.css; the legacy dark.css poll is fully retired. \ No newline at end of file diff --git a/src/slic3r/GUI/ExportPresetBundleDialog.cpp b/src/slic3r/GUI/ExportPresetBundleDialog.cpp index 3c0f75d4b8..6d642ee1c1 100644 --- a/src/slic3r/GUI/ExportPresetBundleDialog.cpp +++ b/src/slic3r/GUI/ExportPresetBundleDialog.cpp @@ -1,5 +1,4 @@ #include "ExportPresetBundleDialog.hpp" -#include #include "GUI_App.hpp" #include "ConfigWizard.hpp" #include "I18N.hpp" @@ -12,17 +11,14 @@ #include #include #include -#include #include #include namespace Slic3r { namespace GUI { ExportPresetBundleDialog::ExportPresetBundleDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) - : DPIDialog(parent, id, _L("ExportPresetBundle"), pos, size, style) + : WebViewHostDialog(parent, id, _L("ExportPresetBundle"), pos, size, style) { - SetBackgroundColour(*wxWHITE); - SetMinSize(DESIGN_WINDOW_SIZE); Init(); wxGetApp().UpdateDlgDarkUI(this); } @@ -38,97 +34,36 @@ ExportPresetBundleDialog::~ExportPresetBundleDialog() } } -void ExportPresetBundleDialog::LoadUrl(wxString& url) -{ - if (!m_browser) - return; - BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " enter, url=" << url.ToStdString(); - WebView::LoadUrl(m_browser, url); - m_browser->SetFocus(); - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " exit"; -} - -void ExportPresetBundleDialog::on_dpi_changed(const wxRect& suggested_rect) { this->Refresh(); } - void ExportPresetBundleDialog::Init() { - wxString TargetUrl = from_u8( - (boost::filesystem::path(resources_dir()) / "web/dialog/ExportPresetDialog/index.html").make_preferred().string()); - wxString strlang = wxGetApp().current_language_code_safe(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", strlang=%1%") % into_u8(strlang); - if (strlang != "") - TargetUrl = wxString::Format("%s?lang=%s", std::string(TargetUrl.mb_str()), strlang); - TargetUrl = "file://" + TargetUrl; - - // Create the webview - m_browser = WebView::CreateWebView(this, TargetUrl); - if (m_browser == nullptr) { - wxLogError("Could not init m_browser"); - return; - } - - wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); - SetTitle(_L("Export Preset Bundle")); - SetSizer(topsizer); - topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); - - // Set a more sensible size for web browsing - wxSize pSize = FromDIP(wxSize(820, 660)); - SetSize(pSize); - int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL); - int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL); - int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0; - wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY); - Move(tmpPT); - - Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ExportPresetBundleDialog::OnScriptMessage, this, m_browser->GetId()); - - LoadUrl(TargetUrl); + create_webview("web/dialog/ExportPresetDialog/index.html", _L("Export Preset Bundle"), + wxSize(820, 660), wxSize(640, 640)); } -void ExportPresetBundleDialog::RunScript(const wxString& s) +void ExportPresetBundleDialog::on_script_message(const nlohmann::json& j) { - if (!m_browser) + if (handle_common_script_command(j)) return; - WebView::RunScript(m_browser, s); -} - -void ExportPresetBundleDialog::OnScriptMessage(wxWebViewEvent& e) -{ - try { - wxString strInput = e.GetString(); - BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;OnRecv:" << strInput.c_str(); - json j = json::parse(strInput.utf8_string()); - - wxString strCmd = j["command"]; - BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;Command:" << strCmd; - - if (strCmd == "close_page") { - this->EndModal(wxID_CANCEL); - } else if (strCmd == "request_export_preset_profile") { - InitExportData(); - OnRequestPresets(); - } else if (strCmd == "export_local") { - wxFileDialog dlg(this, _L("Save preset bundle"), "", "export.orca_bundle", "Orca Preset Bundle (*.orca_bundle)|*.orca_bundle", - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - wxString path; - wxString name; - if (dlg.ShowModal() == wxID_OK) { - path = dlg.GetPath(); - wxFileName file_name(path); - name = file_name.GetName(); - if (file_name.GetExt().empty()) { - file_name.SetExt("orca_bundle"); - path = file_name.GetFullPath(); - } + const std::string strCmd = j.value("command", ""); + if (strCmd == "request_export_preset_profile") { + InitExportData(); + OnRequestPresets(); + } else if (strCmd == "export_local") { + wxFileDialog dlg(this, _L("Save preset bundle"), "", "export.orca_bundle", + "Orca Preset Bundle (*.orca_bundle)|*.orca_bundle", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + wxString path; + wxString name; + if (dlg.ShowModal() == wxID_OK) { + path = dlg.GetPath(); + wxFileName file_name(path); + name = file_name.GetName(); + if (file_name.GetExt().empty()) { + file_name.SetExt("orca_bundle"); + path = file_name.GetFullPath(); } - OnExportData(path, name, j["data"]); } - - } catch (std::exception& e) { - BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;Error:" << e.what(); + OnExportData(path, name, j.value("data", json())); } } @@ -331,8 +266,7 @@ void ExportPresetBundleDialog::OnRequestPresets() } } - wxString strJS = wxString::Format("HandleStudio(%s)", wxString::FromUTF8(res.dump(-1, ' ', false, json::error_handler_t::ignore))); - wxGetApp().CallAfter([this, strJS] { RunScript(strJS); }); + call_web_handler(res); } void ExportPresetBundleDialog::OnExportData(const wxString& path, const wxString& filename, json data) diff --git a/src/slic3r/GUI/ExportPresetBundleDialog.hpp b/src/slic3r/GUI/ExportPresetBundleDialog.hpp index f83fc1b6f4..9340759c06 100644 --- a/src/slic3r/GUI/ExportPresetBundleDialog.hpp +++ b/src/slic3r/GUI/ExportPresetBundleDialog.hpp @@ -3,6 +3,7 @@ #include "GUI.hpp" #include "GUI_Utils.hpp" +#include "Widgets/WebViewHostDialog.hpp" #include "libslic3r/AppConfig.hpp" #include @@ -11,7 +12,6 @@ #include #include #include -#include namespace Slic3r { namespace GUI { @@ -37,7 +37,7 @@ enum ExportCase { CASE_COUNT, }; -class ExportPresetBundleDialog : public Slic3r::GUI::DPIDialog +class ExportPresetBundleDialog : public Slic3r::GUI::WebViewHostDialog { public: ExportPresetBundleDialog(wxWindow* parent, @@ -52,16 +52,12 @@ public: // Utilities bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; } bool recreate_GUI() const { return m_recreate_GUI; } - void on_dpi_changed(const wxRect& suggested_rect) override; void show_export_result(const ExportCase& e); void Init(); void InitExportData(); - // Webview - void LoadUrl(wxString& url); - void OnScriptMessage(wxWebViewEvent& e); - void RunScript(const wxString& s); + void on_script_message(const nlohmann::json& payload) override; void OnRequestPresets(); void OnExportData(const wxString& path, const wxString& name, json data); @@ -69,9 +65,6 @@ protected: bool m_seq_top_layer_only_changed{false}; bool m_recreate_GUI{false}; - // Webview - wxWebView* m_browser{nullptr}; - // Export Preset std::unordered_map m_printer_presets; // first: printer name, second: printer presets have same printer name std::unordered_map> diff --git a/src/slic3r/GUI/PluginWebDialog.cpp b/src/slic3r/GUI/PluginWebDialog.cpp index 234f0ccefe..b316cb1fe0 100644 --- a/src/slic3r/GUI/PluginWebDialog.cpp +++ b/src/slic3r/GUI/PluginWebDialog.cpp @@ -2,7 +2,6 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Widgets/StateColor.hpp" #include @@ -16,96 +15,37 @@ namespace Slic3r { namespace GUI { namespace { -// CSS "#rrggbb" for a wxColour (wxC2S_HTML_SYNTAX is the portable accessor used -// throughout the codebase for color->CSS). -std::string css_color(const wxColour& c) { return c.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); } - -// Build a "; - return s; -} - -// User script that prepends the host theme into the document, before the plugin's -// own "; + return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend"); } // Injected into every page at document start (before the plugin's own scripts). @@ -172,11 +112,8 @@ PluginWebDialog::PluginWebDialog(wxWindow* parent, if (wxWebView* wv = browser()) { wv->SetBackgroundColour(wxGetApp().get_window_default_clr()); - // Inject the host theme first so its "; + 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','');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 diff --git a/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp b/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp index ea657d2bc5..a9cae5b942 100644 --- a/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp +++ b/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp @@ -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