mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 18:31:11 +00:00
fix: Home start shows Prepare or a blank window, and Prepare opens slowly (#15878)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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__
|
||||
|
||||
Reference in New Issue
Block a user