mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-03 22:01:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4710ec274f | ||
|
|
a6dbf2502d | ||
|
|
8a6377f087 | ||
|
|
c86e33db6d | ||
|
|
019f6677b9 | ||
|
|
561c9e38e0 | ||
|
|
92de8d3eae |
@@ -30,8 +30,9 @@ how you read the wx docs:
|
||||
Look things up in the source the app is built from — it beats memory, and 3.3 changed real behaviour:
|
||||
|
||||
```bash
|
||||
WX=$(find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets' | head -1)
|
||||
# macOS: deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets Linux: deps/build/dep_wxWidgets-prefix/...
|
||||
WX=$(find -L deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets' 2>/dev/null | head -1)
|
||||
# macOS: deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets Linux, Windows: deps/<tree>/dep_wxWidgets-prefix/...
|
||||
# -L follows a worktree's deps/<tree> symlinked to the main checkout. Not a glob: zsh aborts on one that matches nothing.
|
||||
# If deps are not built: git clone --depth 1 -b v3.3.2 https://github.com/SoftFever/Orca-deps-wxWidgets
|
||||
grep -n "CaptureMouse" -A 30 $WX/interface/wx/window.h # documented contract (doxygen source)
|
||||
grep -rn "@onlyfor\|not implemented" $WX/interface/wx/popupwin.h # documented platform limits
|
||||
@@ -40,6 +41,13 @@ grep -n "IsDark" $WX/docs/changes.txt # what changed in
|
||||
grep -n "NotifyCaptureLost" -r $WX/src/osx $WX/src/gtk $WX/src/msw # what each port actually does
|
||||
```
|
||||
|
||||
On Windows these lookups are bash: run them from Git Bash. PowerShell has no `grep`, and its `find` is
|
||||
Windows' text-search `find.exe`. To locate the wx tree from PowerShell:
|
||||
|
||||
```powershell
|
||||
$WX = Resolve-Path deps\*\dep_wxWidgets-prefix\src\dep_wxWidgets, deps\*\*\dep_wxWidgets-prefix\src\dep_wxWidgets -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Path
|
||||
```
|
||||
|
||||
`interface/wx/<class>.h` is the documentation; `src/common` holds shared behaviour and
|
||||
`src/{msw,osx,gtk,unix,generic}` the per-port implementation. When the docs and the source disagree,
|
||||
the source is what runs — the references mark such facts **[source]**. Orca-side design docs live in
|
||||
|
||||
@@ -331,6 +331,10 @@ OrcaSlicer: integer spinners are `::SpinInput` ([below](#spininput)); progress b
|
||||
Contract:
|
||||
- A page must be created with the book as its parent and added once; the book owns and deletes it
|
||||
(`interface/wx/bookctrl.h:253-254, 273`). `RemovePage` detaches without deleting, and you then own it (`:324-330`).
|
||||
- Removing the selected page selects the page before it (the new first page if it was first) through `SetSelection`,
|
||||
so that page is shown and PAGE_CHANGING/CHANGED are sent; removing a page before the selection only shifts the
|
||||
index. This is `wxBookCtrlBase::DoSetSelectionAfterRemoval` (`src/common/bookctrl.cpp:477-495` **[source]**),
|
||||
called from `DoRemovePage` by `wxSimplebook`, `wxChoicebook`, `wxListbook`, `wxToolbook` and Orca's `Notebook`.
|
||||
- `GetSelection()` inside a `PAGE_CHANGED` handler may return the old or the new page depending on the platform; use
|
||||
`event.GetSelection()` (`interface/wx/bookctrl.h:160-166`).
|
||||
- `wxSimplebook` has no UI; switch with `ChangeSelection()`. `SetSelection()` sends PAGE_CHANGING/CHANGED
|
||||
|
||||
@@ -10,7 +10,7 @@ wx asserts are compiled out in Orca (`wxDEBUG_LEVEL=0`), so every misuse below t
|
||||
an assert fails silently. "GTK" means wxGTK3, Orca's Linux default (X11 and Wayland); GTK2 is only
|
||||
an opt-out build (`-DDEP_WX_GTK3=OFF`), noted where it differs. Paths starting `interface/`,
|
||||
`include/`, `src/`, `docs/` are in the wx tree
|
||||
(`find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets'`); Orca paths are
|
||||
(located as in `SKILL.md` §Ground truth); Orca paths are
|
||||
relative to `src/slic3r/GUI/`.
|
||||
|
||||
Contents: [Rules](#rules) · [Mouse capture](#mouse-capture) · [Mouse events](#mouse-events) ·
|
||||
|
||||
@@ -332,7 +332,9 @@ indices: pages come and go per printer and per feature flag.
|
||||
pages depending on the printer and on `use_printer_agents`; a removed page stays registered but is
|
||||
not prebuilt (its `LazyPage::in_book()` is false).
|
||||
- Plugin pages are appended by `PluginPages::initialize` (`plugin/host/PluginPages.hpp`) with
|
||||
namespaced ids (`plugin.<plugin_key>.<name>`) that cannot collide with `TAB_ID_*`.
|
||||
namespaced ids (`plugin.<plugin_key>.<name>`) that cannot collide with `TAB_ID_*`. Each is a
|
||||
`LazyPage<PluginPage>` with order −1, destroyed when its capability goes away.
|
||||
→ [Deferred construction](#deferred-construction-lazy-lazypage-stagedbuild-idlescheduler)
|
||||
|
||||
### Preset tabs
|
||||
|
||||
@@ -529,6 +531,32 @@ the main frame does nothing to a panel after creating it.
|
||||
m_idle.add(m_diff_dialog);
|
||||
```
|
||||
Cite: `IdleScheduler::tick`, `docs/HLSD/deferred-page-construction.md`.
|
||||
- **Rule:** A lazy page that can be destroyed while the main frame lives takes a negative order and
|
||||
stays out of `m_lazy_pages`.
|
||||
**Why:** `m_lazy_pages` and `PrebuildQueue` hold raw `LazyBase*` and nothing removes one
|
||||
(`PrebuildQueue` has only `add` and `clear`). The queue calls `pending()` on every task each slice,
|
||||
and `prebuild_pages_when_idle` reads every entry of `m_lazy_pages`, so a page destroyed while still
|
||||
listed can be read after it is freed. A page only taken out of the book is fine: it stays registered
|
||||
and its `pending()` is false (`MainFrame::show_device`).
|
||||
```cpp
|
||||
// Right (PluginPages::create_page): order -1, and no m_lazy_pages.push_back
|
||||
auto* page = new GUI::LazyPage<PluginPage>(m_parent, name, -1, [capability](wxWindow* parent) {
|
||||
return new PluginPage(parent, capability);
|
||||
});
|
||||
```
|
||||
Cite: `PluginPages::create_page`, `PluginPages::remove_page`.
|
||||
- **Rule:** Remove several lazy pages from a book left to right.
|
||||
**Why:** removing the selected page selects and shows the page before it
|
||||
(`references/controls-dataview.md` §Book controls), and showing an unbuilt `LazyPage` while the frame
|
||||
is shown builds it. In any other order the page before the selected one can be one removed next,
|
||||
built only to be destroyed; left to right it is one that stays (unless the selected page is the
|
||||
book's first).
|
||||
```cpp
|
||||
// Right (PluginPages::shutdown): m_order is the tabs' left-to-right order
|
||||
for (const PluginCapabilityId& id : std::vector<PluginCapabilityId>(m_order))
|
||||
remove_page(id);
|
||||
```
|
||||
Cite: `PluginPages::shutdown`, `PluginPages::relayout`, `PluginPages::on_plugin_deregister`.
|
||||
|
||||
## Plater and Sidebar
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ to write a custom control on the wx side and how to author an Orca widget on the
|
||||
`StaticBox`/`StateHandler` foundation. Read it before writing or reviewing any `wxEVT_PAINT`
|
||||
handler, `render`/`doRender` method, `messureSize`, or a new class under `src/slic3r/GUI/Widgets/`.
|
||||
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets`).
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
|
||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every
|
||||
wx assert below is compiled out and `wxCHECK*` returns silently, so paint misuse shows up only as
|
||||
wrong, missing or stale pixels, never as an assert dialog.
|
||||
|
||||
@@ -78,8 +78,9 @@ Contents: [Rules](#rules) · [The wx build Orca uses](#the-wx-build-orca-uses)
|
||||
`references/colours-dark-mode.md`.
|
||||
- The checked-out source is the tree that every wx citation in this skill refers to:
|
||||
`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets` on macOS and
|
||||
`deps/build/dep_wxWidgets-prefix/src/dep_wxWidgets` on Linux. Find it with
|
||||
`find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets'`. On macOS its
|
||||
`deps/<tree>/dep_wxWidgets-prefix/src/dep_wxWidgets` on Linux and Windows (`deps/build` for a
|
||||
release build; `build_win.bat` names the others). Locate it with the bash or PowerShell lookup in
|
||||
`SKILL.md` §Ground truth. On macOS its
|
||||
`src/osx/cocoa/colour.mm` already has the patch applied.
|
||||
- **Flatpak builds wx separately.** `deps/CMakeLists.txt` leaves `dep_wxWidgets` out of the deps
|
||||
target when `FLATPAK` is set. Instead, `scripts/flatpak/com.orcaslicer.OrcaSlicer.yml` has its own
|
||||
|
||||
@@ -5,7 +5,7 @@ How wx 3.3.2 popups and menus behave on each port, and the Orca wrappers built o
|
||||
`append_menu_item`, `Plater::PopupMenu` and the macOS menubar versus `BBLTopbar`. Read it before you add
|
||||
or change anything that opens over other UI and must close by itself, or any context menu or menubar item.
|
||||
|
||||
wx cites are relative to the wx tree root (`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets`).
|
||||
wx cites are relative to the wx tree root (located as in `SKILL.md` §Ground truth).
|
||||
**[source]** marks behaviour derived from the implementation that the wx docs do not state or contradict.
|
||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0`, so every "asserts" below means "fails silently in Orca".
|
||||
"GTK" means wxGTK3 (X11 and Wayland), the default Linux build; GTK2 is only an opt-out (`-DDEP_WX_GTK3=OFF`).
|
||||
|
||||
@@ -7,7 +7,7 @@ yields and nested event loops, progress dialogs, startup, shutdown and exception
|
||||
screen, and the access rules for `wxGetApp()` and `app_config`. Read it whenever code runs off the
|
||||
main thread, defers work, starts a timer, yields, shows progress, or runs during startup or shutdown.
|
||||
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets`).
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
|
||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`, so `wxASSERT`
|
||||
is compiled out and `wxCHECK*` returns silently: a timer started off the main thread never fires on macOS,
|
||||
`Exit()` on a loop that is not the active one is ignored, `Start(0)` on macOS fails — all without
|
||||
|
||||
@@ -6,10 +6,9 @@ port; what Orca's `DPIDialog`/`DPIFrame` add; the Orca dialog recipe; and the `M
|
||||
Read it before writing or reviewing any dialog, frame, close handler, `Destroy()`/`delete`, or code
|
||||
that keeps a pointer to a window across an event, a `CallAfter` or a modal loop.
|
||||
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (`find deps -maxdepth 5 -type d -path
|
||||
'*dep_wxWidgets-prefix/src/dep_wxWidgets'`). wx is built with `wxBUILD_DEBUG_LEVEL=0` and
|
||||
`libslic3r_gui` with `wxDEBUG_LEVEL=0`: every wx assert quoted below is compiled out, so misuse
|
||||
fails silently (dropped call, stuck loop, freed memory), never with an assert dialog. "GTK" below
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth). wx is
|
||||
built with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every wx assert
|
||||
quoted below is compiled out, so misuse fails silently (dropped call, stuck loop, freed memory), never with an assert dialog. "GTK" below
|
||||
means wxGTK as Orca builds it on Linux: GTK3 by default (X11 or Wayland); GTK2 is only an opt-out.
|
||||
|
||||
Contents: [Rules](#rules) · [1 Creating and parenting](#1-creating-and-parenting-windows) ·
|
||||
|
||||
@@ -14,7 +14,7 @@ Contents: [Rules](#rules) · [1 Reading the change logs](#1-reading-the-change-l
|
||||
[8 Migration done in Orca](#8-migration-already-done-in-orca)
|
||||
|
||||
All `docs/`, `interface/`, `include/`, `src/`, `build/` cites are relative to the pinned wx tree
|
||||
(`find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets'`), except paths
|
||||
(located as in `SKILL.md` §Ground truth), except paths
|
||||
explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca file + symbol cites.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="0.5" y="2.5" width="15" height="11" rx="2.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M6.5 5.5l4.5 2.5-4.5 2.5z" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M1.5 2.5h4a2 2 0 0 1 2 2v9a1.5 1.5 0 0 0-1.5-1.5h-4.5zM14.5 2.5h-4a2 2 0 0 0-2 2v9a1.5 1.5 0 0 1 1.5-1.5h4.5z" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M10.5 5.5h2.5M10.5 7.5h2.5M3 5.5h2.5M3 7.5h2.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 421 B |
@@ -3396,6 +3396,8 @@ void GLGizmoTextureDisplacement::ensure_panel_icons()
|
||||
"texture_displacement_adjust.svg", "canvas_drag.svg", "texture_displacement_move_up.svg",
|
||||
"texture_displacement_move_down.svg", "texture_displacement_drag.svg",
|
||||
"texture_displacement_select_all.svg", "texture_displacement_erase_all.svg",
|
||||
// Header help links: the video walkthrough and the wiki page.
|
||||
"texture_displacement_video_guide.svg", "texture_displacement_wiki.svg",
|
||||
};
|
||||
std::vector<std::string> paths;
|
||||
paths.reserve(names.size());
|
||||
@@ -5507,7 +5509,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
||||
const float seg_pad = m_imgui->scaled(0.5f);
|
||||
const float seg_w[2] = { ImGui::CalcTextSize(labels[0].c_str()).x + 2.f * seg_pad, ImGui::CalcTextSize(labels[1].c_str()).x + 2.f * seg_pad };
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + panel_w - (seg_w[0] + seg_w[1] + gap_s + icon_sm)));
|
||||
// Three icons now follow the segmented control (video guide, wiki, dock toggle), each preceded by
|
||||
// its own gap - the reserved width has to cover all of them or the cluster runs past the panel edge.
|
||||
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(),
|
||||
x0 + panel_w - (seg_w[0] + seg_w[1] + 3.f * (gap_s + icon_sm))));
|
||||
|
||||
// Standard / Pro is a mode, not an option: Standard hides every mesh-preparation control and folds
|
||||
// the whole recipe into Bake, Pro shows all of it and hands the ordering to the user.
|
||||
@@ -5549,6 +5554,15 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
||||
ImGui::GetWindowDrawList()->AddRect(seg_min, ImVec2(ImGui::GetItemRectMax().x, seg_min.y + frame_h),
|
||||
ImGui::GetColorU32(col_frame), style.FrameRounding);
|
||||
|
||||
ImGui::SameLine(0.f, gap_s);
|
||||
if (icon_button(807, "texture_displacement_video_guide.svg", icon_sm, _L("Video guide"),
|
||||
_L("Watch the texture displacement walkthrough on YouTube. Opens in your browser.")))
|
||||
wxLaunchDefaultBrowser("https://www.youtube.com/watch?v=D7w3tG1kdvE");
|
||||
ImGui::SameLine(0.f, gap_s);
|
||||
if (icon_button(808, "texture_displacement_wiki.svg", icon_sm, _L("Documentation"),
|
||||
_L("Open the texture displacement page of the OrcaSlicer wiki. Opens in your browser.")))
|
||||
wxLaunchDefaultBrowser("https://www.orcaslicer.com/wiki/print_prepare/prepare_texture_displacement.html");
|
||||
|
||||
ImGui::SameLine(0.f, gap_s);
|
||||
if (icon_button(806, "canvas_drag.svg", icon_sm, m_undocked ? _L("Dock panel") : _L("Undock panel"),
|
||||
_L("Detach this panel so it can be dragged anywhere over the 3D view, or dock it back beside "
|
||||
|
||||
@@ -184,7 +184,8 @@ class MainFrame : public DPIFrame
|
||||
std::string m_name{ "prepare_layout" };
|
||||
wxSize m_laid_out_size;
|
||||
} m_prepare_layout_prebuild{ *this };
|
||||
// Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
|
||||
// Every built-in LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
|
||||
// Plugin pages stay out: PluginPages destroys them at runtime.
|
||||
std::vector<LazyBase*> m_lazy_pages;
|
||||
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
|
||||
wxString m_printer_url;
|
||||
|
||||
@@ -119,33 +119,13 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Create the webview
|
||||
m_browser = WebView::CreateWebView(this, "");
|
||||
if (m_browser == nullptr) {
|
||||
wxLogError("Could not init m_browser");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
inject_vue_resize_workaround(m_browser);
|
||||
|
||||
auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db");
|
||||
auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend());
|
||||
auto wv_ctx = webkit_web_view_get_context(wv);
|
||||
auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx);
|
||||
webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
#endif
|
||||
|
||||
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
|
||||
create_browser();
|
||||
m_reset_on_show = WebView::NeedsRecreateOnShow();
|
||||
|
||||
SetSizer(topsizer);
|
||||
|
||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
|
||||
update_mode();
|
||||
|
||||
// Log backend information
|
||||
/* m_browser->GetUserAgent() may lead crash
|
||||
if (wxGetApp().get_mode() == comDevelop) {
|
||||
@@ -177,12 +157,44 @@ PrinterWebView::~PrinterWebView()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
|
||||
}
|
||||
|
||||
void PrinterWebView::create_browser()
|
||||
{
|
||||
m_browser = WebView::CreateWebView(this, "");
|
||||
|
||||
#ifdef __linux__
|
||||
inject_vue_resize_workaround(m_browser);
|
||||
|
||||
auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db");
|
||||
auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend());
|
||||
auto wv_ctx = webkit_web_view_get_context(wv);
|
||||
auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx);
|
||||
webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
#endif
|
||||
|
||||
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
|
||||
update_mode();
|
||||
}
|
||||
|
||||
void PrinterWebView::reset_browser()
|
||||
{
|
||||
m_browser->Destroy(); // also removes it from the sizer
|
||||
create_browser();
|
||||
GetSizer()->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
Layout();
|
||||
|
||||
// OnLoaded may have cleared m_url_deferred already, so requeue the last url for Show().
|
||||
m_apikey_sent = false;
|
||||
m_url_deferred = m_url;
|
||||
}
|
||||
|
||||
void PrinterWebView::load_url(wxString& url, wxString apikey)
|
||||
{
|
||||
// this->Show();
|
||||
// this->Raise();
|
||||
if (m_browser == nullptr)
|
||||
return;
|
||||
m_url = url;
|
||||
m_apikey = apikey;
|
||||
m_apikey_sent = false;
|
||||
m_handler = create_printer_webview_handler(*this);
|
||||
@@ -200,6 +212,8 @@ void PrinterWebView::load_url(wxString& url, wxString apikey)
|
||||
|
||||
bool PrinterWebView::Show(bool show)
|
||||
{
|
||||
if (show && std::exchange(m_reset_on_show, false))
|
||||
reset_browser();
|
||||
if (show && !m_url_deferred.empty()) {
|
||||
m_browser->LoadURL(m_url_deferred);
|
||||
//ORCA: m_url_deferred will be cleared on load success
|
||||
|
||||
@@ -58,13 +58,18 @@ private:
|
||||
friend class PrinterWebViewHandler;
|
||||
|
||||
void SendAPIKey();
|
||||
void create_browser();
|
||||
void reset_browser();
|
||||
|
||||
wxWebView* m_browser;
|
||||
long m_zoomFactor;
|
||||
wxString m_apikey;
|
||||
bool m_apikey_sent;
|
||||
// Last url passed to load_url(), reloaded after reset_browser().
|
||||
wxString m_url;
|
||||
wxString m_url_deferred;
|
||||
std::unique_ptr<PrinterWebViewHandler> m_handler;
|
||||
bool m_reset_on_show{false};
|
||||
|
||||
// DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
+34
-13
@@ -80,16 +80,10 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_browser = WebView::CreateWebView(this, m_project_home_url);
|
||||
if (m_browser == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("load web view of project page failed");
|
||||
return;
|
||||
}
|
||||
create_browser();
|
||||
m_reset_on_show = WebView::NeedsRecreateOnShow();
|
||||
//m_browser->Hide();
|
||||
main_sizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId());
|
||||
Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this, m_browser->GetId());
|
||||
|
||||
Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this);
|
||||
|
||||
@@ -117,6 +111,23 @@ void ProjectPanel::shutdown()
|
||||
m_reload_task.reset();
|
||||
}
|
||||
|
||||
void ProjectPanel::create_browser()
|
||||
{
|
||||
m_browser = WebView::CreateWebView(this, m_project_home_url);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId());
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this);
|
||||
}
|
||||
|
||||
void ProjectPanel::reset_browser()
|
||||
{
|
||||
m_browser->Destroy(); // also removes it from the sizer
|
||||
create_browser();
|
||||
GetSizer()->Insert(0, m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
Layout();
|
||||
m_web_init_completed.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Helper to convert newlines to <br>
|
||||
static std::string convert_newlines_to_br(const std::string& text) {
|
||||
std::string result = text;
|
||||
@@ -281,12 +292,13 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
||||
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||
|
||||
if (m_web_init_completed.load(std::memory_order_acquire) &&
|
||||
!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
|
||||
if (!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
|
||||
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
RunScript(strJS.ToStdString());
|
||||
m_info_script = strJS.ToStdString();
|
||||
if (m_web_init_completed.load(std::memory_order_acquire))
|
||||
RunScript(m_info_script);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -327,6 +339,11 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
||||
}
|
||||
else if (strCmd == "request_3mf_info") {
|
||||
m_web_init_completed.store(true, std::memory_order_release);
|
||||
// Replay the stored info after each page load.
|
||||
CallAfter([this] {
|
||||
if (!m_info_script.empty())
|
||||
RunScript(m_info_script);
|
||||
});
|
||||
}
|
||||
else if (strCmd == "edit_project_info") {
|
||||
show_info_editor(true);
|
||||
@@ -383,6 +400,8 @@ void ProjectPanel::clear_model_info()
|
||||
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
// Runs after any store queued by an earlier reload pass, so stale info is never replayed.
|
||||
m_info_script.clear();
|
||||
RunScript(strJS.ToStdString());
|
||||
});
|
||||
}
|
||||
@@ -523,10 +542,12 @@ void ProjectPanel::RunScript(std::string content)
|
||||
WebView::RunScript(m_browser, content);
|
||||
}
|
||||
|
||||
bool ProjectPanel::Show(bool show)
|
||||
bool ProjectPanel::Show(bool show)
|
||||
{
|
||||
if (show && std::exchange(m_reset_on_show, false))
|
||||
reset_browser();
|
||||
if (show) update_model_data();
|
||||
return wxPanel::Show(show);
|
||||
return wxPanel::Show(show);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -80,9 +80,14 @@ private:
|
||||
AuxiliaryPanel* m_auxiliary{nullptr};
|
||||
wxString m_project_home_url;
|
||||
wxString m_root_dir;
|
||||
// Last show_3mf_info script, also sent whenever the page asks for it.
|
||||
std::string m_info_script;
|
||||
bool m_reset_on_show{false};
|
||||
static inline std::atomic<int> m_sequence_id{8000};
|
||||
|
||||
void show_info_editor(bool show);
|
||||
void create_browser();
|
||||
void reset_browser();
|
||||
|
||||
|
||||
public:
|
||||
|
||||
@@ -62,10 +62,10 @@ namespace GUI {
|
||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
||||
m_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "")
|
||||
url += "?lang=" + strlang;
|
||||
m_home_url += "?lang=" + strlang;
|
||||
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -109,12 +109,8 @@ WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||
m_info = new wxInfoBar(this);
|
||||
topsizer->Add(m_info, wxSizerFlags().Expand());
|
||||
// Create the webview
|
||||
m_browser = WebView::CreateWebView(this, url);
|
||||
if (m_browser == nullptr) {
|
||||
wxLogError("Could not init m_browser");
|
||||
return;
|
||||
}
|
||||
m_browser->Hide();
|
||||
create_browser();
|
||||
m_reset_on_show = WebView::NeedsRecreateOnShow();
|
||||
SetSizer(topsizer);
|
||||
|
||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
@@ -264,6 +260,27 @@ WebViewPanel::~WebViewPanel()
|
||||
}
|
||||
|
||||
|
||||
void WebViewPanel::create_browser()
|
||||
{
|
||||
m_browser = WebView::CreateWebView(this, m_home_url);
|
||||
m_browser->Hide();
|
||||
}
|
||||
|
||||
void WebViewPanel::reset_browser()
|
||||
{
|
||||
m_browser->Destroy(); // also removes it from the sizer
|
||||
create_browser();
|
||||
GetSizer()->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
Layout();
|
||||
}
|
||||
|
||||
bool WebViewPanel::Show(bool show)
|
||||
{
|
||||
if (show && std::exchange(m_reset_on_show, false))
|
||||
reset_browser();
|
||||
return wxPanel::Show(show);
|
||||
}
|
||||
|
||||
void WebViewPanel::load_url(wxString& url)
|
||||
{
|
||||
this->Show();
|
||||
|
||||
@@ -113,9 +113,15 @@ public:
|
||||
int get_model_mall_detail_url(std::string *url, std::string id);
|
||||
|
||||
void update_mode();
|
||||
|
||||
bool Show(bool show = true) override;
|
||||
private:
|
||||
void create_browser();
|
||||
void reset_browser();
|
||||
|
||||
wxWebView* m_browser;
|
||||
wxString m_home_url;
|
||||
bool m_reset_on_show{false};
|
||||
wxButton * m_button_stop;
|
||||
wxTextCtrl *m_url;
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
|
||||
@@ -381,6 +381,17 @@ void WebView::MarkScriptMessageHandlerAdded(wxWebView * webView)
|
||||
if (WebViewRef *ref = webview_ref(webView))
|
||||
ref->m_script_handler_added = true;
|
||||
}
|
||||
|
||||
bool WebView::NeedsRecreateOnShow()
|
||||
{
|
||||
const bool recreating = Slic3r::GUI::wxGetApp().is_recreating_gui();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": is_recreating_gui = " << recreating;
|
||||
#ifdef __WIN32__
|
||||
return recreating;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#if wxUSE_WEBVIEW_EDGE
|
||||
bool WebView::CheckWebViewRuntime()
|
||||
{
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
// Marks "wx" as registered so CreateWebView's deferred add skips the duplicate.
|
||||
static void MarkScriptMessageHandlerAdded(wxWebView * webView);
|
||||
|
||||
// On Windows, a WebView2 backend created during a GUI rebuild (language switch) can come up
|
||||
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
|
||||
static bool NeedsRecreateOnShow();
|
||||
|
||||
static void RecreateAll();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "PluginPages.hpp"
|
||||
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/Notebook.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||
@@ -183,8 +184,10 @@ void PluginPages::initialize(Notebook* parent)
|
||||
|
||||
void PluginPages::shutdown()
|
||||
{
|
||||
while (!m_pages.empty())
|
||||
remove_page(m_pages.begin()->first);
|
||||
// Removing the selected tab selects the tab to its left. In tab order that is a built-in tab,
|
||||
// never an unbuilt plugin page that is removed next and would be built only to be destroyed.
|
||||
for (const PluginCapabilityId& id : std::vector<PluginCapabilityId>(m_order))
|
||||
remove_page(id);
|
||||
m_parent = nullptr;
|
||||
}
|
||||
|
||||
@@ -225,8 +228,7 @@ bool PluginPages::create_page(const PluginCapabilityId& id)
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key;
|
||||
}
|
||||
|
||||
auto* page = new PluginPage(m_parent, std::move(capability));
|
||||
|
||||
wxBitmap bitmap;
|
||||
if (!icon.empty()) {
|
||||
try {
|
||||
boost::filesystem::path icon_path(icon);
|
||||
@@ -234,7 +236,7 @@ bool PluginPages::create_page(const PluginCapabilityId& id)
|
||||
if (extension == ".svg" || extension == ".png")
|
||||
icon_path.replace_extension();
|
||||
|
||||
page->set_icon(create_scaled_bitmap(icon_path.string(), m_parent, 20));
|
||||
bitmap = create_scaled_bitmap(icon_path.string(), m_parent, 20);
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what();
|
||||
} catch (...) {
|
||||
@@ -242,7 +244,15 @@ bool PluginPages::create_page(const PluginCapabilityId& id)
|
||||
}
|
||||
}
|
||||
|
||||
m_pages.emplace(id, page);
|
||||
// Built the first time its tab is shown, so a language switch never creates the page's browser
|
||||
// while the main window is being rebuilt. Never prebuilt: the idle queue cannot drop a page
|
||||
// that remove_page() destroys.
|
||||
auto* page = new GUI::LazyPage<PluginPage>(m_parent, GUI::into_u8(page_tab_id(id)), -1,
|
||||
[capability = std::move(capability)](wxWindow* parent) {
|
||||
return new PluginPage(parent, capability);
|
||||
});
|
||||
|
||||
m_pages.emplace(id, Page{page, bitmap});
|
||||
m_order.push_back(id);
|
||||
return true;
|
||||
}
|
||||
@@ -271,16 +281,10 @@ void PluginPages::on_plugin_register(const std::string& plugin_key)
|
||||
|
||||
void PluginPages::on_plugin_deregister(const std::string& plugin_key)
|
||||
{
|
||||
for (auto it = m_pages.begin(); it != m_pages.end();) {
|
||||
if (it->first.plugin_key != plugin_key) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
const PluginCapabilityId id = it->first;
|
||||
++it;
|
||||
remove_page(id);
|
||||
}
|
||||
// In tab order, as in shutdown().
|
||||
for (const PluginCapabilityId& id : std::vector<PluginCapabilityId>(m_order))
|
||||
if (id.plugin_key == plugin_key)
|
||||
remove_page(id);
|
||||
}
|
||||
|
||||
void PluginPages::remove_page(const PluginCapabilityId& id)
|
||||
@@ -289,8 +293,10 @@ void PluginPages::remove_page(const PluginCapabilityId& id)
|
||||
if (it == m_pages.end())
|
||||
return;
|
||||
|
||||
PluginPage* page = it->second;
|
||||
page->detach_capability();
|
||||
GUI::LazyPage<PluginPage>* page = it->second.page;
|
||||
// Only a built page has installed a message sender on the capability.
|
||||
if (PluginPage* built = page->get())
|
||||
built->detach_capability();
|
||||
|
||||
m_pages.erase(it);
|
||||
m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end());
|
||||
@@ -348,26 +354,27 @@ void PluginPages::relayout()
|
||||
bool up_to_date = page_count >= tab_ids.size();
|
||||
for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i)
|
||||
up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]);
|
||||
for (const auto& [id, page] : m_pages) {
|
||||
for (const auto& [id, entry] : m_pages) {
|
||||
if (!up_to_date)
|
||||
break;
|
||||
const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end();
|
||||
up_to_date = (m_parent->FindPage(page) != wxNOT_FOUND) == wanted;
|
||||
up_to_date = entry.page->in_book() == wanted;
|
||||
}
|
||||
|
||||
if (!up_to_date) {
|
||||
const wxString id_to_reselect = m_parent->GetSelectedPageName();
|
||||
|
||||
for (const auto& [id, page] : m_pages) {
|
||||
const int idx = m_parent->FindPage(page);
|
||||
// In tab order, as in shutdown().
|
||||
for (const auto& id : m_order) {
|
||||
const int idx = m_parent->FindPage(m_pages.at(id).page);
|
||||
if (idx != wxNOT_FOUND)
|
||||
m_parent->RemovePage(idx);
|
||||
}
|
||||
|
||||
for (const auto& id : tab_ids) {
|
||||
PluginPage* page = m_pages.at(id);
|
||||
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), "",
|
||||
false, page->icon());
|
||||
const Page& entry = m_pages.at(id);
|
||||
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), entry.page, wxString::FromUTF8(id.name), "",
|
||||
false, entry.icon);
|
||||
}
|
||||
|
||||
if (!id_to_reselect.empty())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <slic3r/GUI/LazyPage.hpp>
|
||||
#include <slic3r/GUI/WebPanel.hpp>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <slic3r/plugin/pluginTypes/pages/PagesPluginCapability.hpp>
|
||||
@@ -29,8 +30,6 @@ public:
|
||||
|
||||
void detach_capability();
|
||||
void push_message(const std::string& message);
|
||||
void set_icon(const wxBitmap& icon) { m_icon = icon; }
|
||||
const wxBitmap& icon() const { return m_icon; }
|
||||
|
||||
protected:
|
||||
std::optional<std::string> page_html() override;
|
||||
@@ -41,7 +40,6 @@ private:
|
||||
|
||||
std::shared_ptr<PagesPluginCapability> m_cap;
|
||||
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
||||
wxBitmap m_icon;
|
||||
};
|
||||
|
||||
class PluginPages
|
||||
@@ -73,7 +71,13 @@ private:
|
||||
void show_overflow_menu();
|
||||
static wxString page_tab_id(const PluginCapabilityId& id);
|
||||
|
||||
std::map<PluginCapabilityId, PluginPage*> m_pages;
|
||||
struct Page
|
||||
{
|
||||
GUI::LazyPage<PluginPage>* page{nullptr};
|
||||
wxBitmap icon;
|
||||
};
|
||||
|
||||
std::map<PluginCapabilityId, Page> m_pages;
|
||||
std::vector<PluginCapabilityId> m_order;
|
||||
Notebook* m_parent{nullptr};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user