fix: Home start shows Prepare or a blank window, and Prepare opens slowly (#15878)

This commit is contained in:
Kris Austin
2026-09-26 14:23:52 -03:00
committed by GitHub
parent e77d179bbe
commit 237cd10eb5
16 changed files with 421 additions and 342 deletions
+102 -185
View File
@@ -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<T>: the holder
### The holder: `Lazy<T>`
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<T>`, 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<T>`, 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<Panel>`
`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<Panel>: 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<Panel>` 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<DiffPresetDialog>` 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<T>` 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<Panel>`, since a tab's panel has one instance.
2. A `LazyPage<Panel>*` 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.
+1 -1
View File
@@ -7,7 +7,6 @@
<script type="text/javascript" src="../include/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="../include/json2.js"></script>
<script type="text/javascript" src="../include/globalapi.js"></script>
<link rel="stylesheet" type="text/css" href="../include/swiper/swiper-bundle.min.css" />
<script type="text/javascript" src="../include/swiper/swiper-bundle.min.js"></script>
@@ -18,6 +17,7 @@
<link rel="stylesheet" type="text/css" href="model.css" />
<link rel="stylesheet" type="text/css" href="./css/dark.css" />
<link rel="stylesheet" type="text/css" href="../include/global.css" /> <!-- ORCA One for all-->
<script type="text/javascript" src="../include/globalapi.js"></script>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="model.js"></script>
+12 -7
View File
@@ -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
+2
View File
@@ -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;
+14 -8
View File
@@ -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::milliseconds>(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;
}
+1 -1
View File
@@ -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.
+11 -6
View File
@@ -3,6 +3,7 @@
#include <chrono>
#include <boost/log/trivial.hpp>
#include <wx/evtloop.h>
#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<int()> 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;
}
+72
View File
@@ -81,6 +81,7 @@
#ifdef __WXGTK__
#include <gtk/gtk.h>
#include <wx/glcanvas.h>
#endif // __WXGTK__
#include <slic3r/GUI/CreatePresetsDialog.hpp>
@@ -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);
+32
View File
@@ -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<LazyBase*> m_lazy_pages;
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
+1 -1
View File
@@ -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};
+2 -2
View File
@@ -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:
+126 -124
View File
@@ -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<GLTexture*>(&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<GLint>(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)
+21
View File
@@ -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
+12 -2
View File
@@ -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<void()>
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()
+5 -2
View File
@@ -71,6 +71,7 @@ class Page: public std::enable_shared_from_this<Page>// : 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();
+7 -3
View File
@@ -10,6 +10,7 @@
#include <wx/webviewarchivehandler.h>
#include <wx/webviewfshandler.h>
#include <wx/weakref.h>
#if wxUSE_WEBVIEW_EDGE
#include <wx/msw/webview_edge.h>
#elif defined(__WXMAC__)
@@ -235,7 +236,9 @@ class FakeWebView : public wxWebView
wxDEFINE_EVENT(EVT_WEBVIEW_RECREATED, wxCommandEvent);
static std::vector<wxWebView*> g_webviews;
static std::vector<wxWebView*> 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<wxWeakRef<wxWebView>> 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<wxWebView>& wv : views)
if (wv)
addScriptMessageHandler(wv.get());
}
}
#ifndef __WIN32__