diff --git a/openspec/changes/archive/2026-04-29-filament-manager-zoom-persistence/proposal.md b/openspec/changes/archive/2026-04-29-filament-manager-zoom-persistence/proposal.md new file mode 100644 index 0000000000..b37681e90e --- /dev/null +++ b/openspec/changes/archive/2026-04-29-filament-manager-zoom-persistence/proposal.md @@ -0,0 +1,26 @@ +## Why + +The Filament Manager page supports Ctrl+mousewheel to adjust the browser zoom level, but that zoom is never persisted. Every time BambuStudio restarts the page resets to the default zoom (1.0×), forcing users to re-apply their preferred zoom on every launch. + +## What Changes + +- Bind `wxEVT_WEBVIEW_LOADED` on the `wxWebView` owned exclusively by `DeviceWebPage` (the filament manager). When the page finishes loading, read `filament_manager_zoom_factor` from `AppConfig` and apply it via `wxWebView::SetZoomFactor()`. +- Add `DeviceWebPage::SaveZoom()` — reads the current zoom via `wxWebView::GetZoomFactor()` and writes it to `AppConfig`, then calls `AppConfig::save()`. +- Call `SaveZoom()` from `MainFrame`'s `wxEVT_CLOSE_WINDOW` handler (before any veto checks) so the last-used zoom is always captured on shutdown. +- Remove the dead `long m_zoomFactor` field and its `= 100` assignment from `PrinterWebView` (never read or applied). + +## Capabilities + +### New Capabilities + +- Filament Manager page zoom level (set via Ctrl+mousewheel) is now remembered across BambuStudio launches via the `filament_manager_zoom_factor` key in `AppConfig`. + +### Modified Capabilities + +- No behaviour change for any other page. The zoom binding is placed in `DeviceWebPage` rather than `PrinterWebView` to ensure it does not affect the separate printer-web panel (`m_printer_view`) which also uses `PrinterWebView`. + +## Impact + +- **Affected files**: `DeviceWebPage.cpp/.hpp`, `PrinterWebView.cpp/.hpp`, `MainFrame.cpp` +- **Affected feature**: Filament Manager zoom persistence +- **Risk**: Very low. Uses established `AppConfig` set/get/save pattern (same as `window_mainframe`). Guards against `FakeWebView` (zoom ≤ 0 is skipped) and malformed config values (try/catch around `std::stof`). Cross-platform: WebView2 (Windows), WKWebView (macOS), WebKitGTK (Linux) all implement `GetZoomFactor`/`SetZoomFactor` via wxWidgets virtuals. diff --git a/openspec/changes/archive/2026-04-29-filament-manager-zoom-persistence/tasks.md b/openspec/changes/archive/2026-04-29-filament-manager-zoom-persistence/tasks.md new file mode 100644 index 0000000000..d649d11a0b --- /dev/null +++ b/openspec/changes/archive/2026-04-29-filament-manager-zoom-persistence/tasks.md @@ -0,0 +1,13 @@ +# Tasks — Filament Manager Zoom Persistence +_Completed: 2026-04-29 19:31 UTC+8_ + +## Implementation + +- [x] `src/slic3r/GUI/PrinterWebView.hpp` — remove dead `long m_zoomFactor` field +- [x] `src/slic3r/GUI/PrinterWebView.cpp` — remove dead `m_zoomFactor = 100` assignment +- [x] `src/slic3r/GUI/DeviceWeb/DeviceWebPage.hpp` — add public `SaveZoom()`, private `OnWebLoaded(wxWebViewEvent&)` +- [x] `src/slic3r/GUI/DeviceWeb/DeviceWebPage.cpp` + - [x] Bind `wxEVT_WEBVIEW_LOADED` → `OnWebLoaded` on `GetWebView()` in constructor + - [x] Implement `OnWebLoaded`: read and validate `filament_manager_zoom_factor` from `AppConfig`, call `SetZoomFactor` + - [x] Implement `SaveZoom`: call `GetZoomFactor`, guard `> 0`, write to `AppConfig` and save +- [x] `src/slic3r/GUI/MainFrame.cpp` — call `m_web_device->SaveZoom()` at top of `wxEVT_CLOSE_WINDOW` handler diff --git a/src/slic3r/GUI/DeviceWeb/DeviceWebPage.cpp b/src/slic3r/GUI/DeviceWeb/DeviceWebPage.cpp index 2dfc3ade6f..fb32963437 100644 --- a/src/slic3r/GUI/DeviceWeb/DeviceWebPage.cpp +++ b/src/slic3r/GUI/DeviceWeb/DeviceWebPage.cpp @@ -5,6 +5,8 @@ #include "libslic3r_version.h" #include "DeviceWebPage.hpp" +#include + #if defined(__WXOSX__) #include "slic3r/Utils/MacDarkMode.hpp" #endif @@ -40,6 +42,13 @@ DeviceWebPage::DeviceWebPage(wxWindow *parent): wxPanel(parent, wxID_ANY, wxDefa auto web_sizer = new wxBoxSizer(wxVERTICAL); web_sizer->Add(m_device_webview, 1, wxEXPAND); + // Restore saved zoom once the page has finished loading. + // Binding to GetWebView() here rather than inside PrinterWebView keeps + // this behaviour exclusive to the filament manager (PrinterWebView is + // reused by the printer-web panel in MainFrame). + if (wxWebView* wv = GetWebView()) + wv->Bind(wxEVT_WEBVIEW_LOADED, &DeviceWebPage::OnWebLoaded, this); + LoadUrl(); SetSizer(web_sizer); @@ -128,4 +137,43 @@ void DeviceWebPage::NotifyFilamentMachineChanged() m_device_web_mgr->NotifyState("filament", "machine", "selected_changed"); } +void DeviceWebPage::OnWebLoaded(wxWebViewEvent& evt) +{ + evt.Skip(); + + auto* config = wxGetApp().app_config; + if (!config || !config->has("filament_manager_zoom_factor")) + return; + + wxWebView* wv = GetWebView(); + if (!wv) + return; + + try { + float zoom = std::stof(config->get("filament_manager_zoom_factor")); + if (zoom >= 0.25f && zoom <= 5.0f) + wv->SetZoomFactor(zoom); + } catch (const std::exception&) { + // Ignore malformed config values; browser default (1.0) will be used. + } +} + +void DeviceWebPage::SaveZoom() +{ + wxWebView* wv = GetWebView(); + if (!wv) + return; + + float zoom = wv->GetZoomFactor(); + if (zoom <= 0.0f) + return; + + auto* config = wxGetApp().app_config; + if (!config) + return; + + config->set("filament_manager_zoom_factor", std::to_string(zoom)); + config->save(); +} + }} diff --git a/src/slic3r/GUI/DeviceWeb/DeviceWebPage.hpp b/src/slic3r/GUI/DeviceWeb/DeviceWebPage.hpp index 0b40d6000a..9a15d1005c 100644 --- a/src/slic3r/GUI/DeviceWeb/DeviceWebPage.hpp +++ b/src/slic3r/GUI/DeviceWeb/DeviceWebPage.hpp @@ -37,12 +37,17 @@ class DeviceWebPage: public wxPanel { void msw_rescale(); + // Persist the current webview zoom level to AppConfig. + // Called from MainFrame's close handler so the level survives app restarts. + void SaveZoom(); + wxWebView *GetWebView() const { if (m_device_webview) return m_device_webview->GetWebView(); return nullptr; } private: + void OnWebLoaded(wxWebViewEvent& evt); PrinterWebView* m_device_webview{nullptr}; // owned by wx parent std::unique_ptr m_device_http_server; std::unique_ptr m_device_web_bridge; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 1a37b30945..f1a4086a78 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -460,6 +460,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ // declare events Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": mainframe received close_widow event"; + + // Persist the filament manager zoom level before any close veto can + // abort the shutdown, so the last-used zoom is always saved. + if (m_web_device) + m_web_device->SaveZoom(); + if (event.CanVeto() && m_plater->get_view3D_canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) { // prevents to open the save dirty project dialog event.Veto(); diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp index 91b4a056e3..5174512eca 100644 --- a/src/slic3r/GUI/PrinterWebView.cpp +++ b/src/slic3r/GUI/PrinterWebView.cpp @@ -46,9 +46,6 @@ PrinterWebView::PrinterWebView(wxWindow *parent) } */ - //Zoom - m_zoomFactor = 100; - //Connect the idle events Bind(wxEVT_CLOSE_WINDOW, &PrinterWebView::OnClose, this); diff --git a/src/slic3r/GUI/PrinterWebView.hpp b/src/slic3r/GUI/PrinterWebView.hpp index da0c4666f7..c9102f84ab 100644 --- a/src/slic3r/GUI/PrinterWebView.hpp +++ b/src/slic3r/GUI/PrinterWebView.hpp @@ -44,7 +44,6 @@ class PrinterWebView : public wxPanel { private: wxWebView* m_browser; - long m_zoomFactor; // DECLARE_EVENT_TABLE() };