diff --git a/docs/HLSD/deferred-page-construction.md b/docs/HLSD/deferred-page-construction.md index 748ca65d52..6a25d16fc3 100644 --- a/docs/HLSD/deferred-page-construction.md +++ b/docs/HLSD/deferred-page-construction.md @@ -2,219 +2,136 @@ ## Why it exists -The main window is a notebook of tabs. When the first frame appears, one tab is on -screen and the others are not; some of them are opened later in the session, some -never, and some only exist for certain printers. Building a tab before the first frame -adds its cost to every startup, whether or not the tab is used. +The main window is a notebook of tabs, and only one of them is on screen when the first +frame appears. Others are opened later in the session, some never, and some only exist +for certain printers. Building every tab before the first frame makes each startup pay for +tabs the user may never open. -This subsystem builds a tab's panel the first time the tab is shown. It also builds the -remaining tabs while the user is idle after startup, in units of tens of milliseconds, -so a click that lands in the middle of one waits for that unit and no longer. Startup -pays only for what the first frame shows, and the other tabs are usually built before -anyone opens them. +This subsystem builds a tab the first time it is shown, and builds the rest in small units +while the user is idle after startup. Startup pays only for what the first frame shows, +the other tabs are usually ready before anyone opens them, and a click that lands in the +middle of the idle build waits for one unit at most. Work that is not a tab, such as a +dialog or the 3D view's GL resources, uses the same machinery. ## The parts -`src/slic3r/GUI/Lazy.hpp`, `LazyPage.hpp`, `StagedBuild.hpp` and `IdleScheduler.hpp/.cpp` -(with its wx-free `PrebuildQueue.hpp`) are independent. A holder can hold anything a -factory makes, a page is a holder with a placeholder widget, a staged object can live -outside a holder, and the scheduler knows none of them; it runs tasks, which `MainFrame` -makes from the holders. +The parts are independent. A holder can hold anything a factory makes, a placeholder page +is a holder with a widget, a staged object can live outside a holder, and the scheduler +knows none of them; it runs tasks, which the main window makes from the holders. -### Lazy: the holder +### The holder: `Lazy` -Holds an object that a factory makes on first use, under a name for the log and a place -in the idle queue, both given by the owner. The header has no wx dependency; the busy -cursor for an on-demand build lives in `Lazy.cpp` and is skipped when there is no app, -so the holder is unit-tested. +A holder keeps one object and the factory that makes it. The rest of the app reads the +object if it exists, makes sure it exists now when about to show or navigate to it, or runs +something once it exists. A type with one instance in the app gets these as statics +through `LazyInstance`, so callers need no reference to the main window, and all of them +are harmless while no holder exists. -- `get()` is null until the object is completely built; `built()` says the same. -- `ensure()` builds whatever is left now, under a busy cursor, and returns the object, or - null in the two cases below. A click on an unbuilt tab or a first open of a dialog goes - through this, and it logs the units and time it took. -- `build_step()` runs one unit of construction and returns true while more remain. The - first unit is the factory call, and each later unit is one `StagedBuild` step if the - type has them. -- `when_built(fn)` runs `fn(object)` now if it exists, otherwise once it is built. -- `pending()` says whether the idle prebuild has work here: not built, and the factory - has not returned null. A null factory result is logged and the holder stays unbuilt. -- A unit that pumps the event loop cannot re-enter the holder; a nested `build_step()` - does nothing and a nested `ensure()` returns null. -- After a unit throws, the holder and the scheduler still run the next one. +The holder guarantees that callers see the object only once it is completely built. A +build cannot re-enter itself, so a nested request finds nothing yet, and a factory that +returns null or a unit that throws leaves the holder and the scheduler able to carry on. +The holder does not own the object; its wx parent does, as for any window. The holder has +no wx dependency and is unit-tested. -The holder does not own the object; its wx parent does, as for any window. A type with -one instance in the app derives from `LazyInstance`, which points at that instance's -holder (the holder registers itself, and a recreated `MainFrame`'s holder replaces the -old frame's) and gives the type the static entry points the rest of the app uses, -`T::if_built()`, `T::ensure()` and `T::when_built(fn)`. They return null, or do -nothing, while no holder exists, so a caller needs no `mainframe` check. +### The placeholder page: `LazyPage` -`built()` is an atomic flag, since a job worker reads it through the statics -(`MainFrame::get_calibration_curr_tab()`); it is set after the object is complete. +The notebook needs a page object for a tab to exist and for tabs to be inserted and +removed by pointer, and the placeholder is that object. It builds the real panel inside +itself the first time it is shown and forwards showing and hiding afterwards, so a panel's +own show handling stays its activation hook. Nothing builds while the main window is +hidden; the window's first show builds the start page. A page that is out of the book is +not prebuilt. A panel built while its page is hidden stays hidden, and gets the theming +the window applied before the panel existed. -`LazyBase` is the holder's type-free interface (`name()`, `built()`, `pending()`, -`build_step()`, `prebuild_order()`) and is what the scheduler side sees. +### Staged construction: `StagedBuild` -### LazyPage: the placeholder +A constructor too big to be one unit builds a skeleton and queues the rest as steps, which +run one per unit. A child panel's steps can be forwarded to its parent, and the parent is +complete only once the child is. Nothing may use what a step builds before the last step +has run, so staged panels follow these constraints: -A `wxPanel` placed in the parent in place of the real panel, and a `Lazy` whose -factory makes the panel inside it (by default `new Panel(parent)`). The notebook needs a -page object for the tab to exist and for `show_device()` to insert and remove tabs by -pointer, and the placeholder is that object. `MainFrame` creates every page once, named -after its `TAB_ID_*`, and keeps it for the frame's life; `show_device()` only moves -pages in and out of the book. - -- `Show()` is forwarded to the panel, so a panel's own `Show()` override stays its - activation hook (refresh timers, machine sync) and `SelectPageByName()` works - unchanged. A show builds the panel unless the frame itself is still hidden, because - the book selects its first page as it is inserted; `MainFrame::Show()` shows the - current page again when the frame becomes visible, which builds it. -- `in_book()` says whether the parent notebook currently lists the page, and - `pending()` is that and not built, so a tab `show_device()` has taken out of the book - is not prebuilt. -- A panel built while its page is hidden stays hidden, and a completed panel gets the - dark-mode pass the frame ran before it existed. - -The Compare presets dialog is a holder without a page. `MainFrame` keeps a -`Lazy` whose factory constructs the dialog and binds its events, the -dialog derives from `LazyInstance`, and its callers use -`DiffPresetDialog::ensure()->show()` and `DiffPresetDialog::if_built()` like a tab's -callers do. Saving a preset refreshes the dialog only while it is shown, since `show()` -reloads the presets. - -### StagedBuild: construction in units - -A mixin for a panel whose constructor is too big to be one unit. The constructor builds -the skeleton (sizers, and the parts other code may touch) and queues the rest with -`add_build_step()`. `build_step()` runs one step, and `add_build_steps_of(child)` -forwards a child's steps so a nested panel is spread the same way; the parent is built -only once the child is, including steps the child queues later. Steps run in order, on -the main thread. A `Lazy` recognises a staged type at compile time and runs its steps -one per unit. - -Nothing may touch what a step builds before the last step has run. In practice: - -- nothing paints the panel before it is complete, since a panel built at idle is hidden - with its page and one built on demand finishes inside `ensure()` before the event loop - runs again; -- members created in steps are initialised to null in the header, so a partially built - panel can be destroyed; -- timers and event handlers that use step content check `built()` first - (`MonitorPanel::update_all()`, `CalibrationPanel::update_all()`), and a child's steps - are queued before anything in the constructor can fire such a handler; -- a destructor that disconnects from step content checks `built()` first; -- a constructor or step does not take focus while the panel is off screen - (`IsShownOnScreen()` before `SetFocus()`), since it may run while the user is typing +- members created in steps start out null, so a partly built panel can be destroyed; +- timers, event handlers and destructors that touch step content check that the panel is + complete first; +- nothing takes focus while off screen, since a unit may run while the user is typing elsewhere; -- where a step's widgets must keep their place in a sizer that later steps also fill, the - constructor adds an empty slot sizer in that position and the step fills the slot - (`StatusBasePanel`). +- a widget added by a step keeps its place in the sizer through an empty slot the skeleton + creates. -### IdleScheduler: when to build +### The scheduler: `IdleScheduler` and `PrebuildQueue` -A task is any `LazyBase`: a name for the log, `pending()`, `build_step()` and -`prebuild_order()`. `PrebuildQueue` holds the tasks by order (equal order in the order -added) and runs a slice, the units of the first pending task until it completes, the -budget is spent on the clock it is given, or the interrupt predicate says input arrived. -It has no wx dependency and is unit-tested with a fake clock. A task whose work is gone -is passed over and stays in the queue, so a tab that `show_device()` removes and later -re-inserts is pending again. +The queue holds tasks in order, and a slice runs units of the first pending task until +the task finishes, the time budget is spent, or input arrives. It has no wx dependency and +is unit-tested with a fake clock. A task whose work goes away, such as a tab removed from +the book, is skipped, and becomes pending again if the work comes back. -`IdleScheduler` drives the queue with the real clock, the app's input timestamp and the -Windows queue check, and logs each unit and slice. Each slice is a timer message: a -period of 250 ms while waiting for the user to go quiet, and a one-shot of zero after a -slice that left work, so the event loop dispatches whatever it has queued (paint, -timers, input) before the next slice runs. Chaining slices with `CallAfter` would not do -this: wx drains pending events fully before the next native message, on every platform. -On GTK the one-shot is 5 ms, because a due GLib timeout runs ahead of the redraw and -idle sources that paint and deliver posted events. -A slice runs only once the user has been idle for the quiet time, and the timer stops -itself once no task is pending. The tick period, quiet time and slice length are -constants in `IdleScheduler.cpp`. A unit cannot be interrupted once started, so the -largest unit bounds click latency on Windows; on the other platforms a click also waits -for the rest of the slice. A unit that pumps the event loop lets the timer fire inside -its own slice, and that tick does nothing. +A slice runs only once the user has been idle for a short quiet time, and each slice is its +own timer message, so paint, timers and input queued in between are handled before the +next slice. Posting slices as pending events would not do that, because wx drains every +pending event before the next native message. On GTK a timer that is always due starves +the lower-priority sources that repaint and deliver posted events, so slices are a few +milliseconds apart. On Windows a slice also waits while the native queue holds input, +not counting mouse moves, which Windows synthesizes whenever a window appears under the +cursor. A slice never runs inside a `wxYield()`, where it would build pages in the middle of +the code that yielded. A unit cannot be interrupted, so the largest unit bounds how long a +click can wait. +When nothing is pending the timer stops and the subsystem costs nothing. -The tasks are made by their owners. `MainFrame::prebuild_pages_when_idle()` registers -every `LazyPage` the frame created, in or out of the book (`pending()` is false for a -page out of the book), the Compare presets holder, and the Prepare sidebar's settings -page from `ParamsPanel::settings_page_prebuild()`, whose first unit selects the default -tab if none is selected yet and whose later units build one option group each. -`show_device()` only restarts the timer. `MainFrame` owns the scheduler because it owns -everything the tasks build, and clearing the queue with the frame is what keeps a task -from outliving its object. +The main window owns the scheduler because it owns what the tasks build, and clearing the +queue with the window keeps a task from outliving its object. Each owner provides its own +tasks, such as a tab, a dialog, the Prepare tab's settings page one option group at a +time, the Prepare page's layout at the size the book gives its pages, or the 3D view's GL +resources. -Idle time comes from `GUI_App::FilterEvent`, which timestamps mouse and keyboard events -(`wxEVT_CATEGORY_USER_INPUT` minus command events, which all claim that category), and -`GUI_App::input_idle_ms()` reports it. On Windows a slice additionally refuses to start -when the message queue holds keyboard, button, touch or pen input. Mouse moves are -excluded because Windows synthesises one whenever a window appears under the cursor, -which every unit does. Other platforms use the timestamp alone. +### The 3D view's GL resources -Once every registered page is built the timer is stopped and the subsystem costs -nothing. +OpenGL is loaded on the Prepare tab's canvas. When the start page is not Prepare, loading +it is an idle task that makes the context current on the hidden canvas, so the start page +paints first and Prepare never appears. Loading it on a shown canvas under `Freeze()` holds +back the start page's paint, and on GTK `Freeze()` cannot hide the canvas, which is a +native child window or a Wayland subsurface drawn outside GTK. A hidden Windows child +window keeps its device context, macOS attaches the context to a hidden view, and GTK +creates the canvas's surface when the widget is realized, so on GTK the task realizes the +canvas first. If the context cannot be made current, the canvas's first render loads the +resources. ## Rules -**What builds before the first frame.** The Prepare tab's plater, because `post_init()` -needs its GL canvas on screen to initialise OpenGL in every startup state, and the start -page the user configured. Home is built by the first `MainFrame::Show()`, Prepare's -settings page by selecting the tab. `post_init()` passes through the Prepare tab for GL -init under `Freeze()` with `MainFrame::select_prepare_for_gl_init()`, which changes the -selection without the page-changed event, so nothing else is built for that pass. +**Before the first frame.** Only the start page and the Prepare tab's plater are built +before the first frame. Everything else goes through a holder. -**Reaching a lazy object.** A caller uses the type's own statics. `T::if_built()` may -return null and is for telling the object something it can live without (a rescale, a -colour change, a status update). `T::ensure()` builds the object and is for navigating -to it or for a caller that is about to show it. A caller that tells the object something -it would not fetch for itself on construction uses `T::when_built()`, which keeps the -message until the object exists. A panel that pulls its state when constructed (the Home -page requests the recent list on load, the Device tab reads the device manager on show) -is reached with `if_built()`; one that cannot pull gets `when_built()`. +**Reaching a lazy object.** Callers use the type's statics. Reading it if built is for +things the object can live without, such as a rescale, a color change or a status refresh. +Making sure it exists is for navigating to it or showing it. Running something once it is +built is for state it would not fetch for itself on construction. A panel that pulls its +state when constructed only ever needs to be read if built. -**Unit size.** A unit cannot be interrupted, so it should stay within the slice length on -a fast machine. A constructor above that is staged. A single widget above it is the -floor unless the widget itself is split. +**Unit size.** A unit should fit in one slice on a fast machine. A constructor over that is +staged, and a single widget over it is accepted unless the widget itself can be split. -**Order.** Cheapest and most likely to be opened first, given where `MainFrame` creates -each holder. The settings page is 0 (the Prepare tab's own content), Home 10, Device 20 -(a Bambu user's usual second stop), Calibration 30, Multi-device 40, the web Device -view 50, Project 60 (a second WebView2 instance) and the Compare presets dialog 100; -steps of ten so a new tab takes a value between its neighbours without renumbering -them. `MainFrame::prebuild_pages_when_idle()` registers the tasks once, from -`post_init()`. - -**Never prebuilt.** A holder with a negative order, for a tab that few sessions open -and that costs more to build unasked than it saves (the Design tab), and plugin-provided -tabs, which are Python-side and not lazy pages. +**Order.** Tasks run cheapest and most likely to be opened first. Each holder's order is +given where it is created, with gaps so a new task fits between its neighbors. A negative +order is never prebuilt, for something few sessions open that costs more to build unasked +than it saves. ## Adopting it -A lazy tab needs: +A lazy tab needs a panel type deriving from `LazyInstance`, a placeholder page the main +window creates once with its order and registers for the idle build, and every use of the +panel outside the main window going through the statics. Its constructor has to cope with +the main window already existing and the user being busy elsewhere, so it takes no focus +while off screen, and it does all of its own setup, since the main window does nothing to a +panel after creating it. -1. The panel derived from `LazyInstance`, since a tab's panel has one instance. -2. A `LazyPage*` member in `MainFrame`, created once in `init_tabpanel()` with - its `TAB_ID_*` as the name, its place by the order rule (and a factory if - `new Panel(parent)` is not enough), added to `m_lazy_pages`, and used wherever the - tab is added to or looked up in the notebook. -3. Every use of the panel outside `MainFrame` going through one of the panel's statics, - chosen by the rule above. When converting an existing member, `grep` for it; the - compiler finds the rest. -4. A constructor that copes with the frame already existing and the user being busy - elsewhere, since `wxGetApp().mainframe` is set, the frame may be shown, and the user - may be typing when a lazy panel is built: no `SetFocus()` while off screen, and - whatever the constructor did through a `MainFrame` accessor before (a mode update, a - deferred URL) done in the constructor itself. +To stage a heavy constructor, keep the skeleton in the constructor, move the rest into +steps in its original order, and follow the staged-construction constraints. Measure the +units; a step that is still one big widget is split inside the widget or accepted. -To stage a heavy constructor, inherit `StagedBuild`, keep the skeleton in the constructor, -move the rest into `add_build_step()` lambdas in the original order, and follow the -staged-panel rules above. Measure the units. A step that is still one big widget has to -be split inside that widget or accepted as the floor. +## Verifying -Verification is by log. `MainFrame::prebuild_pages_when_idle` lists the queue it -registered, `IdleScheduler::tick` reports each completed task by name at info level and -each slice and unit at debug level, and `Lazy::ensure` reports an object a user built -on demand with the units and time it took. A run from the configured start -page shows every registered page complete, in order, with no unit longer than intended. -A click on a tab mid-prebuild shows the finished panel with an `ensure` line for what was -left, and the slices resume for the remaining tasks once the user is idle again. +The log lists the queue when it is registered, reports each completed task at info level +and each slice and unit at debug level, and reports every on-demand build with its units +and time. A task's completion line counts only the slice it finished in. A run from the +configured start page should show every registered task complete in order, with no unit +longer than intended. A click on a tab during the idle build should show an on-demand +build for what was left, with the slices resuming once the user is idle again. diff --git a/resources/web/model/index.html b/resources/web/model/index.html index 72feda0f5f..86776b6577 100644 --- a/resources/web/model/index.html +++ b/resources/web/model/index.html @@ -7,7 +7,6 @@ - @@ -18,6 +17,7 @@ + diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 71caeb9ee3..ffa067308c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7281,6 +7281,16 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) m_last_w = w; m_last_h = h; + set_imgui_scaling(); + + this->request_extra_frame(); + + // ensures that this canvas is current + _set_current(); +} + +void GLCanvas3D::set_imgui_scaling() +{ float font_size = wxGetApp().em_unit(); #ifdef _WIN32 @@ -7293,15 +7303,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) #endif #if ENABLE_RETINA_GL - imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); + wxGetApp().imgui()->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); #else - imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); + wxGetApp().imgui()->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); #endif - - this->request_extra_frame(); - - // ensures that this canvas is current - _set_current(); } BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model, bool include_plates) const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 57912ee7cd..36a6c7b944 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1299,6 +1299,8 @@ public: Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr); bool make_current_for_postinit(); + // Sizes ImGui's fonts and style for this canvas; the fonts are rebuilt when the size changes. + void set_imgui_scaling(); private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1a153ed4fb..a41001f9e0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -857,7 +857,12 @@ void GUI_App::post_init() slow_bootup = true; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", slow bootup, won't render gl here."; } - if (!switch_to_3d) { + // Starting on Home, the GL resources load at idle so Home paints first and Prepare is never + // shown. + const bool gl_at_idle = !starts_on_prepare() && is_editor(); + if (!switch_to_3d && gl_at_idle) { + plater_->select_view_3D("3D"); + } else if (!switch_to_3d) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", begin load_gl_resources"; #ifndef __linux__ mainframe->Freeze(); @@ -865,9 +870,6 @@ void GUI_App::post_init() plater_->canvas3D()->enable_render(false); mainframe->select_prepare_for_gl_init(); plater_->select_view_3D("3D"); - // The first render happens before the queued new_project() sets the same view. - plater_->get_camera().select_view("topfront"); - plater_->get_camera().requires_zoom_to_bed = true; //BBS init the opengl resource here if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() || !plater_->canvas3D()->make_current_for_postinit()) { @@ -905,8 +907,6 @@ void GUI_App::post_init() } if (starts_on_prepare()) mainframe->select_tab(TAB_ID_PREPARE); - else if (is_editor()) - mainframe->select_tab(TAB_ID_HOME); #ifndef __linux__ mainframe->Thaw(); #endif @@ -3423,6 +3423,10 @@ bool GUI_App::on_init_inner() } BOOST_LOG_TRIVIAL(info) << "create the main window"; mainframe = new MainFrame(); + // The first render can happen as soon as the frame is shown, before the queued + // new_project() sets the same view. + plater_->get_camera().select_view("topfront"); + plater_->get_camera().requires_zoom_to_bed = true; if (is_editor()) { if (starts_on_prepare()) { mainframe->select_tab(TAB_ID_PREPARE); @@ -8201,10 +8205,12 @@ int GUI_App::input_idle_ms() const return int(std::chrono::duration_cast(std::chrono::steady_clock::now() - m_last_input).count()); } -// Every wxCommandEvent claims the user-input category, so only real mouse and key events count. +// Every wxCommandEvent claims the user-input category, so only real mouse and key events count, +// plus main window resizes, since a border drag produces no mouse events. int GUI_App::FilterEvent(wxEvent& event) { - if (!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT)) + if ((!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT)) || + (event.GetEventType() == wxEVT_SIZE && event.GetEventObject() == mainframe)) m_last_input = std::chrono::steady_clock::now(); return Event_Skip; } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e888a52b06..a8fa8432b1 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -389,7 +389,7 @@ public: bool is_editor() const { return m_app_mode == EAppMode::Editor; } bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } bool is_recreating_gui() const { return m_is_recreating_gui; } - // Milliseconds since the last mouse or keyboard event the app processed. + // Milliseconds since the last mouse or keyboard event the app processed, or main window resize. int input_idle_ms() const; int FilterEvent(wxEvent& event) override; // The Preferences "Default page" choice, stored as its index: 0 Home, 1 Prepare. diff --git a/src/slic3r/GUI/IdleScheduler.cpp b/src/slic3r/GUI/IdleScheduler.cpp index 2b71a9f8e6..18498fce0d 100644 --- a/src/slic3r/GUI/IdleScheduler.cpp +++ b/src/slic3r/GUI/IdleScheduler.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "libslic3r/Utils.hpp" @@ -23,12 +24,9 @@ constexpr int quiet_ms = 500; // queued meanwhile are handled first; a click waits at most a slice plus the unit that // overran it. constexpr int slice_ms = 40; -// On GTK a due timer runs ahead of repaints and posted events, so the next slice waits a few ms. -#ifdef __WXGTK__ +// Delay before the next slice; on GTK a due timer runs ahead of repaints and posted events, +// and wxOSX rejects a 0 ms timer. constexpr int next_slice_ms = 5; -#else -constexpr int next_slice_ms = 0; -#endif // True when unhandled keyboard, button, touch or pen input is queued; only Windows can ask. bool input_pending() @@ -42,6 +40,13 @@ bool input_pending() #endif } +// True inside a wxYield(), where a slice would build pages in the middle of the code that yielded. +bool yielding() +{ + const wxEventLoopBase* loop = wxEventLoopBase::GetActive(); + return loop != nullptr && loop->IsYielding(); +} + } // namespace IdleScheduler::IdleScheduler(std::function input_idle_ms) : m_input_idle_ms(std::move(input_idle_ms)) @@ -69,7 +74,7 @@ void IdleScheduler::tick() stop(); return; } - if (m_input_idle_ms() < quiet_ms || input_pending()) { + if (m_input_idle_ms() < quiet_ms || input_pending() || yielding()) { start(); return; } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ec4428a32a..a57dbf0b97 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -81,6 +81,7 @@ #ifdef __WXGTK__ #include +#include #endif // __WXGTK__ #include @@ -510,6 +511,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ wxQueueEvent(wxGetApp().plater(), new SimpleEvent(EVT_NOTICE_CHILDE_SIZE_CHANGED)); fit_tab_labels(); // ORCA on resize + // Restarts the idle build so a hidden Prepare page is laid out at the new size. + if (m_prebuild_started) + m_idle.start(); }); //BBS @@ -4008,13 +4012,81 @@ bool MainFrame::Show(bool show) return changed; } +bool MainFrame::GLResourcesPrebuild::built() const +{ + return m_frame.m_plater != nullptr && m_frame.m_plater->canvas3D()->is_initialized() && + m_frame.m_plater->get_partplate_list().icon_textures_loaded(); +} + +bool MainFrame::GLResourcesPrebuild::build_step() +{ + GLCanvas3D* canvas = m_frame.m_plater->canvas3D(); +#ifdef __WXGTK__ + // wx creates a GTK canvas's GL surface when the widget is realized, so the context can be + // made current on it while hidden. + gtk_widget_realize(canvas->get_wxglcanvas()->GetHandle()); +#endif + if (!canvas->make_current_for_postinit()) { + // The first render of the canvas loads everything instead. + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": cannot make the GL context current on the hidden canvas"; + m_failed = true; + return false; + } + switch (m_step) { + case 0: + m_failed = !wxGetApp().init_opengl(); + break; + case 1: { + const Size size = canvas->get_canvas_size(); + wxGetApp().imgui()->set_display_size(float(std::max(1, size.get_width())), float(std::max(1, size.get_height()))); + canvas->set_imgui_scaling(); + // Builds the font atlas without leaving a frame open at the hidden canvas's size. + wxGetApp().imgui()->new_frame(); + wxGetApp().imgui()->end_frame(); + break; + } + case 2: + // One texture per unit until none remain. + if (m_frame.m_plater->get_partplate_list().load_next_plate_texture()) + return true; + break; + case 3: + m_failed = !canvas->init(); + break; + default: + // Runs after init(), which sets the color mode the icons are drawn for. + m_frame.m_plater->get_partplate_list().load_icon_textures(); + return false; + } + ++m_step; + return !m_failed; +} + +bool MainFrame::PrepareLayoutPrebuild::built() const +{ + // The book lays out the page it shows. + const wxWindow* page = m_frame.m_tabpanel != nullptr ? m_frame.m_tabpanel->GetCurrentPage() : nullptr; + return page == nullptr || page == m_frame.m_plater || page->GetSize() == m_laid_out_size; +} + +bool MainFrame::PrepareLayoutPrebuild::build_step() +{ + // Sized as the book sizes the page it selects, so the selection finds nothing to lay out. + const wxWindow* page = m_frame.m_tabpanel->GetCurrentPage(); + m_laid_out_size = page->GetSize(); + m_frame.m_plater->SetSize(page->GetRect()); + return false; +} + // A page out of the book stays registered and is passed over; a negative order is never // registered. void MainFrame::prebuild_pages_when_idle() { m_idle.clear(); + m_idle.add(m_gl_prebuild); if (m_param_panel) m_idle.add(m_param_panel->settings_page_prebuild()); + m_idle.add(m_prepare_layout_prebuild); for (LazyBase* page : m_lazy_pages) if (page->prebuild_order() >= 0) m_idle.add(*page); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index f580de57e0..dce12c3fc1 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -140,6 +140,38 @@ class MainFrame : public DPIFrame wxTimer* m_reset_title_text_colour_timer{ nullptr }; IdleScheduler m_idle; bool m_prebuild_started{ false }; + // Loads the Prepare canvas's GL resources while its page is hidden. + class GLResourcesPrebuild : public LazyBase + { + public: + explicit GLResourcesPrebuild(MainFrame& frame) : m_frame(frame) {} + const std::string& name() const override { return m_name; } + bool built() const override; + bool pending() const override { return !m_failed && !built(); } + bool build_step() override; + int prebuild_order() const override { return 0; } + + private: + MainFrame& m_frame; + std::string m_name{ "gl_resources" }; + int m_step{ 0 }; + bool m_failed{ false }; + } m_gl_prebuild{ *this }; + // Lays out the hidden Prepare page at the size the book gives its pages. + class PrepareLayoutPrebuild : public LazyBase + { + public: + explicit PrepareLayoutPrebuild(MainFrame& frame) : m_frame(frame) {} + const std::string& name() const override { return m_name; } + bool built() const override; + bool build_step() override; + int prebuild_order() const override { return 0; } + + private: + MainFrame& m_frame; + 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. std::vector m_lazy_pages; // The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built. diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 921ac62f15..8dde661bdd 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -112,7 +112,7 @@ public: bool split_multi_line{false}; bool option_label_at_right{false}; // BBS: new layout - wxWindow * stb; + wxWindow * stb{ nullptr }; const wxString icon; const wxString title; bool m_labels_hidden{false}; diff --git a/src/slic3r/GUI/ParamsPanel.hpp b/src/slic3r/GUI/ParamsPanel.hpp index 9f9264b480..f4e151456d 100644 --- a/src/slic3r/GUI/ParamsPanel.hpp +++ b/src/slic3r/GUI/ParamsPanel.hpp @@ -122,8 +122,8 @@ class ParamsPanel : public wxPanel wxPanel* m_current_tab { nullptr }; - // Builds the selected page's option groups at idle; while no tab is selected yet, - // its first unit selects the default one. + // Builds the selected page's option groups at idle and then shows them for the mode; + // while no tab is selected yet, its first unit selects the default one. class SettingsPagePrebuild : public LazyBase { public: diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index e90b21e0ff..8e2d6ba37b 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -799,67 +799,8 @@ void PartPlate::render_logo(bool bottom, bool render_cali) { if (!m_partplate_list->render_bedtype_logo) { // render third-party printer texture logo - if (m_partplate_list->m_logo_texture_filename.empty()) { - m_partplate_list->m_logo_texture.reset(); + if (!m_partplate_list->load_logo_texture()) return; - } - - //GLTexture* temp_texture = const_cast(&m_temp_texture); - - if (m_partplate_list->m_logo_texture.get_id() == 0 || m_partplate_list->m_logo_texture.get_source() != m_partplate_list->m_logo_texture_filename) { - m_partplate_list->m_logo_texture.reset(); - - if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".svg")) { - /*// use higher resolution images if graphic card and opengl version allow - GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) { - // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { - render_default(bottom, false); - return; - } - canvas.request_extra_frame(); - }*/ - - // starts generating the main texture, compression will run asynchronously - GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048; - if (!m_partplate_list->m_logo_texture.load_from_svg_file(m_partplate_list->m_logo_texture_filename, true, true, true, logo_tex_size)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename; - return; - } - } - else if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".png")) { - // generate a temporary lower resolution texture to show while no main texture levels have been compressed - /* if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_logo_texture_filename) { - if (!temp_texture->load_from_file(m_logo_texture_filename, false, GLTexture::None, false)) { - render_default(bottom, false); - return; - } - canvas.request_extra_frame(); - }*/ - - // starts generating the main texture, compression will run asynchronously - if (!m_partplate_list->m_logo_texture.load_from_file(m_partplate_list->m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename; - return; - } - } - else { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_partplate_list->m_logo_texture_filename; - return; - } - } - else if (m_partplate_list->m_logo_texture.unsent_compressed_data_available()) { - // sends to gpu the already available compressed levels of the main texture - m_partplate_list->m_logo_texture.send_compressed_data_to_gpu(); - - // the temporary texture is not needed anymore, reset it - //if (temp_texture->get_id() != 0) - // temp_texture->reset(); - - //canvas.request_extra_frame(); - } if (m_logo_triangles.is_initialized()) render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom); @@ -4232,6 +4173,7 @@ Vec2d PartPlateList::compute_shape_position(int index, int cols) //generate icon textures void PartPlateList::generate_icon_textures() { + m_icon_textures_dark = m_is_dark; // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(), icon_size = max_tex_size / 8; std::string path = resources_dir() + "/images/"; @@ -4447,6 +4389,9 @@ void PartPlateList::release_icon_textures() PartPlateList::is_load_bedtype_textures = false; PartPlateList::is_load_extruder_only_area_textures = false; PartPlateList::is_load_cali_texture = false; + m_next_bedtype_texture = 0; + m_next_extruder_only_area_texture = 0; + m_next_cali_texture = 0; for (int i = 0; i < btCount; i++) { for (auto& part: bed_texture_info[i].parts) { if (part.texture) { @@ -6002,12 +5947,7 @@ void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& pr plate_hover_action = hover_id % PartPlate::GRABBER_COUNT; } - static bool last_dark_mode_status = m_is_dark; - if (m_is_dark != last_dark_mode_status) { - last_dark_mode_status = m_is_dark; - generate_icon_textures(); - } else if(m_del_texture.get_id() == 0) - generate_icon_textures(); + load_icon_textures(); for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) { int current_index = (*it)->get_index(); if (only_current && (current_index != m_current_plate)) @@ -6149,6 +6089,8 @@ bool PartPlateList::set_shapes(const Pointfs &shape, } is_load_bedtype_textures = false; //reload textures is_load_extruder_only_area_textures = false; // reload textures + m_next_bedtype_texture = 0; + m_next_extruder_only_area_texture = 0; calc_bounding_boxes(); update_logo_texture_filename(texture_filename); @@ -7089,53 +7031,120 @@ bool PartPlateList::init_extruder_only_area_info() return true; } -void PartPlateList::load_bedtype_textures() +static GLint logo_texture_size() { - if (PartPlateList::is_load_bedtype_textures) return; - - init_bed_type_info(); - GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048; - for (int i = 0; i < (unsigned int)btCount; ++i) { - for (int j = 0; j < bed_texture_info[i].parts.size(); j++) { - std::string filename = resources_dir() + "/images/" + bed_texture_info[i].parts[j].filename; - if (boost::filesystem::exists(filename)) { - PartPlateList::bed_texture_info[i].parts[j].texture = new GLTexture(); - if (!PartPlateList::bed_texture_info[i].parts[j].texture->load_from_svg_file(filename, true, true, true, logo_tex_size)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename; - } - } else { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename; - } - } - } - PartPlateList::is_load_bedtype_textures = true; + return std::min(OpenGLManager::get_gl_info().get_max_tex_size(), 2048); } -void PartPlateList::load_extruder_only_area_textures() { - if (PartPlateList::is_load_extruder_only_area_textures) return; +// Loads the texture of the next untried part across the parts of `infos`, in order, advancing +// `next`; false once every part has been tried. +static bool load_next_part_texture(PartPlateList::BedTextureInfo* infos, size_t count, size_t& next, bool compress_and_filter) +{ + size_t k = next; + for (size_t i = 0; i < count; ++i) { + if (k >= infos[i].parts.size()) { + k -= infos[i].parts.size(); + continue; + } + ++next; + PartPlateList::BedTextureInfo::TexturePart& part = infos[i].parts[k]; + const std::string filename = resources_dir() + "/images/" + part.filename; + if (boost::filesystem::exists(filename)) { + part.texture = new GLTexture(); + if (!part.texture->load_from_svg_file(filename, true, compress_and_filter, compress_and_filter, logo_texture_size())) + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load texture from %1% failed!") % filename; + } else { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load texture from %1% failed!") % filename; + } + return true; + } + return false; +} - auto ok = init_extruder_only_area_info(); - if (!ok) { +void PartPlateList::load_bedtype_textures() +{ + while (load_next_bedtype_texture()) {} +} + +bool PartPlateList::load_next_bedtype_texture() +{ + if (PartPlateList::is_load_bedtype_textures) + return false; + if (m_next_bedtype_texture == 0) + init_bed_type_info(); + if (load_next_part_texture(bed_texture_info, btCount, m_next_bedtype_texture, true)) + return true; + PartPlateList::is_load_bedtype_textures = true; + return false; +} + +bool PartPlateList::load_logo_texture() +{ + if (m_logo_texture_filename.empty()) { + m_logo_texture.reset(); + return false; + } + + if (m_logo_texture.get_id() != 0 && m_logo_texture.get_source() == m_logo_texture_filename) { + if (m_logo_texture.unsent_compressed_data_available()) + // sends to gpu the already available compressed levels of the main texture + m_logo_texture.send_compressed_data_to_gpu(); + return true; + } + + m_logo_texture.reset(); + // starts generating the main texture, compression will run asynchronously + if (boost::algorithm::iends_with(m_logo_texture_filename, ".svg")) { + if (!m_logo_texture.load_from_svg_file(m_logo_texture_filename, true, true, true, logo_texture_size())) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_logo_texture_filename; + return false; + } + } + else if (boost::algorithm::iends_with(m_logo_texture_filename, ".png")) { + if (!m_logo_texture.load_from_file(m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_logo_texture_filename; + return false; + } + } + else { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_logo_texture_filename; + return false; + } + return true; +} + +void PartPlateList::load_icon_textures() +{ + if (!icon_textures_loaded()) + generate_icon_textures(); +} + +bool PartPlateList::load_next_plate_texture() +{ + if (!render_bedtype_logo) { + load_logo_texture(); + return false; + } + return load_next_bedtype_texture() || load_next_cali_texture() || load_next_extruder_only_area_texture(); +} + +void PartPlateList::load_extruder_only_area_textures() +{ + while (load_next_extruder_only_area_texture()) {} +} + +bool PartPlateList::load_next_extruder_only_area_texture() +{ + if (PartPlateList::is_load_extruder_only_area_textures) + return false; + if (m_next_extruder_only_area_texture == 0 && !init_extruder_only_area_info()) { PartPlateList::is_load_extruder_only_area_textures = true; - return; - } - GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048; - for (int i = 0; i < (unsigned int) ExtruderOnlyAreaType::btAreaCount; ++i) { - for (int j = 0; j < extruder_only_area_info[i].parts.size(); j++) { - std::string filename = resources_dir() + "/images/" + extruder_only_area_info[i].parts[j].filename; - if (boost::filesystem::exists(filename)) { - PartPlateList::extruder_only_area_info[i].parts[j].texture = new GLTexture(); - if (!PartPlateList::extruder_only_area_info[i].parts[j].texture->load_from_svg_file(filename, true, false, false, logo_tex_size)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename; - } - } else { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename; - } - } + return false; } + if (load_next_part_texture(extruder_only_area_info, (size_t) ExtruderOnlyAreaType::btAreaCount, m_next_extruder_only_area_texture, false)) + return true; PartPlateList::is_load_extruder_only_area_textures = true; + return false; } void PartPlateList::init_cali_texture_info() @@ -7150,26 +7159,19 @@ void PartPlateList::init_cali_texture_info() void PartPlateList::load_cali_textures() { - if (PartPlateList::is_load_cali_texture) return; + while (load_next_cali_texture()) {} +} - init_cali_texture_info(); - GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048; - for (int i = 0; i < (unsigned int)btCount; ++i) { - for (int j = 0; j < cali_texture_info.parts.size(); j++) { - std::string filename = resources_dir() + "/images/" + cali_texture_info.parts[j].filename; - if (boost::filesystem::exists(filename)) { - PartPlateList::cali_texture_info.parts[j].texture = new GLTexture(); - if (!PartPlateList::cali_texture_info.parts[j].texture->load_from_svg_file(filename, true, true, true, logo_tex_size)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load cali texture from %1% failed!") % filename; - } - } - else { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load cali texture from %1% failed!") % filename; - } - } - } +bool PartPlateList::load_next_cali_texture() +{ + if (PartPlateList::is_load_cali_texture) + return false; + if (m_next_cali_texture == 0) + init_cali_texture_info(); + if (load_next_part_texture(&cali_texture_info, 1, m_next_cali_texture, true)) + return true; PartPlateList::is_load_cali_texture = true; + return false; } void PartPlateList::on_extruder_count_changed(int extruder_count) diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 9bee917243..52a277d0e3 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -644,6 +644,7 @@ class PartPlateList : public ObjectBase std::string m_hover_tooltip; bool m_is_dark = false; + bool m_icon_textures_dark = false; int m_filament_count = 1; @@ -943,12 +944,25 @@ public: bool calc_extruder_only_area(Rect &left_only_rect, Rect &right_only_rect); void init_bed_type_info(); bool init_extruder_only_area_info(); + // Each load_*_textures() loads whatever of its set is not loaded yet; each load_next_*() + // loads one texture and returns false once none remain. void load_bedtype_textures(); + bool load_next_bedtype_texture(); void load_extruder_only_area_textures(); + bool load_next_extruder_only_area_texture(); + // Starts loading the printer's logo texture, or sends the levels compressed since; false when + // there is no logo to draw. + bool load_logo_texture(); void show_cali_texture(bool show = true); void init_cali_texture_info(); void load_cali_textures(); + bool load_next_cali_texture(); + bool icon_textures_loaded() const { return m_del_texture.get_id() != 0 && m_icon_textures_dark == m_is_dark; } + void load_icon_textures(); + // Loads the next bed-type, calibration or extruder-area texture, or the logo, which rendering + // otherwise loads on first use; false once none remain. + bool load_next_plate_texture(); void on_extruder_count_changed(int extruder_count); @@ -960,6 +974,13 @@ public: BedTextureInfo bed_texture_info[btCount]; BedTextureInfo cali_texture_info; BedTextureInfo extruder_only_area_info[(unsigned char) Slic3r::ExtruderOnlyAreaType::btAreaCount]; + +private: + // The next part to load in each texture set, counted across the set's parts in order; reset + // with the set's is_load_* flag. + size_t m_next_bedtype_texture{ 0 }; + size_t m_next_cali_texture{ 0 }; + size_t m_next_extruder_only_area_texture{ 0 }; }; } // namespace GUI diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ea0ba3fec4..09843cbc94 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -7165,12 +7165,18 @@ void Tab::restore_last_select_item() bool Tab::page_build_pending() const { - return m_active_page != nullptr && m_active_page->build_pending(); + return m_active_page != nullptr && (m_active_page->build_pending() || m_active_page->visibility_pending()); } bool Tab::page_build_step() { - return m_active_page != nullptr && m_active_page->build_step(m_mode); + if (m_active_page == nullptr) + return false; + if (m_active_page->build_pending()) + m_active_page->build_step(m_mode); + else + m_active_page->update_visibility(m_mode, true); + return page_build_pending(); } void Tab::update_description_lines() @@ -8601,6 +8607,8 @@ void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibil } m_show = ret_val; + if (update_contolls_visibility) + m_visibility_applied = true; #ifdef __WXMSW__ if (!m_show) return; // BBS: fix field control position @@ -8654,6 +8662,7 @@ bool Page::activate_group(size_t i, ConfigOptionMode mode, std::function auto& group = m_optgroups[i]; if (!group->activate(throw_if_canceled)) return false; + m_visibility_applied = false; m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section group->update_visibility(mode); #if HIDE_FIRST_SPLIT_LINE @@ -8688,6 +8697,7 @@ void Page::clear() for (auto group : m_optgroups) group->clear(); m_page_title = NULL; + m_visibility_applied = false; } void Page::msw_rescale() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index c5879c6e94..fa8a7ae68b 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -71,6 +71,7 @@ class Page: public std::enable_shared_from_this// : public wxScrolledWindo // BBS: new layout wxStaticText* m_page_title; bool m_show = true; + bool m_visibility_applied = false; public: //BBS: GUI refactor Page(wxWindow* parent, const wxString& title, int iconID, wxPanel* tab_owner); @@ -98,6 +99,8 @@ public: bool build_pending() const; // Builds the next option group that has no controls yet; true while some remain. bool build_step(ConfigOptionMode mode); + // Whether the controls have not been shown or hidden for a mode since they were built. + bool visibility_pending() const { return !m_visibility_applied; } void clear(); void msw_rescale(); void sys_color_changed(); @@ -443,8 +446,8 @@ public: // BBS: new layout void set_expanded(bool value); void restore_last_select_item(); - // page_build_pending() says whether the selected page has groups without controls, and - // page_build_step() builds one. + // page_build_pending() says whether the selected page has groups without controls or controls + // not yet shown for the mode, and page_build_step() does the next of those. bool page_build_pending() const; bool page_build_step(); diff --git a/src/slic3r/GUI/Widgets/WebView.cpp b/src/slic3r/GUI/Widgets/WebView.cpp index a29a3cb725..a313e80d66 100644 --- a/src/slic3r/GUI/Widgets/WebView.cpp +++ b/src/slic3r/GUI/Widgets/WebView.cpp @@ -10,6 +10,7 @@ #include #include +#include #if wxUSE_WEBVIEW_EDGE #include #elif defined(__WXMAC__) @@ -235,7 +236,9 @@ class FakeWebView : public wxWebView wxDEFINE_EVENT(EVT_WEBVIEW_RECREATED, wxCommandEvent); static std::vector g_webviews; -static std::vector g_delay_webviews; +// Webviews waiting for their script handler while another one is added; adding it yields, so a +// view can be destroyed while it waits. +static std::vector> g_delay_webviews; class WebViewRef : public wxObjectRefData { @@ -340,8 +343,9 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url) addScriptMessageHandler(webView); while (!g_delay_webviews.empty()) { auto views = std::move(g_delay_webviews); - for (auto wv : views) - addScriptMessageHandler(wv); + for (const wxWeakRef& wv : views) + if (wv) + addScriptMessageHandler(wv.get()); } } #ifndef __WIN32__