feat: faster startup by lazy-loading main window panels on idle or first use (#15811)

This commit is contained in:
Kris Austin
2026-09-23 19:11:10 -03:00
committed by GitHub
parent 9bae19fcb3
commit 66b300987b
52 changed files with 2035 additions and 483 deletions
+220
View File
@@ -0,0 +1,220 @@
# Deferred Page Construction: High Level Design
## 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.
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.
## 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.
### Lazy<T>: the holder
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.
- `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 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.
`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.
`LazyBase` is the holder's type-free interface (`name()`, `built()`, `pending()`,
`build_step()`, `prebuild_order()`) and is what the scheduler side sees.
### LazyPage<Panel>: the placeholder
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
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`).
### IdleScheduler: when to build
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.
`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.
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.
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.
Once every registered page is built the timer is stopped and the subsystem costs
nothing.
## 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.
**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()`.
**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.
**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.
## Adopting it
A lazy tab needs:
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, 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.
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.
+8
View File
@@ -286,6 +286,8 @@ set(SLIC3R_GUI_SOURCES
GUI/FilamentMapDialog.hpp
GUI/IconManager.cpp
GUI/IconManager.hpp
GUI/IdleScheduler.cpp
GUI/IdleScheduler.hpp
GUI/ImageGrid.cpp
GUI/ImageGrid.h
GUI/ImGuiWrapper.cpp
@@ -347,6 +349,10 @@ set(SLIC3R_GUI_SOURCES
GUI/KBShortcutsDialog.hpp
GUI/KeyChord.cpp
GUI/KeyChord.hpp
GUI/Lazy.cpp
GUI/Lazy.hpp
GUI/LazyPage.cpp
GUI/LazyPage.hpp
GUI/LibVGCode/LibVGCodeWrapper.hpp
GUI/LibVGCode/LibVGCodeWrapper.cpp
GUI/LinuxDisplayBackend.cpp
@@ -433,6 +439,7 @@ set(SLIC3R_GUI_SOURCES
GUI/Plater.hpp
GUI/PlateSettingsDialog.cpp
GUI/PlateSettingsDialog.hpp
GUI/PrebuildQueue.hpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/PresetBundleDialog.cpp
@@ -512,6 +519,7 @@ set(SLIC3R_GUI_SOURCES
GUI/SliceInfoPanel.hpp
GUI/SlicingProgressNotification.cpp
GUI/SlicingProgressNotification.hpp
GUI/StagedBuild.hpp
GUI/StatusPanel.cpp
GUI/StatusPanel.hpp
GUI/StepMeshDialog.cpp
+21 -11
View File
@@ -863,17 +863,27 @@ void AuxiliaryPanel::init_tabpanel()
m_tabpanel->SetBackgroundColour(wxColour("#FEFFFF"));
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [](wxBookCtrlEvent &e) { /* Event handling */ });
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
add_build_step([this] {
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
});
add_build_step([this] {
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
});
add_build_step([this] {
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
});
add_build_step([this] {
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
});
add_build_step([this] {
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
Layout();
});
}
wxWindow *AuxiliaryPanel::create_side_tools()
+2 -1
View File
@@ -46,6 +46,7 @@
#include "slic3r/GUI/UpgradePanel.hpp"
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#include "StagedBuild.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
@@ -194,7 +195,7 @@ public:
};
class AuxiliaryPanel : public wxPanel
class AuxiliaryPanel : public wxPanel, public StagedBuild
{
private:
Tabbook *m_tabpanel = {nullptr};
+2 -1
View File
@@ -12,6 +12,7 @@
#include "libslic3r/CAD/CadDocument.hpp"
#include "slic3r/GUI/CAD/DesignInteraction.hpp" // CadLevel: what one Esc press means
#include "slic3r/GUI/Lazy.hpp"
class ComboBox; // Orca dropdown (Widgets/ComboBox.hpp) — replaces wxChoice everywhere here
class StaticBox; // Orca rounded card frame (Widgets/StaticBox.hpp)
@@ -46,7 +47,7 @@ class DesignCanvas;
// Design (CAD) tab: a sketch-first, Onshape-style form-driven CAD panel.
// Sketch and Extrude are independent tools: the user creates a Sketch first,
// then selects it and Extrudes to produce a solid.
class DesignPanel : public wxPanel
class DesignPanel : public wxPanel, public LazyInstance<DesignPanel>
{
public:
explicit DesignPanel(wxWindow* parent);
+1 -2
View File
@@ -1989,11 +1989,10 @@ json action_set_feature_expr(DesignPanel* panel, const json& params)
// Dispatch one parsed request ON THE MAIN THREAD. Returns a JSON-RPC reply string.
std::string handle_on_main(const std::string& method, const json& params, const json& id)
{
MainFrame* mf = wxGetApp().mainframe;
// The panel is built on first use, and in a headless session nobody clicks the tab that
// would build it -- so build it here rather than refusing. Safe: this runs on the main
// thread (see the CallAfter that dispatches us).
DesignPanel* panel = mf ? mf->ensure_design_panel() : nullptr;
DesignPanel* panel = DesignPanel::ensure();
if (!panel)
return rpc_error(id, -32001, "Design panel not ready");
+6 -3
View File
@@ -491,14 +491,15 @@ void CalibrationPanel::init_tabpanel() {
selected);
}
for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
add_build_steps_of(*m_cali_panels[i]);
// ORCA use standard paddings and keep arrow icon for consistent look between sidebars
//for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
// m_tabpanel->SetPageImage(i, "");
//auto padding_size = m_tabpanel->GetBtnsListCtrl()->GetPaddingSize(0);
//m_tabpanel->GetBtnsListCtrl()->SetPaddingSize({ FromDIP(15), padding_size.y });
m_initialized = true;
}
void CalibrationPanel::init_timer()
@@ -534,6 +535,8 @@ void CalibrationPanel::update_print_error_info(int code, std::string msg, std::s
}
void CalibrationPanel::update_all() {
// Every wizard's pages exist once the last build step has run.
if (!built()) return;
NetworkAgent* m_agent = wxGetApp().getAgent();
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
@@ -597,7 +600,7 @@ void CalibrationPanel::update_all() {
void CalibrationPanel::show_status(int status)
{
if (!m_initialized) return;
if (!built()) return;
if (last_status == status)return;
last_status = status;
+2 -2
View File
@@ -3,6 +3,7 @@
#include "CalibrationWizard.hpp"
#include "Tabbook.hpp"
#include "Lazy.hpp"
//#include "Widgets/SideTools.hpp"
namespace Slic3r { namespace GUI {
@@ -88,7 +89,7 @@ private:
};
class CalibrationPanel : public wxPanel
class CalibrationPanel : public wxPanel, public StagedBuild, public LazyInstance<CalibrationPanel>
{
public:
CalibrationPanel(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
@@ -109,7 +110,6 @@ protected:
int last_status;
bool m_initialized { false };
std::string last_conn_type = "undedefined";
MachineObject* obj{ nullptr };
MachineObject* last_obj { nullptr };
+56 -65
View File
@@ -130,6 +130,15 @@ CalibrationWizard::~CalibrationWizard()
;
}
void CalibrationWizard::add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make)
{
add_build_step([this, &step, make = std::move(make)] {
step = new CalibrationWizardPageStep(make());
m_all_pages_sizer->Add(step->page, 1, wxEXPAND | wxALL, FromDIP(25));
step->page->Hide();
});
}
void CalibrationWizard::on_cali_job_finished(wxCommandEvent& event)
{
this->on_cali_job_finished(event.GetString());
@@ -520,33 +529,28 @@ void PressureAdvanceWizard::on_cali_job_finished(wxString evt_data)
void PressureAdvanceWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationPAStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
save_step = new CalibrationWizardPageStep(new CalibrationPASavePage(m_scrolledWindow));
add_page_step(start_step, [this] { return new CalibrationPAStartPage(m_scrolledWindow); });
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
add_page_step(save_step, [this] { return new CalibrationPASavePage(m_scrolledWindow); });
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
add_build_step([this] {
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_step);
m_page_steps.push_back(save_step);
for (int i = 0; i < m_page_steps.size() -1; i++) {
m_page_steps[i]->chain(m_page_steps[i+1]);
}
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_step);
m_page_steps.push_back(save_step);
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
}
for (int i = 0; i < m_page_steps.size() -1; i++) {
m_page_steps[i]->chain(m_page_steps[i+1]);
}
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Hide();
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
if (!m_page_steps.empty())
show_step(m_page_steps.front());
});
}
void PressureAdvanceWizard::on_cali_action(wxCommandEvent& evt)
@@ -1053,59 +1057,46 @@ FlowRateWizard::FlowRateWizard(wxWindow* parent, wxWindowID id, const wxPoint& p
void FlowRateWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationFlowRateStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
add_page_step(start_step, [this] { return new CalibrationFlowRateStartPage(m_scrolledWindow); });
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
// manual
cali_coarse_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI));
coarse_save_step = new CalibrationWizardPageStep(new CalibrationFlowCoarseSavePage(m_scrolledWindow));
cali_fine_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI));
fine_save_step = new CalibrationWizardPageStep(new CalibrationFlowFineSavePage(m_scrolledWindow));
add_page_step(cali_coarse_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI); });
add_page_step(coarse_save_step, [this] { return new CalibrationFlowCoarseSavePage(m_scrolledWindow); });
add_page_step(cali_fine_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI); });
add_page_step(fine_save_step, [this] { return new CalibrationFlowFineSavePage(m_scrolledWindow); });
// auto
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
save_step = new CalibrationWizardPageStep(new CalibrationFlowX1SavePage(m_scrolledWindow));
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
add_page_step(save_step, [this] { return new CalibrationFlowX1SavePage(m_scrolledWindow); });
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_coarse_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(coarse_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_fine_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(fine_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
add_build_step([this] {
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_coarse_step);
m_page_steps.push_back(coarse_save_step);
m_page_steps.push_back(cali_fine_step);
m_page_steps.push_back(fine_save_step);
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
//m_page_steps.push_back(cali_step);
//m_page_steps.push_back(save_step);
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_coarse_step);
m_page_steps.push_back(coarse_save_step);
m_page_steps.push_back(cali_fine_step);
m_page_steps.push_back(fine_save_step);
for (int i = 0; i < m_page_steps.size() - 1; i++) {
m_page_steps[i]->chain(m_page_steps[i + 1]);
}
//m_page_steps.push_back(cali_step);
//m_page_steps.push_back(save_step);
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
}
for (int i = 0; i < m_page_steps.size() - 1; i++) {
m_page_steps[i]->chain(m_page_steps[i + 1]);
}
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
// hide all pages
cali_step->page->Hide();
save_step->page->Hide();
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Hide();
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
if (!m_page_steps.empty())
show_step(m_page_steps.front());
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
});
}
void FlowRateWizard::on_cali_action(wxCommandEvent& evt)
+5 -1
View File
@@ -9,6 +9,7 @@
#include "CalibrationWizardPresetPage.hpp"
#include "CalibrationWizardCaliPage.hpp"
#include "CalibrationWizardSavePage.hpp"
#include "StagedBuild.hpp"
namespace Slic3r { namespace GUI {
@@ -36,7 +37,7 @@ struct ConfigIndexValue
int index{0};
};
class CalibrationWizard : public wxPanel {
class CalibrationWizard : public wxPanel, public StagedBuild {
public:
CalibrationWizard(wxWindow* parent, CalibMode mode,
wxWindowID id = wxID_ANY,
@@ -79,6 +80,9 @@ public:
protected:
void on_cali_go_home();
// Queues a page as a build step, created hidden and added to the pages sizer.
void add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make);
protected:
/* wx widgets*/
wxScrolledWindow* m_scrolledWindow;
+3 -1
View File
@@ -192,8 +192,10 @@ void CalibrationCaliPage::update(MachineObject* obj)
set_cali_img();
}
// A calibration can run before the Device tab is ever opened, and only its status
// panel shows a print error.
if (obj->print_error > 0) {
StatusPanel* status_panel = Slic3r::GUI::wxGetApp().mainframe->m_monitor->get_status_panel();
StatusPanel* status_panel = MonitorPanel::ensure()->get_status_panel();
status_panel->obj = obj;
status_panel->update_error_message();
}
+6 -3
View File
@@ -538,7 +538,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
break;
}
case DeviceErrorDialog::CHECK_ASSISTANT: {
wxGetApp().mainframe->m_monitor->jump_to_HMS(); // go to assistant page
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_HMS(); // go to assistant page
break;
}
case DeviceErrorDialog::FILAMENT_EXTRUDED: {
@@ -568,7 +569,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
case DeviceErrorDialog::JUMP_TO_LIVEVIEW: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_LiveView();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_LiveView();
break;
}
case DeviceErrorDialog::NO_REMINDER_NEXT_TIME: {
@@ -618,7 +620,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
case DeviceErrorDialog::OK_JUMP_RACK: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Rack();
break;
}
case DeviceErrorDialog::ABORT: {
@@ -934,7 +934,8 @@ void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK) {
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Upgrade();
};
}
}
@@ -344,7 +344,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
if (dlg.ShowModal() == wxID_OK)
{
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Upgrade();
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
evt.SetEventObject(this);
+57 -32
View File
@@ -863,7 +863,7 @@ void GUI_App::post_init()
mainframe->Freeze();
#endif
plater_->canvas3D()->enable_render(false);
mainframe->select_tab(TAB_ID_PREPARE);
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");
@@ -903,10 +903,10 @@ void GUI_App::post_init()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
}
}
if (is_editor())
mainframe->select_tab(TAB_ID_HOME);
if (app_config->get("default_page") == "1")
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
@@ -915,6 +915,7 @@ void GUI_App::post_init()
plater_->trigger_restore_project(1);
//#endif
mainframe->prebuild_pages_when_idle();
//BBS: remove GCodeViewer as seperate APP logic
/*if (this->init_params->start_as_gcodeviewer) {
@@ -1929,9 +1930,9 @@ bool GUI_App::hot_reload_network_plugin()
m_device_manager->add_user_subscribe();
}
if (mainframe && mainframe->m_monitor) {
mainframe->m_monitor->update_network_version_footer();
mainframe->m_monitor->set_default();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->update_network_version_footer();
monitor->set_default();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reset monitor panel";
}
@@ -3422,14 +3423,16 @@ bool GUI_App::on_init_inner()
}
BOOST_LOG_TRIVIAL(info) << "create the main window";
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor()) {
mainframe->select_tab(TAB_ID_HOME);
if (starts_on_prepare()) {
mainframe->select_tab(TAB_ID_PREPARE);
} else {
mainframe->select_tab(TAB_ID_HOME);
}
}
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();
mainframe->m_project->init_auxiliary();
// update_mode(); // !!! do that later
SetTopWindow(mainframe);
@@ -4140,13 +4143,13 @@ void GUI_App::select_machine(const std::string& agent_id)
// Use MonitorPanel::select_machine() to trigger full selection flow
// This reuses existing logic for machine switching (UI updates, callbacks, etc.)
if (mainframe && mainframe->m_monitor) {
mainframe->m_monitor->select_machine(dev_id);
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->select_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": triggered select_machine for dev_id=" << dev_id;
} else {
// Fallback if MonitorPanel not available
m_device_manager->set_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fallback set_selected_machine dev_id=" << dev_id;
} else if (m_device_manager->set_selected_machine(dev_id)) {
// The Device tab's own state is set when the tab is built.
MonitorPanel::on_machine_selected(m_device_manager->get_selected_machine());
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": set_selected_machine dev_id=" << dev_id;
}
}
@@ -4670,6 +4673,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
//BBS: trigger restore project logic here, and skip confirm
plater_->trigger_restore_project(1);
mainframe->prebuild_pages_when_idle();
// #ys_FIXME_delete_after_testing Do we still need this ?
// CallAfter([]() {
@@ -5011,7 +5015,8 @@ void GUI_App::get_login_info(const std::string& provider/* = ORCA_CLOUD_PROVIDER
wxString strJS = wxString::Format("window.postMessage(%s)", from_u8(logout_cmd));
GUI::wxGetApp().run_script(strJS);
}
mainframe->m_webview->SetLoginPanelVisibility(true);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SetLoginPanelVisibility(true);
}
}
@@ -5143,9 +5148,9 @@ std::string GUI_App::handle_web_request(std::string cmd)
"homepage_bambu_login_or_register",
};
if (app_config->get_stealth_mode() && stealth_blocked_info_commands.count(command_str)) {
CallAfter([this] {
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
CallAfter([] {
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
});
return "";
}
@@ -5159,8 +5164,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
if (dlg.ShowModal() == wxID_OK) {
app_config->set_bool("stealth_mode", false);
app_config->save();
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
// Continue with login
if (command_str == "homepage_login_or_register")
this->request_login(true);
@@ -5241,8 +5246,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
}
else if (command_str.compare("get_recent_projects") == 0) {
if (mainframe) {
if (mainframe->m_webview) {
mainframe->m_webview->SendRecentList(INT_MAX);
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendRecentList(INT_MAX);
}
}
}
@@ -7775,8 +7780,8 @@ void GUI_App::on_stealth_mode_enter()
BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter";
request_user_logout(ORCA_CLOUD_PROVIDER);
request_user_logout(BBL_CLOUD_PROVIDER);
if (mainframe && mainframe->m_webview) {
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendCloudProvidersInfo();
}
}
@@ -8186,6 +8191,24 @@ ConfigOptionMode GUI_App::get_saved_mode()
return saved_mode_from_string(app_config->get("user_mode"));
}
bool GUI_App::starts_on_prepare() const
{
return app_config->get("default_page") == "1";
}
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.
int GUI_App::FilterEvent(wxEvent& event)
{
if (!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT))
m_last_input = std::chrono::steady_clock::now();
return Event_Skip;
}
ConfigOptionMode GUI_App::get_mode()
{
return app_config->get_bool("developer_mode") ? comDevelop : get_saved_mode();
@@ -8237,9 +8260,10 @@ void GUI_App::update_mode()
mainframe->m_param_panel->update_mode();
if (mainframe->m_param_dialog)
mainframe->m_param_dialog->panel()->update_mode();
if (mainframe->m_printer_view)
mainframe->m_printer_view->update_mode();
mainframe->m_webview->update_mode();
if (PrinterWebView* view = PrinterWebView::if_built())
view->update_mode();
if (WebViewPanel* home = WebViewPanel::if_built())
home->update_mode();
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
@@ -8257,9 +8281,10 @@ void GUI_App::update_mode()
}
void GUI_App::update_internal_development() {
mainframe->m_webview->update_mode();
if (mainframe->m_printer_view)
mainframe->m_printer_view->update_mode();
if (WebViewPanel* home = WebViewPanel::if_built())
home->update_mode();
if (PrinterWebView* view = PrinterWebView::if_built())
view->update_mode();
}
void GUI_App::show_ip_address_enter_dialog(wxString title)
+7
View File
@@ -1,6 +1,7 @@
#ifndef slic3r_GUI_App_hpp_
#define slic3r_GUI_App_hpp_
#include <chrono>
#include <functional>
#include <memory>
#include <string>
@@ -248,6 +249,7 @@ private:
bool m_app_conf_exists{ false };
EAppMode m_app_mode{ EAppMode::Editor };
bool m_is_recreating_gui{ false };
std::chrono::steady_clock::time_point m_last_input{ std::chrono::steady_clock::now() };
#ifdef __linux__
bool m_opengl_initialized{ false };
#endif
@@ -387,6 +389,11 @@ 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.
int input_idle_ms() const;
int FilterEvent(wxEvent& event) override;
// The Preferences "Default page" choice, stored as its index: 0 Home, 1 Prepare.
bool starts_on_prepare() const;
std::string logo_name() const { return is_editor() ? "OrcaSlicer" : "OrcaSlicer-gcodeviewer"; }
bool is_closing() const { return m_is_closing.load(std::memory_order_acquire); }
+2 -1
View File
@@ -122,7 +122,8 @@ HMSNotifyItem::HMSNotifyItem(const std::string& dev_id, wxWindow *parent, DevHMS
m_hms_content->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
wxCommandEvent evt(EVT_ALREADY_READ_HMS);
evt.SetString(long_error_code);
wxPostEvent(wxGetApp().mainframe->m_monitor, evt);
if (MonitorPanel* monitor = MonitorPanel::if_built())
wxPostEvent(monitor, evt);
if (!m_url.empty()) wxLaunchDefaultBrowser(m_url);
});
+100
View File
@@ -0,0 +1,100 @@
#include "IdleScheduler.hpp"
#include <chrono>
#include <boost/log/trivial.hpp>
#include "libslic3r/Utils.hpp"
#ifdef _WIN32
#include <windows.h>
#endif
namespace Slic3r { namespace GUI {
namespace {
// The timer's period while waiting for the user to go quiet.
constexpr int tick_ms = 250;
// Input-free time before a slice may start, about a double-click interval, so a user
// mid-gesture is left alone.
constexpr int quiet_ms = 500;
// Budget of one slice. The next slice is a timer message, so paint, timers and input
// 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__
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()
{
#ifdef _WIN32
// Windows synthesizes a mouse move whenever a window appears under the cursor, so those
// do not count.
return (GetQueueStatus(QS_INPUT & ~QS_MOUSEMOVE) >> 16) != 0;
#else
return false;
#endif
}
} // namespace
IdleScheduler::IdleScheduler(std::function<int()> input_idle_ms) : m_input_idle_ms(std::move(input_idle_ms))
{
m_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&) { tick(); });
}
void IdleScheduler::start()
{
if (!m_timer.IsRunning())
m_timer.Start(tick_ms);
}
void IdleScheduler::stop()
{
m_timer.Stop();
}
void IdleScheduler::tick()
{
// A unit that pumps the event loop lets the timer fire inside its own slice.
if (m_in_slice)
return;
if (!m_queue.pending()) {
stop();
return;
}
if (m_input_idle_ms() < quiet_ms || input_pending()) {
start();
return;
}
const auto now_ms = [] {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
};
const char* const fn = __FUNCTION__;
m_in_slice = true;
PrebuildQueue::Slice slice;
{
ScopeGuard in_slice([this] { m_in_slice = false; });
slice = m_queue.run_slice(slice_ms, now_ms, input_pending, [fn](const std::string& name, long long ms) {
BOOST_LOG_TRIVIAL(debug) << fn << ": " << name << ": unit took " << ms << " ms";
});
}
if (slice.completed)
BOOST_LOG_TRIVIAL(info) << fn << ": task complete: " << slice.name << ", " << slice.units << " unit(s) in " << slice.ms << " ms this slice";
else
BOOST_LOG_TRIVIAL(debug) << fn << ": " << slice.name << ": " << slice.units << " unit(s) in " << slice.ms << " ms, yielding";
if (!slice.remaining)
stop();
else if (input_pending())
m_timer.Start(tick_ms);
else
m_timer.StartOnce(next_slice_ms);
}
}} // namespace Slic3r::GUI
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <functional>
#include <string>
#include <wx/event.h>
#include <wx/timer.h>
#include "PrebuildQueue.hpp"
namespace Slic3r { namespace GUI {
// Runs the queue's units while the user is idle. Once the user has been idle long enough,
// slices run one per timer message until the work is done or input arrives, so the event
// loop handles what it has between slices. Main thread only.
class IdleScheduler
{
public:
// input_idle_ms reports how long ago the user last touched the mouse or keyboard.
explicit IdleScheduler(std::function<int()> input_idle_ms);
void add(LazyBase& task) { m_queue.add(task); }
void clear() { m_queue.clear(); }
// The task names in queue order, for a log line.
std::string names() const { return m_queue.names(); }
// Arms the timer; it stops itself once no task is pending, so call again when a task
// appears.
void start();
void stop();
private:
void tick();
PrebuildQueue m_queue;
wxTimer m_timer;
std::function<int()> m_input_idle_ms;
bool m_in_slice{ false };
};
}} // namespace Slic3r::GUI
+27
View File
@@ -0,0 +1,27 @@
#include "Lazy.hpp"
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/utils.h>
namespace Slic3r { namespace GUI {
LazyBase::OnDemandBuild::OnDemandBuild(const LazyBase& lazy) : m_lazy(lazy), m_started(std::chrono::steady_clock::now())
{
// The unit tests have no app.
if (wxTheApp != nullptr)
m_busy = std::make_unique<wxBusyCursor>();
}
LazyBase::OnDemandBuild::~OnDemandBuild()
{
BOOST_LOG_TRIVIAL(info) << "Lazy::ensure: built " << m_lazy.name() << " on demand, " << m_units << " unit(s) in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_started).count() << " ms";
}
void LazyBase::log_null_factory(const std::string& name)
{
BOOST_LOG_TRIVIAL(error) << "Lazy::build_step: the factory for " << name << " returned null";
}
}} // namespace Slic3r::GUI
+198
View File
@@ -0,0 +1,198 @@
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "StagedBuild.hpp"
class wxBusyCursor;
// Deferred construction of one object. Lazy<T> holds the factory and builds the object on
// demand or, unit by unit, from an IdleScheduler; LazyBase is what the scheduler sees of
// it; LazyInstance<Self> gives a type with one such object app-wide static access to it.
// docs/HLSD/deferred-page-construction.md covers the design.
namespace Slic3r { namespace GUI {
template <class Self> class LazyInstance;
// A prebuild task: what PrebuildQueue sees of a Lazy<T>. A type with its own unit sequence
// implements it directly.
class LazyBase
{
public:
virtual ~LazyBase() = default;
// Labels the object in logs.
virtual const std::string& name() const = 0;
virtual bool built() const = 0;
// Whether the idle prebuild has work here.
virtual bool pending() const { return !built(); }
// Runs one unit and returns true while more remain.
virtual bool build_step() = 0;
// Position in the idle queue: lower builds first, negative is never prebuilt.
virtual int prebuild_order() const = 0;
protected:
// Busy cursor around a build the user is waiting on, and a log line after it.
class OnDemandBuild
{
public:
explicit OnDemandBuild(const LazyBase& lazy);
~OnDemandBuild();
void unit() { ++m_units; }
private:
const LazyBase& m_lazy;
std::unique_ptr<wxBusyCursor> m_busy;
std::chrono::steady_clock::time_point m_started;
int m_units{ 0 };
};
static void log_null_factory(const std::string& name);
};
// Holds an object a factory makes on the first build_step() or ensure(), followed by one
// StagedBuild step per unit if the type has them, and keeps callbacks until the object is
// complete. The object's parent owns it, not the holder. A LazyInstance type is registered
// for its statics; any other type is reached only through the holder.
template <class T>
class Lazy : public LazyBase
{
public:
using Factory = std::function<T*()>;
Lazy(std::string name, int order, Factory make) : m_name(std::move(name)), m_order(order), m_make(std::move(make))
{
if constexpr (registered())
LazyInstance<T>::s_lazy = this;
}
~Lazy() override
{
if constexpr (registered())
if (LazyInstance<T>::s_lazy == this)
LazyInstance<T>::s_lazy = nullptr;
}
Lazy(const Lazy&) = delete;
Lazy& operator=(const Lazy&) = delete;
// Null until completely built.
T* get() const { return built() ? m_object : nullptr; }
// Builds whatever is left now and returns the object, null if the factory returned null
// or a nested call finds the object mid-build.
T* ensure()
{
if (!built() && !m_building) {
OnDemandBuild build(*this);
do
build.unit();
while (build_step());
}
return get();
}
// Runs fn on the object now if it is built, otherwise once it is.
void when_built(std::function<void(T&)> fn)
{
if (built())
fn(*m_object);
else
m_deferred.push_back(std::move(fn));
}
const std::string& name() const override { return m_name; }
// Read by a worker thread through the statics; the last write in build_step() releases it.
bool built() const override { return m_complete.load(std::memory_order_acquire); }
bool pending() const override { return !built() && !m_failed; }
int prebuild_order() const override { return m_order; }
// The factory is the first unit, then one StagedBuild step each. A unit that pumps the
// event loop cannot re-enter; a nested call does nothing.
bool build_step() override
{
if (built() || m_building || m_failed)
return false;
m_building = true;
try {
if (m_object == nullptr)
m_object = m_make();
else
step();
} catch (...) {
m_building = false;
throw;
}
m_building = false;
if (m_object == nullptr) {
m_failed = true;
log_null_factory(m_name);
return false;
}
if (steps_remain())
return true;
m_complete.store(true, std::memory_order_release);
for (auto& fn : m_deferred)
fn(*m_object);
m_deferred.clear();
return false;
}
private:
static constexpr bool registered() { return std::is_base_of_v<LazyInstance<T>, T>; }
static constexpr bool staged() { return std::is_base_of_v<StagedBuild, T>; }
bool steps_remain() const
{
if constexpr (staged())
return !m_object->built();
else
return false;
}
void step()
{
if constexpr (staged())
m_object->build_step();
}
std::string m_name;
int m_order;
Factory m_make;
T* m_object{ nullptr };
std::atomic<bool> m_complete{ false };
bool m_building{ false };
bool m_failed{ false };
std::vector<std::function<void(T&)>> m_deferred;
};
// Mixin for a type with one lazily built instance in the app. The statics reach that
// instance through its Lazy holder (a recreated MainFrame's holder replaces the old
// frame's).
template <class Self>
class LazyInstance
{
public:
// Null until completely built.
static Self* if_built() { return s_lazy ? s_lazy->get() : nullptr; }
// Builds the object if needed; null while no holder exists or the holder has no
// object.
static Self* ensure() { return s_lazy ? s_lazy->ensure() : nullptr; }
// Runs fn on the object now if it is built, otherwise once it is.
static void when_built(std::function<void(Self&)> fn)
{
if (s_lazy)
s_lazy->when_built(std::move(fn));
}
private:
friend class Lazy<Self>;
inline static Lazy<Self>* s_lazy{ nullptr };
};
}} // namespace Slic3r::GUI
+14
View File
@@ -0,0 +1,14 @@
#include "LazyPage.hpp"
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
void apply_dark_ui_to_lazy_panel(wxWindow* panel)
{
#ifdef _MSW_DARK_MODE
wxGetApp().UpdateDarkUIWin(panel);
#endif
}
}} // namespace Slic3r::GUI
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include <functional>
#include <string>
#include <utility>
#include <wx/bookctrl.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include "Lazy.hpp"
namespace Slic3r { namespace GUI {
// Applies the app's dark-mode pass to a panel built after the frame's own pass ran.
void apply_dark_ui_to_lazy_panel(wxWindow* panel);
// A page whose panel is built the first time the page is shown, or earlier by an
// IdleScheduler; nothing builds while the frame is still hidden. The Lazy side holds the
// panel and, for a LazyInstance panel type, registers it for the type's statics.
template <class Panel>
class LazyPage : public wxPanel, public Lazy<Panel>
{
public:
using Factory = std::function<Panel*(wxWindow* parent)>;
LazyPage(wxWindow* parent, std::string name, int order, Factory make = [](wxWindow* parent) { return new Panel(parent); })
: wxPanel(parent), Lazy<Panel>(std::move(name), order, [this, make = std::move(make)] {
Panel* panel = make(this);
GetSizer()->Add(panel, 1, wxEXPAND);
// Hidden while its page is, as an inserted page would be.
if (!IsShown())
panel->Hide();
return panel;
})
{
SetSizer(new wxBoxSizer(wxVERTICAL));
// Shown by the book when its tab is selected.
Hide();
this->when_built([this](Panel& panel) {
apply_dark_ui_to_lazy_panel(&panel);
Layout();
});
}
// The parent book currently lists this page.
bool in_book() const
{
auto* book = dynamic_cast<wxBookCtrlBase*>(GetParent());
return book != nullptr && book->FindPage(this) != wxNOT_FOUND;
}
// Pending only while the tab is in the book.
bool pending() const override { return in_book() && Lazy<Panel>::pending(); }
// Forwarded so the panel's own Show() override stays its activation hook; wx calls this
// virtual from the book's ShowWithEffect() only for wxSHOW_EFFECT_NONE, the default.
bool Show(bool show = true) override
{
const bool changed = wxPanel::Show(show);
if (show) {
// The book shows its first page as it is inserted, before startup has chosen the
// start page, so a hidden frame builds nothing; MainFrame::Show() completes it.
if (this->built() || wxGetTopLevelParent(this)->IsShown()) {
if (Panel* panel = this->ensure())
panel->Show(true);
// The sizer skipped the panel while the book kept it hidden.
Layout();
}
} else if (Panel* panel = this->get()) {
panel->Show(false);
}
return changed;
}
};
}} // namespace Slic3r::GUI
+157 -170
View File
@@ -317,7 +317,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
// BBS
, m_recent_projects(18)
, m_settings_dialog(this)
, diff_dialog(this)
, m_idle([] { return wxGetApp().input_idle_ms(); })
, m_diff_dialog("compare_presets", 100, [this] { return make_diff_dialog(); })
{
#ifdef __WXOSX__
set_miniaturizable(GetHandle());
@@ -737,9 +738,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
wxGetApp().persist_window_geometry(this, true);
wxGetApp().persist_window_geometry(&m_settings_dialog, true);
// bind events from DiffDlg
bind_diff_dialog();
}
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
@@ -832,8 +830,10 @@ bool MainFrame::handle_global_shortcut(const KeyChord& chord)
return true;
}
void MainFrame::bind_diff_dialog()
DiffPresetDialog* MainFrame::make_diff_dialog()
{
auto* dialog = new DiffPresetDialog(this);
auto get_tab = [](Preset::Type type) {
Tab* null_tab = nullptr;
for (Tab* tab : wxGetApp().tabs_list)
@@ -842,23 +842,24 @@ void MainFrame::bind_diff_dialog()
return null_tab;
};
auto transfer = [this, get_tab](Preset::Type type) {
get_tab(type)->transfer_options(diff_dialog.get_left_preset_name(type),
diff_dialog.get_right_preset_name(type),
diff_dialog.get_selected_options(type));
auto transfer = [dialog, get_tab](Preset::Type type) {
get_tab(type)->transfer_options(dialog->get_left_preset_name(type),
dialog->get_right_preset_name(type),
dialog->get_selected_options(type));
};
auto process_options = [this](std::function<void(Preset::Type)> process) {
const Preset::Type diff_dlg_type = diff_dialog.view_type();
auto process_options = [dialog](std::function<void(Preset::Type)> process) {
const Preset::Type diff_dlg_type = dialog->view_type();
if (diff_dlg_type == Preset::TYPE_INVALID) {
for (const Preset::Type& type : diff_dialog.types_list() )
for (const Preset::Type& type : dialog->types_list() )
process(type);
}
else
process(diff_dlg_type);
};
diff_dialog.Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
dialog->Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
return dialog;
}
@@ -1186,8 +1187,9 @@ void MainFrame::update_edge_panels()
void MainFrame::shutdown()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
if (m_project != nullptr)
m_project->shutdown();
m_idle.stop();
if (ProjectPanel* project = ProjectPanel::if_built())
project->shutdown();
m_plugin_pages.shutdown();
if (m_plater != nullptr)
m_plater->remove_dock_panes();
@@ -1314,19 +1316,6 @@ void MainFrame::show_option(bool show)
}
}
#ifdef SLIC3R_CAD
DesignPanel* MainFrame::ensure_design_panel()
{
if (m_design_panel == nullptr && m_design_page != nullptr) {
wxBusyCursor busy;
m_design_panel = new DesignPanel(m_design_page);
m_design_page->GetSizer()->Add(m_design_panel, 1, wxEXPAND);
m_design_page->Layout();
}
return m_design_panel;
}
#endif
void MainFrame::init_tabpanel() {
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on
// Windows 10 with multiple high resolution displays connected.
@@ -1369,23 +1358,20 @@ void MainFrame::init_tabpanel() {
// m_param_panel->OnActivate();
#ifdef SLIC3R_CAD
else if (m_design_page != nullptr && panel == m_design_page) {
// Built on first activation, never at startup: the panel creates several hundred
// controls and its own GL canvas, which a user who does not open the tab should
// not pay for.
ensure_design_panel();
// Re-sync the Design bed to the active printer: the panel is built before the
// printer profile is fully applied, so its bed must refresh on activation or the
// grid (true bed) spills past the stale default bed quad.
m_design_panel->on_tab_shown();
DesignPanel::ensure()->on_tab_shown();
}
#endif
else if (panel == m_monitor) {
else if (panel == m_monitor_page) {
//monitor
}
#ifdef SLIC3R_CAD
// Any page that is not Design takes the Design status line down with it — see
// DesignPanel::on_tab_hidden for why the popup does not follow the page on its own.
if (m_design_panel != nullptr && panel != m_design_page) m_design_panel->on_tab_hidden();
if (DesignPanel* design = DesignPanel::if_built(); design != nullptr && panel != m_design_page)
design->on_tab_hidden();
#endif
#ifndef __APPLE__
if (m_last_selected_tab == TAB_ID_PREPARE) {
@@ -1401,13 +1387,14 @@ void MainFrame::init_tabpanel() {
});
if (wxGetApp().is_editor()) {
m_webview = new WebViewPanel(m_tabpanel);
m_home_page = new LazyPage<WebViewPanel>(m_tabpanel, TAB_ID_HOME, 10);
m_lazy_pages.push_back(m_home_page);
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
wxString url = evt.GetString();
select_tab(TAB_ID_HOME);
m_webview->load_url(url);
WebViewPanel::ensure()->load_url(url);
});
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
m_tabpanel->AddPage(TAB_ID_HOME, m_home_page, "", "tab_home_active");
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
}
@@ -1418,15 +1405,13 @@ void MainFrame::init_tabpanel() {
wxGetApp().plater_ = m_plater;
#ifdef SLIC3R_CAD
// Stand-in page for the Design tab. The real DesignPanel is built into it the first time
// the tab is selected (see the page-changed handler above), so nothing it constructs sits
// on the startup path. The experimental feature is off by default, and when it is off the
// page is never created, so the tab does not appear at all (the preference takes effect on
// the next start, like the other feature toggles).
// The experimental feature is off by default, and when it is off the page is never
// created, so the tab does not appear at all (the preference takes effect on the next
// start, like the other feature toggles).
if (wxGetApp().is_enable_cad_feature()) {
m_design_page = new wxPanel(this);
m_design_page->SetSizer(new wxBoxSizer(wxVERTICAL));
m_design_page->Hide();
// Experimental and heavy enough that building it unasked would cost more than it saves.
m_design_page = new LazyPage<DesignPanel>(this, TAB_ID_DESIGN, -1);
m_lazy_pages.push_back(m_design_page);
start_mcp_control_if_enabled(); // opens the MCP socket iff ORCA_CAD_MCP is set
}
#endif
@@ -1434,33 +1419,41 @@ void MainFrame::init_tabpanel() {
create_preset_tabs();
//BBS add pages
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
m_monitor_page = new LazyPage<MonitorPanel>(m_tabpanel, TAB_ID_MONITOR, 20);
m_lazy_pages.push_back(m_monitor_page);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor_page, _L("Device"), "tab_monitor_active");
m_printer_view = new PrinterWebView(m_tabpanel);
m_printer_view_page = new LazyPage<PrinterWebView>(m_tabpanel, TAB_ID_MONITOR_WEB, 50, [this](wxWindow* parent) {
auto* view = new PrinterWebView(parent);
if (!m_printer_url.empty())
view->load_url(m_printer_url, m_printer_api_key);
return view;
});
m_lazy_pages.push_back(m_printer_view_page);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
//select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
m_printer_url = evt.GetString();
m_printer_api_key = evt.GetAPIkey();
if (PrinterWebView* view = PrinterWebView::if_built())
view->load_url(m_printer_url, m_printer_api_key);
});
m_printer_view->Hide();
m_multi_machine_page = new LazyPage<MultiMachinePage>(m_tabpanel, TAB_ID_MULTI_DEVICE, 40);
m_lazy_pages.push_back(m_multi_machine_page);
if (wxGetApp().is_enable_multi_machine()) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
// TODO: change the bitmap
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
}
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_project->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
m_project_page = new LazyPage<ProjectPanel>(m_tabpanel, TAB_ID_PROJECT, 60);
m_lazy_pages.push_back(m_project_page);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project_page, _L("Project"), "tab_auxiliary_active");
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
// show_device() removes this tab for printers without the Bambu device tab, and the page
// then never builds its panel.
m_calibration_page = new LazyPage<CalibrationPanel>(m_tabpanel, TAB_ID_CALIBRATION, 30);
m_lazy_pages.push_back(m_calibration_page);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration_page, _L("Calibration"), "tab_calibration_active");
// Plugin pages are appended after the built-in tabs; their ids are namespaced
// (plugin.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
@@ -1495,67 +1488,44 @@ void MainFrame::show_device(bool should_use_native) {
// Remove the extra page before switching to any layout that shouldn't have it.
if (!want_web_device_tab) {
if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
m_printer_view->Show(false);
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
}
if (use_printer_agents) {
if (!m_monitor) {
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
}
if (m_tabpanel->FindPage(m_monitor) == wxNOT_FOUND) {
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
m_printer_view->Show(false);
if (!m_monitor_page->in_book()) {
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
m_monitor_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
_L("Device"), "tab_monitor_active");
}
if (m_printer_view == nullptr) {
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
});
}
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
}
// TODO: change the bitmap
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
m_multi_machine->Show(false);
if (!m_multi_machine_page->in_book()) {
m_multi_machine_page->Show(false);
// Past the web Device tab when it is already there, so enabling multi-machine
// later can't wedge this page between the two Device tabs.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
}
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
m_calibration->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
if (!m_calibration_page->in_book()) {
m_calibration_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
_L("Calibration"), "tab_calibration_active");
}
if (want_web_device_tab) {
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
m_printer_view->Show(false);
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) == wxNOT_FOUND) {
m_printer_view_page->Show(false);
// Immediately right of the native Device tab, not at the end of the tab bar.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
m_printer_view_page, _L("Device (Web)"), "tab_monitor_active");
} else {
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
}
@@ -1567,48 +1537,37 @@ void MainFrame::show_device(bool should_use_native) {
fit_tab_labels(); // ORCA on printer change
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
if (m_prebuild_started)
m_idle.start();
return;
}
if (should_use_native) {
if (m_tabpanel->FindPage(m_monitor) != wxNOT_FOUND) {
if (m_monitor_page->in_book()) {
fit_tab_labels(); // ORCA on printer change - same button layout
return;
}
// Remove printer view
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
m_printer_view->Show(false);
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
// Create/insert monitor page
if (!m_monitor) {
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
// Insert monitor page
m_monitor_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
_L("Device"), "tab_monitor_active");
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
}
// TODO: change the bitmap
m_multi_machine->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
m_multi_machine_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine_page,
_L("Multi-device"), "tab_multi_active");
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
m_calibration->Show(false);
m_calibration_page->Show(false);
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
// append, so its position doesn't depend on the relayout() below running afterwards.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
_L("Calibration"), "tab_calibration_active");
#ifdef _MSW_DARK_MODE
@@ -1616,37 +1575,30 @@ void MainFrame::show_device(bool should_use_native) {
#endif // _MSW_DARK_MODE
} else {
if (m_tabpanel->FindPage(m_printer_view) != wxNOT_FOUND) {
if (m_printer_view_page->in_book()) {
fit_tab_labels(); // ORCA on printer change - same button layout
return;
}
if ((idx = m_tabpanel->FindPage(m_calibration)) != wxNOT_FOUND) {
m_calibration->Show(false);
if ((idx = m_tabpanel->FindPage(m_calibration_page)) != wxNOT_FOUND) {
m_calibration_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if ((idx = m_tabpanel->FindPage(m_multi_machine)) != wxNOT_FOUND) {
m_multi_machine->Show(false);
if ((idx = m_tabpanel->FindPage(m_multi_machine_page)) != wxNOT_FOUND) {
m_multi_machine_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if ((idx = m_tabpanel->FindPage(m_monitor)) != wxNOT_FOUND) {
m_monitor->Show(false);
if ((idx = m_tabpanel->FindPage(m_monitor_page)) != wxNOT_FOUND) {
m_monitor_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if (m_printer_view == nullptr) {
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
});
}
m_printer_view->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
m_printer_view_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view_page,
_L("Device"), "tab_monitor_active");
}
fit_tab_labels(); // ORCA on printer change
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
if (m_prebuild_started)
m_idle.start();
}
bool MainFrame::is_prepare_or_preview_tab() const
@@ -2692,13 +2644,11 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
//BBS GUI refactor: remove unused layout new/dlg
//if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
m_param_panel->msw_rescale();
m_project->msw_rescale();
if(m_monitor)
m_monitor->msw_rescale();
if(m_multi_machine)
m_multi_machine->msw_rescale();
if(m_calibration)
m_calibration->msw_rescale();
// A panel mid-build gets the pass once it is complete.
ProjectPanel::when_built([](ProjectPanel& project) { project.msw_rescale(); });
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.msw_rescale(); });
MultiMachinePage::when_built([](MultiMachinePage& multi_machine) { multi_machine.msw_rescale(); });
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.msw_rescale(); });
// BBS
#if 0
@@ -2752,7 +2702,8 @@ void MainFrame::on_sys_color_changed()
#endif
#endif
diff_dialog.on_sys_color_changed();
if (DiffPresetDialog* dialog = DiffPresetDialog::if_built())
dialog->on_sys_color_changed();
// BBS
m_tabpanel->Rescale();
@@ -2760,10 +2711,8 @@ void MainFrame::on_sys_color_changed()
// update Plater
wxGetApp().plater()->sys_color_changed();
if(m_monitor)
m_monitor->on_sys_color_changed();
if(m_calibration)
m_calibration->on_sys_color_changed();
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.on_sys_color_changed(); });
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.on_sys_color_changed(); });
// update Tabs
for (auto tab : wxGetApp().tabs_list)
tab->sys_color_changed();
@@ -3696,7 +3645,8 @@ void MainFrame::set_max_recent_count(int max)
}
wxGetApp().app_config->set_recent_projects(recent_projects);
wxGetApp().app_config->save();
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
}
@@ -4040,23 +3990,56 @@ void MainFrame::select_tab(wxPanel* panel)
select_tab(page_name);
}
// Selects the Prepare page without the page-changed event, so the GL canvas is on screen
// and nothing else is built for the pass.
void MainFrame::select_prepare_for_gl_init()
{
m_tabpanel->ChangeSelection(m_tabpanel->FindPageByName(TAB_ID_PREPARE));
}
// The book shows its first page as it is inserted, while the frame is hidden and nothing
// may build; the first show completes that page.
bool MainFrame::Show(bool show)
{
const bool changed = DPIFrame::Show(show);
if (show && changed && m_tabpanel != nullptr)
if (wxWindow* page = m_tabpanel->GetCurrentPage())
page->Show(true);
return changed;
}
// 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();
if (m_param_panel)
m_idle.add(m_param_panel->settings_page_prebuild());
for (LazyBase* page : m_lazy_pages)
if (page->prebuild_order() >= 0)
m_idle.add(*page);
m_idle.add(m_diff_dialog);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": prebuild queue: " << m_idle.names();
m_idle.start();
m_prebuild_started = true;
}
//BBS
void MainFrame::jump_to_monitor(std::string dev_id)
{
if(!m_monitor)
return;
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
if (!dev_id.empty()) {
((MonitorPanel*)m_monitor)->select_machine(dev_id);
MonitorPanel::ensure()->select_machine(dev_id);
}
}
void MainFrame::jump_to_multipage()
{
if(!m_multi_machine)
if (!m_multi_machine_page->in_book())
return;
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
if (MultiMachinePage* page = m_multi_machine_page->ensure())
page->jump_to_send_page();
}
@@ -4105,8 +4088,8 @@ void MainFrame::request_select_tab(const wxString& id)
}
int MainFrame::get_calibration_curr_tab() {
if (m_calibration)
return m_calibration->get_tabpanel()->GetSelection();
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
return calibration->get_tabpanel()->GetSelection();
return -1;
}
@@ -4215,7 +4198,8 @@ void MainFrame::add_to_recent_projects(const wxString& filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(0);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(0);
}
}
@@ -4331,7 +4315,8 @@ void MainFrame::open_recent_project(size_t file_id, wxString const & filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
}
}
@@ -4354,7 +4339,8 @@ void MainFrame::remove_recent_project(size_t file_id, wxString const &filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
void MainFrame::load_url(wxString url)
@@ -4408,14 +4394,14 @@ bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName
void MainFrame::refresh_plugin_tips()
{
if (m_webview != nullptr)
m_webview->ShowNetpluginTip();
if (WebViewPanel* home = WebViewPanel::if_built())
home->ShowNetpluginTip();
}
void MainFrame::RunScript(wxString js)
{
if (m_webview != nullptr)
m_webview->RunScript(js);
if (WebViewPanel* home = WebViewPanel::if_built())
home->RunScript(js);
}
void MainFrame::technology_changed()
@@ -4524,7 +4510,8 @@ void MainFrame::update_side_preset_ui()
//take off multi machine
if(m_multi_machine){m_multi_machine->clear_page();}
if (MultiMachinePage* multi_machine = MultiMachinePage::if_built())
multi_machine->clear_page();
}
void MainFrame::on_select_default_preset(SimpleEvent& evt)
+27 -17
View File
@@ -26,6 +26,8 @@
#include "Widgets/SideButton.hpp"
#include "Widgets/SideMenuPopup.hpp"
#include "FilamentGroupPopup.hpp"
#include "LazyPage.hpp"
#include "IdleScheduler.hpp"
#include <boost/property_tree/ptree_fwd.hpp>
@@ -136,6 +138,13 @@ class MainFrame : public DPIFrame
#endif
bool m_loaded {false};
wxTimer* m_reset_title_text_colour_timer{ nullptr };
IdleScheduler m_idle;
bool m_prebuild_started{ false };
// 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.
wxString m_printer_url;
wxString m_printer_api_key;
wxString m_qs_last_input_file = wxEmptyString;
wxString m_qs_last_output_file = wxEmptyString;
@@ -180,7 +189,7 @@ class MainFrame : public DPIFrame
bool can_delete() const;
bool can_delete_all() const;
bool can_reslice() const;
void bind_diff_dialog();
DiffPresetDialog* make_diff_dialog();
// BBS
wxBoxSizer* create_side_tools();
@@ -377,6 +386,12 @@ public:
void select_tab(wxPanel* panel);
void select_tab(const wxString& id = wxString());
void request_select_tab(const wxString& id);
// post_init() needs the plater's canvas on screen to initialize OpenGL; this pass does not
// build the settings page.
void select_prepare_for_gl_init();
// Builds the lazy tab pages while the user is idle; post_init() calls it once.
void prebuild_pages_when_idle();
bool Show(bool show = true) override;
int get_calibration_curr_tab();
void select_view(const std::string& direction);
void update_shortcut_labels();
@@ -435,27 +450,21 @@ public:
BBLTopbar* m_topbar{ nullptr };
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr };
// Lazy pages, created once and kept for the frame's life; their panels are reached
// through LazyInstance's statics, and show_device() only moves pages in and out of the book.
#ifdef SLIC3R_CAD
// The tab page is the placeholder; m_design_panel stays null until the tab is first
// selected, so everything the Design panel builds stays off the startup path.
wxPanel* m_design_page { nullptr };
DesignPanel* m_design_panel { nullptr };
// Builds the Design panel if it does not exist yet and returns it (null only before the
// placeholder page itself exists). Main thread only -- it creates wx controls. Both the
// tab activation and the MCP socket go through this: the socket is driven headlessly,
// with nobody to click the tab, and without this every verb would answer "not ready".
DesignPanel* ensure_design_panel();
LazyPage<DesignPanel>* m_design_page { nullptr };
#endif
//BBS: GUI refactor
MonitorPanel* m_monitor{ nullptr };
LazyPage<MonitorPanel>* m_monitor_page{ nullptr };
//AuxiliaryPanel* m_auxiliary{ nullptr };
MultiMachinePage* m_multi_machine{ nullptr };
ProjectPanel* m_project{ nullptr };
LazyPage<MultiMachinePage>* m_multi_machine_page{ nullptr };
LazyPage<ProjectPanel>* m_project_page{ nullptr };
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
PrinterWebView* m_printer_view{nullptr};
LazyPage<CalibrationPanel>* m_calibration_page{ nullptr };
LazyPage<WebViewPanel>* m_home_page { nullptr };
LazyPage<PrinterWebView>* m_printer_view_page{nullptr};
PluginPages m_plugin_pages;
wxLogWindow* m_log_window { nullptr };
// BBS
@@ -466,7 +475,8 @@ public:
ParamsDialog* m_param_dialog{ nullptr };
//BBS
SettingsDialog m_settings_dialog;
DiffPresetDialog diff_dialog;
// The Compare presets dialog, built on first use or at idle through its holder.
Lazy<DiffPresetDialog> m_diff_dialog;
wxWindow* m_plater_page{ nullptr };
PrintHostQueueDialog* m_printhost_queue_dlg;
+44 -37
View File
@@ -103,6 +103,7 @@ MonitorPanel::MonitorPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos,
: wxPanel(parent, id, pos, size, style),
m_select_machine(SelectMachinePopup(this))
{
SetBackgroundColour(*wxWHITE);
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
@@ -180,31 +181,36 @@ void MonitorPanel::init_tabpanel()
auto title = m_tabpanel->GetPageText(m_tabpanel->GetSelection());
m_media_file_panel->SwitchStorage(title == _L("Storage"));
}
page->SetFocus();
// The first page is selected while the panel is built off screen.
if (page->IsShownOnScreen())
page->SetFocus();
update_all();
}, m_tabpanel->GetId());
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
m_status_info_panel = new StatusPanel(m_tabpanel);
m_status_info_panel = new StatusPanel(m_tabpanel);
// Queued before the page is added, since adding it selects it and runs the handler above,
// where built() must already be false.
add_build_steps_of(*m_status_info_panel);
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
add_build_step([this] {
m_media_file_panel = new MediaFilePanel(m_tabpanel);
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
});
add_build_step([this] {
m_upgrade_panel = new UpgradePanel(m_tabpanel);
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
});
add_build_step([this] {
m_hms_panel = new HMSPanel(m_tabpanel);
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
m_media_file_panel = new MediaFilePanel(m_tabpanel);
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
std::string network_ver = Slic3r::NetworkAgent::get_version();
if (!network_ver.empty()) {
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
}
m_upgrade_panel = new UpgradePanel(m_tabpanel);
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
m_hms_panel = new HMSPanel(m_tabpanel);
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
std::string network_ver = Slic3r::NetworkAgent::get_version();
if (!network_ver.empty()) {
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
}
m_initialized = true;
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
});
}
void MonitorPanel::set_default()
@@ -288,19 +294,23 @@ void MonitorPanel::on_select_printer(wxCommandEvent& event)
set_default();
update_all();
MachineObject *obj_ = dev->get_selected_machine();
if (obj_) {
obj_->last_cali_version = -1;
obj_->reset_pa_cali_history_result();
obj_->reset_pa_cali_result();
Sidebar &sidebar = GUI::wxGetApp().sidebar();
sidebar.update_sync_status(obj_);
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj_));
}
on_machine_selected(dev->get_selected_machine());
Layout();
}
void MonitorPanel::on_machine_selected(MachineObject* obj)
{
if (obj == nullptr)
return;
obj->last_cali_version = -1;
obj->reset_pa_cali_history_result();
obj->reset_pa_cali_result();
Sidebar& sidebar = GUI::wxGetApp().sidebar();
sidebar.update_sync_status(obj);
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj));
}
void MonitorPanel::on_printer_clicked(wxMouseEvent &event)
{
auto mouse_pos = ClientToScreen(event.GetPosition());
@@ -331,7 +341,8 @@ void MonitorPanel::on_size(wxSizeEvent &event)
void MonitorPanel::update_all()
{
if (!m_initialized)
// Every page exists once the last build step has run.
if (!built())
return;
NetworkAgent* m_agent = wxGetApp().getAgent();
@@ -412,16 +423,12 @@ void MonitorPanel::update_hms_tag()
bool MonitorPanel::Show(bool show)
{
#ifdef __APPLE__
// Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
// still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
if (wxGetApp().mainframe)
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
NetworkAgent* m_agent = wxGetApp().getAgent();
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (show) {
#ifdef __APPLE__
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
start_update();
update_network_version_footer();
@@ -448,7 +455,7 @@ bool MonitorPanel::Show(bool show)
void MonitorPanel::show_status(int status)
{
if (!m_initialized) return;
if (!built()) return;
if (last_status == status)return;
if ((last_status & (int)MonitorStatus::MONITOR_CONNECTING) != 0) {
NetworkAgent* agent = wxGetApp().getAgent();
+8 -6
View File
@@ -49,6 +49,7 @@
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#include "SelectMachinePop.hpp"
#include "Lazy.hpp"
namespace Slic3r {
namespace GUI {
@@ -72,16 +73,16 @@ public:
void msw_rescale();
};
class MonitorPanel : public wxPanel
class MonitorPanel : public wxPanel, public StagedBuild, public LazyInstance<MonitorPanel>
{
private:
Tabbook* m_tabpanel{ nullptr };
wxSizer* m_main_sizer{ nullptr };
StatusPanel* m_status_info_panel;
MediaFilePanel* m_media_file_panel;
UpgradePanel* m_upgrade_panel;
HMSPanel* m_hms_panel;
StatusPanel* m_status_info_panel{ nullptr };
MediaFilePanel* m_media_file_panel{ nullptr };
UpgradePanel* m_upgrade_panel{ nullptr };
HMSPanel* m_hms_panel{ nullptr };
/* side tools */
SideTools* m_side_tools{nullptr};
@@ -96,7 +97,6 @@ private:
wxBitmap m_arrow_img;
int last_status;
bool m_initialized { false };
bool update_flag{false};
wxTimer* m_refresh_timer = nullptr;
@@ -125,6 +125,8 @@ public:
StatusPanel* get_status_panel() {return m_status_info_panel;};
void select_machine(std::string machine_sn);
// Resets the selected printer's calibration results and syncs the sidebar, without the Device tab.
static void on_machine_selected(MachineObject* obj);
void on_timer(wxTimerEvent& event);
void on_select_printer(wxCommandEvent& event);
void on_printer_clicked(wxMouseEvent &event);
+3 -2
View File
@@ -21,8 +21,9 @@ MultiMachineItem::MultiMachineItem(wxWindow* parent, MachineObject* obj)
Bind(wxEVT_MOTION, &MultiMachineItem::OnMove, this);
Bind(EVT_MULTI_DEVICE_VIEW, [obj](auto& e) {
wxGetApp().mainframe->jump_to_monitor(obj->get_dev_id());
if (wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()) {
wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
MonitorPanel* monitor = MonitorPanel::if_built();
if (monitor && monitor->get_status_panel()->get_media_play_ctrl()) {
monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
}
});
wxGetApp().UpdateDarkUIWin(this);
+1
View File
@@ -11,6 +11,7 @@ namespace GUI {
MultiMachinePage::MultiMachinePage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
init_tabpanel();
m_main_sizer = new wxBoxSizer(wxHORIZONTAL);
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxLEFT, 0);
+2 -1
View File
@@ -7,6 +7,7 @@
#include "MultiTaskManagerPage.hpp"
#include "MultiMachineManagerPage.hpp"
#include "Tabbook.hpp"
#include "Lazy.hpp"
#include "wx/button.h"
@@ -19,7 +20,7 @@ namespace GUI {
#define PICK_LEFT_DEV_STATUS 250
#define PICK_DEVICE_MAX 6
class MultiMachinePage : public wxPanel
class MultiMachinePage : public wxPanel, public LazyInstance<MultiMachinePage>
{
private:
wxTimer* m_refresh_timer = nullptr;
+26 -8
View File
@@ -290,7 +290,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
m_compare_btn = new ScalableButton(m_top_panel, wxID_ANY, "compare", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
m_compare_btn->SetToolTip(_L("Compare presets"));
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { wxGetApp().mainframe->diff_dialog.show(); }));
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { DiffPresetDialog::ensure()->show(); }));
m_setting_btn = new ScalableButton(m_top_panel, wxID_ANY, "table", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
m_setting_btn->SetToolTip(_L("View all object's settings"));
@@ -550,18 +550,36 @@ void ParamsPanel::clear_page()
void ParamsPanel::OnActivate()
{
if (m_current_tab == NULL)
{
//the first time
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
// BBS: open/close tab
//m_current_tab = m_tab_print;
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
}
select_default_tab();
Tab* cur_tab = dynamic_cast<Tab *> (m_current_tab);
if (cur_tab)
cur_tab->OnActivate();
}
void ParamsPanel::select_default_tab()
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
// BBS: open/close tab
//m_current_tab = m_tab_print;
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
}
bool ParamsPanel::SettingsPagePrebuild::built() const
{
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
return tab != nullptr && !tab->page_build_pending();
}
bool ParamsPanel::SettingsPagePrebuild::build_step()
{
if (m_panel.m_current_tab == nullptr) {
m_panel.select_default_tab();
return !built();
}
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
return tab != nullptr && tab->page_build_step();
}
void ParamsPanel::OnToggled(wxCommandEvent& event)
{
if (m_mode_region && m_mode_region->GetId() == event.GetId()) {
+21
View File
@@ -30,6 +30,7 @@
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "Widgets/Button.hpp"
#include "Lazy.hpp"
class ModeSwitchButton;
class SwitchButton;
@@ -121,6 +122,22 @@ 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.
class SettingsPagePrebuild : public LazyBase
{
public:
explicit SettingsPagePrebuild(ParamsPanel& panel) : m_panel(panel) {}
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:
ParamsPanel& m_panel;
std::string m_name{ "settings_page" };
} m_settings_page_prebuild{ *this };
bool m_has_object_config { false };
struct Highlighter
@@ -149,6 +166,10 @@ class ParamsPanel : public wxPanel
//clear the right page
void clear_page();
void OnActivate();
// The print tab, or the filament tab without one.
void select_default_tab();
// The settings page's prebuild task.
LazyBase& settings_page_prebuild() { return m_settings_page_prebuild; }
void set_active_tab(wxPanel*tab);
bool is_active_and_shown_tab(wxPanel*tab);
void update_mode();
+17 -16
View File
@@ -10412,8 +10412,8 @@ void Plater::priv::reset(bool apply_presets_change, bool reload_presets)
// Same reason, one level up: the Design tab keeps the editable document, not the Model, so
// clearing the recipe alone leaves the tab showing the previous project's feature tree —
// and its next edit syncs that tree straight back into the new project.
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->clear_document();
if (DesignPanel* design = DesignPanel::if_built())
design->clear_document();
#endif
assemble_view->get_canvas3d()->reset_explosion_ratio();
update();
@@ -13184,17 +13184,17 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
// Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
// while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
// TAB_ID_MONITOR itself.
const bool selecting_web_device_tab = main_frame->m_printer_view &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
const bool selecting_web_device_tab = main_frame->m_printer_view_page &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view_page;
if (selecting_web_device_tab) {
// Use the selected discovered machine when the preset has no host.
main_frame->load_printer_url();
} else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
if (main_frame->m_printer_view && url.empty()) {
if (PrinterWebView* view = PrinterWebView::if_built(); view != nullptr && url.empty()) {
// It's missing_connection page, reload so that we can replay the gif image
main_frame->m_printer_view->reload();
view->reload();
}
}
}
@@ -13925,8 +13925,8 @@ void Plater::priv::unbind_canvas_event_handlers()
// The Design tab's viewport is a fourth GLCanvas3D on the same shared GL context, owned by
// MainFrame rather than by us — same reach as reset() uses for clear_document(). Null until
// the tab has been opened once, so most sessions skip it.
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->unbind_canvas_event_handlers();
if (DesignPanel* design = DesignPanel::if_built())
design->unbind_canvas_event_handlers();
#endif
}
@@ -13939,8 +13939,8 @@ void Plater::priv::reset_canvas_volumes()
preview->get_canvas3d()->reset_volumes();
#ifdef SLIC3R_CAD
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->reset_canvas_volumes();
if (DesignPanel* design = DesignPanel::if_built())
design->reset_canvas_volumes();
#endif
}
@@ -20002,7 +20002,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
{
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
auto calibration_panel = p->main_frame->m_calibration;
CalibrationPanel* calibration_panel = CalibrationPanel::ensure();
if (calibration_panel) {
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
wxCommandEvent event(EVT_CALIBRATION_JOB_FINISHED);
@@ -20034,8 +20034,8 @@ void Plater::print_job_finished(wxCommandEvent &evt)
dev->set_selected_machine(evt.GetString().ToStdString());
p->main_frame->request_select_tab(TAB_ID_MONITOR);
//jump to monitor and select device status panel
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
// Selects the status page on a built Device tab; one built by the switch starts there.
MonitorPanel* curr_monitor = MonitorPanel::if_built();
if(curr_monitor)
curr_monitor->get_tabpanel()->ChangeSelection(MonitorPanel::PrinterTab::PT_STATUS);
}
@@ -20775,8 +20775,8 @@ void Plater::update_print_error_info(int code, std::string msg, std::string extr
if (p->m_send_to_sdcard_dlg) {
p->m_send_to_sdcard_dlg->update_print_error_info(code, msg, extra);
}
if (p->main_frame->m_calibration)
p->main_frame->m_calibration->update_print_error_info(code, msg, extra);
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
calibration->update_print_error_info(code, msg, extra);
}
wxString Plater::get_project_filename(const wxString& extension) const
@@ -21069,7 +21069,8 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
{
printer_name.Replace("Bambu Lab", "", false);
wxString content;
bool device_page = (wxGetApp().mainframe == nullptr) && (wxGetApp().mainframe->m_monitor->IsShown());
MainFrame* frame = wxGetApp().mainframe;
bool device_page = frame != nullptr && frame->m_monitor_page->in_book();
if (type == PrinterWarningType::NOT_CONNECTED) {
if (device_page) {
content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."),
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include "Lazy.hpp"
namespace Slic3r { namespace GUI {
// Holders to build at idle, ordered by prebuild_order(), run one slice at a time. The
// holders outlive the queue. Main thread only.
class PrebuildQueue
{
public:
struct Slice
{
std::string name; // the task that ran; empty when nothing was pending
int units{ 0 }; // units run
long long ms{ 0 }; // time spent
bool completed{ false }; // the task has no more units
bool remaining{ false }; // some task is still pending
};
// Lower order runs first, equal order in the order added.
void add(LazyBase& task)
{
auto after = std::find_if(m_tasks.begin(), m_tasks.end(), [&](const LazyBase* t) { return t->prebuild_order() > task.prebuild_order(); });
m_tasks.insert(after, &task);
}
void clear() { m_tasks.clear(); }
bool pending() const
{
return std::any_of(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
}
// The task names in queue order, comma separated.
std::string names() const
{
std::string out;
for (const LazyBase* t : m_tasks)
out += (out.empty() ? "" : ", ") + t->name();
return out;
}
// Runs units of the first pending task until it completes, budget_ms of now_ms() have
// passed, or interrupt() is true after a unit; on_unit gets the task name and each
// unit's duration. A task with no work is passed over and stays in the queue, so one
// whose work returns is pending again.
Slice run_slice(int budget_ms,
const std::function<long long()>& now_ms,
const std::function<bool()>& interrupt,
const std::function<void(const std::string&, long long)>& on_unit = {})
{
Slice slice;
auto next = std::find_if(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
if (next == m_tasks.end())
return slice;
// The pointer is copied out, since a unit may add tasks and reallocate m_tasks.
LazyBase* const task = *next;
slice.name = task->name();
const long long started = now_ms();
for (;;) {
const long long unit_started = now_ms();
const bool more = task->build_step();
const long long now = now_ms();
++slice.units;
if (on_unit)
on_unit(slice.name, now - unit_started);
slice.ms = now - started;
if (!more) {
slice.completed = true;
break;
}
if (slice.ms >= budget_ms || interrupt())
break;
}
slice.remaining = pending();
return slice;
}
private:
std::vector<LazyBase*> m_tasks;
};
}} // namespace Slic3r::GUI
+4 -5
View File
@@ -1044,8 +1044,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
}
else if (param == "hide_login_side_panel") {
if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) {
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendCloudProvidersInfo();
}
}
// ORCA: apply the preview dimming change immediately to the currently loaded preview
@@ -1254,9 +1254,8 @@ wxBoxSizer *PreferencesDialog::create_item_bambu_cloud(wxString title, wxString
app_config->save();
// Update homepage visibility immediately
auto *mainframe = wxGetApp().mainframe;
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
});
m_sizer->Add(cb, 0, wxALIGN_CENTER);
+2 -1
View File
@@ -26,6 +26,7 @@
#include "wx/textctrl.h"
#include <wx/timer.h>
#include <memory>
#include "Lazy.hpp"
namespace Slic3r {
@@ -34,7 +35,7 @@ namespace GUI {
class PrinterWebViewHandler;
class PrinterWebView : public wxPanel {
class PrinterWebView : public wxPanel, public LazyInstance<PrinterWebView> {
public:
PrinterWebView(wxWindow *parent);
virtual ~PrinterWebView();
+2
View File
@@ -44,6 +44,7 @@ const std::vector<std::string> license_list = {
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "")
@@ -67,6 +68,7 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
m_auxiliary = new AuxiliaryPanel(this);
m_auxiliary->Hide();
main_sizer->Add(m_auxiliary, wxSizerFlags().Expand().Proportion(1));
add_build_steps_of(*m_auxiliary);
Bind(EVT_AUXILIARY_DONE, [this](wxCommandEvent& e) { update_model_data();});
SetSizer(main_sizer);
+2 -2
View File
@@ -35,6 +35,7 @@
#include "libslic3r/ProjectTask.hpp"
#include "wxExtensions.hpp"
#include "Auxiliary.hpp"
#include "Lazy.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
@@ -59,7 +60,7 @@ struct project_file{
std::string size;
};
class ProjectPanel : public wxPanel
class ProjectPanel : public wxPanel, public StagedBuild, public LazyInstance<ProjectPanel>
{
private:
std::atomic<bool> m_web_init_completed{false};
@@ -90,7 +91,6 @@ public:
void msw_rescale();
void update_model_data();
void clear_model_info();
void init_auxiliary() { m_auxiliary->init_auxiliary(); }
bool Show(bool show);
void OnScriptMessage(wxWebViewEvent& evt);
+1 -1
View File
@@ -3470,7 +3470,7 @@ void SelectMachineDialog::navigate_to_timelapse_page()
main_frame->jump_to_monitor();
// then switch to Storage (Media) tab inside Monitor
auto* monitor = dynamic_cast<MonitorPanel*>(main_frame->m_monitor);
MonitorPanel* monitor = MonitorPanel::if_built();
if (monitor) {
auto* tabpanel = monitor->get_tabpanel();
if (tabpanel) {
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <algorithm>
#include <functional>
#include <vector>
namespace Slic3r { namespace GUI {
// A panel whose constructor builds the skeleton and queues the rest as steps, run in order
// on the main thread, one at a time.
class StagedBuild
{
public:
virtual ~StagedBuild() = default;
bool built() const
{
return m_next_step == m_steps.size() &&
std::all_of(m_children.begin(), m_children.end(), [](const StagedBuild* child) { return child->built(); });
}
// Runs one step and returns true while more remain. Own steps first, then whatever a
// child queued after its steps were forwarded.
bool build_step()
{
if (m_next_step < m_steps.size()) {
// Copied out, since a step may queue more steps and reallocate m_steps.
auto step = std::move(m_steps[m_next_step++]);
step();
} else if (StagedBuild* child = unfinished_child()) {
child->build_step();
}
return !built();
}
protected:
void add_build_step(std::function<void()> step) { m_steps.push_back(std::move(step)); }
// Queues one step per step the child has now, and keeps the child so that built() waits
// for any it queues later.
void add_build_steps_of(StagedBuild& child)
{
m_children.push_back(&child);
for (size_t i = child.m_next_step; i < child.m_steps.size(); ++i)
add_build_step([&child] { child.build_step(); });
}
private:
StagedBuild* unfinished_child() const
{
auto it = std::find_if(m_children.begin(), m_children.end(), [](const StagedBuild* child) { return !child->built(); });
return it == m_children.end() ? nullptr : *it;
}
std::vector<std::function<void()>> m_steps;
std::vector<StagedBuild*> m_children;
size_t m_next_step{ 0 };
};
}} // namespace Slic3r::GUI
+99 -66
View File
@@ -1328,18 +1328,21 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
wxBoxSizer *bSizer_left = new wxBoxSizer(wxVERTICAL);
auto m_monitoring_sizer = create_monitoring_page();
bSizer_left->Add(m_monitoring_sizer, 1, wxEXPAND | wxALL, 0);
// The sizers are nested here so each step only appends to its own.
add_build_step([this, bSizer_left] {
auto m_monitoring_sizer = create_monitoring_page();
bSizer_left->Add(m_monitoring_sizer, 1, wxEXPAND | wxALL, 0);
auto m_panel_separotor1 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_panel_separotor1->SetBackgroundColour(STATUS_PANEL_BG);
m_panel_separotor1->SetMinSize(wxSize(-1, PAGE_SPACING));
m_panel_separotor1->SetMaxSize(wxSize(-1, PAGE_SPACING));
m_monitoring_sizer->Add(m_panel_separotor1, 0, wxEXPAND, 0);
auto m_panel_separotor1 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_panel_separotor1->SetBackgroundColour(STATUS_PANEL_BG);
m_panel_separotor1->SetMinSize(wxSize(-1, PAGE_SPACING));
m_panel_separotor1->SetMaxSize(wxSize(-1, PAGE_SPACING));
m_monitoring_sizer->Add(m_panel_separotor1, 0, wxEXPAND, 0);
m_project_task_panel = new PrintingTaskPanel(this, PrintingTaskType::PRINGINT);
m_project_task_panel->init_bitmaps();
m_monitoring_sizer->Add(m_project_task_panel, 0, wxALL | wxEXPAND , 0);
m_project_task_panel = new PrintingTaskPanel(this, PrintingTaskType::PRINGINT);
m_project_task_panel->init_bitmaps();
m_monitoring_sizer->Add(m_project_task_panel, 0, wxALL | wxEXPAND , 0);
});
// auto m_panel_separotor2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
// m_panel_separotor2->SetBackgroundColour(STATUS_PANEL_BG);
@@ -1359,8 +1362,10 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_machine_ctrl_panel->SetDoubleBuffered(true);
auto m_machine_control = create_machine_control_page(m_machine_ctrl_panel);
m_machine_ctrl_panel->SetSizer(m_machine_control);
m_machine_ctrl_panel->Layout();
m_machine_control->Fit(m_machine_ctrl_panel);
add_build_step([this, m_machine_control] {
m_machine_ctrl_panel->Layout();
m_machine_control->Fit(m_machine_ctrl_panel);
});
bSizer_status_below->Add(m_machine_ctrl_panel, 0, wxALL, 0);
@@ -1375,8 +1380,11 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_panel_separotor_bottom->SetBackgroundColour(STATUS_PANEL_BG);
bSizer_status->Add(m_panel_separotor_bottom, 0, wxEXPAND | wxALL, 0);
this->SetSizerAndFit(bSizer_status);
this->Layout();
this->SetSizer(bSizer_status);
add_build_step([this, bSizer_status] {
bSizer_status->SetSizeHints(this);
this->Layout();
});
}
StatusBasePanel::~StatusBasePanel()
@@ -1614,16 +1622,24 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
wxBoxSizer *bSizer_control = new wxBoxSizer(wxVERTICAL);
auto temp_axis_ctrl_sizer = create_temp_axis_group(parent);
auto m_filament_load_sizer = create_filament_group(parent);
// The slot sizers keep each group's place in bSizer_control.
wxBoxSizer *temp_axis_slot = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *filament_slot = new wxBoxSizer(wxVERTICAL);
/* ams control box or live nozzle-rack panel (rack printers switch between the two) */
wxSizer *ams_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
m_panel_nozzle_rack = new wgtDeviceNozzleRack(parent);
m_panel_nozzle_rack->Show(false);
ams_rack_sizer->Add(m_panel_nozzle_rack, 0, wxEXPAND | wxLEFT);
add_build_step([this, parent, temp_axis_slot, filament_slot] {
temp_axis_slot->Add(create_temp_axis_group(parent), 0, wxEXPAND);
filament_slot->Add(create_filament_group(parent), 0, wxEXPAND);
});
add_build_step([this, parent, ams_rack_sizer] {
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
});
add_build_step([this, parent, ams_rack_sizer] {
m_panel_nozzle_rack = new wgtDeviceNozzleRack(parent);
m_panel_nozzle_rack->Show(false);
ams_rack_sizer->Add(m_panel_nozzle_rack, 0, wxEXPAND | wxLEFT);
});
m_ams_rack_switch = new SwitchBoard(parent, _L("Filament"), _L("Hotends"), wxSize(FromDIP(126), FromDIP(26)));
m_ams_rack_switch->updateState("left");
@@ -1631,12 +1647,12 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
m_ams_rack_switch->Bind(wxCUSTOMEVT_SWITCH_POS, &StatusBasePanel::on_ams_rack_switch, this);
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(8));
bSizer_control->Add(temp_axis_ctrl_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(temp_axis_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(m_ams_rack_switch, 0, wxALIGN_CENTRE|wxTOP, FromDIP(6));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(ams_rack_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(m_filament_load_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(filament_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(4));
bSizer_right->Add(bSizer_control, 1, wxEXPAND | wxALL, 0);
@@ -2228,8 +2244,10 @@ void StatusBasePanel::expand_filament_loading(wxMouseEvent& e)
m_filament_step->Show(tag_show);
Layout();
Fit();
wxGetApp().mainframe->m_monitor->get_status_panel()->Layout();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->get_status_panel()->Layout();
monitor->Layout();
}
}
void StatusBasePanel::show_ams_group(bool show)
@@ -2240,7 +2258,8 @@ void StatusBasePanel::show_ams_group(bool show)
m_ams_control->Fit();
Layout();
Fit();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->Layout();
}
// On rack printers, don't clobber the rack view when the user has the switch on "Hotends".
@@ -2253,7 +2272,8 @@ void StatusBasePanel::show_ams_group(bool show)
m_ams_control->Fit();
Layout();
Fit();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->Layout();
}
}
@@ -2277,8 +2297,10 @@ void StatusBasePanel::show_filament_load_group(bool show)
Layout();
Fit();
wxGetApp().mainframe->m_monitor->get_status_panel()->Layout();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->get_status_panel()->Layout();
monitor->Layout();
}
}
}
@@ -2403,6 +2425,12 @@ void StatusPanel::update_camera_state(MachineObject* obj)
StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
: StatusBasePanel(parent, id, pos, size, style)
{
// Wires the controls the base class builds in steps, so it is the last step.
add_build_step([this] { wire_controls(); });
}
void StatusPanel::wire_controls()
{
init_scaled_buttons();
m_buttons.push_back(m_bpButton_z_10);
@@ -2514,47 +2542,50 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
StatusPanel::~StatusPanel()
{
// Disconnect Events
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
// The controls only exist once the last build step has run.
if (built()) {
// Disconnect Events
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_kill_focus), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_set_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_kill_focus), NULL, this);
m_tempCtrl_bed->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_set_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this);
m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this);
m_switch_lamp->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_lamp_switch), NULL, this);
/*m_switch_nozzle_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_printing_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_cham_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);*/
m_switch_lamp->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_lamp_switch), NULL, this);
/*m_switch_nozzle_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_printing_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_cham_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);*/
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
//m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_switch_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this);
m_bpButton_xy->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_xy), NULL, this);
m_bpButton_z_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_10), NULL, this);
m_bpButton_z_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_1), NULL, this);
m_bpButton_z_down_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_1), NULL, this);
m_bpButton_z_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_10), NULL, this);
m_bpButton_e_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_up_10), NULL, this);
m_bpButton_e_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_down_10), NULL, this);
m_nozzle_btn_panel->Disconnect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this);
m_switch_speed->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_switch_speed), NULL, this);
m_calibration_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_start_calibration), NULL, this);
m_options_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_print_options), NULL, this);
m_safety_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_safety_options), NULL, this);
m_parts_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_parts_options), NULL, this);
m_bpButton_xy->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_xy), NULL, this);
m_bpButton_z_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_10), NULL, this);
m_bpButton_z_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_up_1), NULL, this);
m_bpButton_z_down_1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_1), NULL, this);
m_bpButton_z_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_z_down_10), NULL, this);
m_bpButton_e_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_up_10), NULL, this);
m_bpButton_e_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_down_10), NULL, this);
m_nozzle_btn_panel->Disconnect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this);
m_switch_speed->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_switch_speed), NULL, this);
m_calibration_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_start_calibration), NULL, this);
m_options_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_print_options), NULL, this);
m_safety_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_safety_options), NULL, this);
m_parts_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_parts_options), NULL, this);
}
// remove warning dialogs
if (abort_dlg != nullptr)
@@ -5216,7 +5247,9 @@ void StatusPanel::set_default()
m_filament_step->Hide();
error_info_reset();
#ifndef __WXGTK__
SetFocus();
// Also reached while the panel is built off screen.
if (IsShownOnScreen())
SetFocus();
#endif
}
+5 -3
View File
@@ -36,6 +36,7 @@
#include "HMS.hpp"
#include "PartSkipDialog.hpp"
#include "DeviceErrorDialog.hpp"
#include "StagedBuild.hpp"
class StepIndicator;
@@ -375,7 +376,7 @@ public:
void paint(wxPaintEvent&);
};
class StatusBasePanel : public wxScrolledWindow
class StatusBasePanel : public wxScrolledWindow, public StagedBuild
{
protected:
wxBitmap m_item_placeholder;
@@ -443,7 +444,7 @@ protected:
wxMediaCtrl3 * m_media_ctrl;
MediaPlayCtrl * m_media_play_ctrl;
MediaPlayCtrl * m_media_play_ctrl{nullptr};
Label * m_staticText_printing;
wxStaticBitmap *m_bitmap_thumbnail;
@@ -629,6 +630,7 @@ class StatusPanel : public StatusBasePanel
{
private:
friend class MonitorPanel;
void wire_controls();
protected:
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
@@ -679,7 +681,7 @@ protected:
std::map<std::string, std::string> m_print_connect_types;
std::vector<Button *> m_buttons;
int last_status;
ScoreData *m_score_data;
ScoreData *m_score_data = nullptr;
wxBitmap* calib_bitmap = nullptr;
CalibMode m_calib_mode;
CalibrationMethod m_calib_method;
+56 -17
View File
@@ -804,6 +804,10 @@ void Tab::OnActivate()
}
#endif
// The OnActivate() that shows the tab builds the page.
if (wxGetApp().mainframe != nullptr && !wxGetApp().mainframe->is_active_and_shown_tab(m_parent))
return;
// BBS: select on first active
if (!m_active_page)
restore_last_select_item();
@@ -7159,6 +7163,16 @@ void Tab::restore_last_select_item()
m_tabctrl->SelectItem(item);
}
bool Tab::page_build_pending() const
{
return m_active_page != nullptr && m_active_page->build_pending();
}
bool Tab::page_build_step()
{
return m_active_page != nullptr && m_active_page->build_step(m_mode);
}
void Tab::update_description_lines()
{
if (m_active_page && m_active_page->title() == "Dependencies" && m_parent_preset_description_line)
@@ -7375,7 +7389,7 @@ void Tab::OnKeyDown(wxKeyEvent& event)
void Tab::compare_preset()
{
wxGetApp().mainframe->diff_dialog.show(m_type);
DiffPresetDialog::ensure()->show(m_type);
}
void Tab::transfer_options(const std::string &name_from, const std::string &name_to, std::vector<std::string> options)
@@ -7543,8 +7557,10 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_proje
wxGetApp().get_tab(preset_type)->update_tab_ui();
}
// update preset comboboxes in DiffPresetDlg
wxGetApp().mainframe->diff_dialog.update_presets(m_type);
// show() reloads the presets, so only a visible Compare dialog needs updating.
DiffPresetDialog* diff_dialog = DiffPresetDialog::if_built();
if (diff_dialog != nullptr && diff_dialog->IsShown())
diff_dialog->update_presets(m_type);
}
// Called for a currently selected preset.
@@ -8604,20 +8620,8 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#else
//m_vsizer->AddSpacer(10);
#endif
#if HIDE_FIRST_SPLIT_LINE
// BBS: no line spliter for first group
bool first = true;
#endif
for (auto group : m_optgroups) {
if (!group->activate(throw_if_canceled))
continue;
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
if (first) group->stb->Hide();
first = false;
#endif
group->reload_config();
for (size_t i = 0; i < m_optgroups.size(); ++i) {
activate_group(i, mode, throw_if_canceled);
throw_if_canceled();
}
@@ -8636,6 +8640,41 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#endif
}
// Builds one option group; false when it already has its controls.
bool Page::activate_group(size_t i, ConfigOptionMode mode, std::function<void()> throw_if_canceled)
{
auto& group = m_optgroups[i];
if (!group->activate(throw_if_canceled))
return 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
// BBS: no line spliter for first group
if (i == 0) group->stb->Hide();
#endif
group->reload_config();
return true;
}
// The first group without controls.
size_t Page::next_group_to_build() const
{
return std::find_if(m_optgroups.begin(), m_optgroups.end(), [](const auto& group) { return !group->is_activated(); }) - m_optgroups.begin();
}
bool Page::build_pending() const
{
return next_group_to_build() < m_optgroups.size();
}
bool Page::build_step(ConfigOptionMode mode)
{
const size_t i = next_group_to_build();
if (i < m_optgroups.size())
activate_group(i, mode, [] {});
return build_pending();
}
void Page::clear()
{
for (auto group : m_optgroups)
+10
View File
@@ -94,6 +94,10 @@ public:
void reload_config();
void update_visibility(ConfigOptionMode mode, bool update_contolls_visibility);
void activate(ConfigOptionMode mode, std::function<void()> throw_if_canceled);
// Whether an option group has no controls yet.
bool build_pending() const;
// Builds the next option group that has no controls yet; true while some remain.
bool build_step(ConfigOptionMode mode);
void clear();
void msw_rescale();
void sys_color_changed();
@@ -121,6 +125,8 @@ public:
std::map<std::string, std::string> m_opt_id_map;
protected:
size_t next_group_to_build() const;
bool activate_group(size_t i, ConfigOptionMode mode, std::function<void()> throw_if_canceled);
// Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_item_color;
};
@@ -437,6 +443,10 @@ 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.
bool page_build_pending() const;
bool page_build_step();
static bool validate_custom_gcode(const wxString& title, const std::string& gcode);
bool validate_custom_gcodes();
+3 -2
View File
@@ -1962,8 +1962,9 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe)
assert(wxGetApp().preset_bundle);
m_preset_bundle_left = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
m_preset_bundle_right = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
// show() copies the app's bundle into both before anything is displayed.
m_preset_bundle_left = std::make_unique<PresetBundle>();
m_preset_bundle_right = std::make_unique<PresetBundle>();
// Create UI items
+2 -1
View File
@@ -10,6 +10,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Lazy.hpp"
class ScalableButton;
class wxStaticText;
@@ -416,7 +417,7 @@ public:
//------------------------------------------
// DiffPresetDialog
//------------------------------------------
class DiffPresetDialog : public DPIDialog
class DiffPresetDialog : public DPIDialog, public LazyInstance<DiffPresetDialog>
{
DiffViewCtrl* m_tree { nullptr };
wxBoxSizer* m_presets_sizer { nullptr };
+2 -2
View File
@@ -219,6 +219,7 @@ WebViewPanel::WebViewPanel(wxWindow *parent)
Bind(wxEVT_CLOSE_WINDOW, &WebViewPanel::OnClose, this);
m_LoginUpdateTimer = nullptr;
update_mode();
}
WebViewPanel::~WebViewPanel()
@@ -416,8 +417,7 @@ void WebViewPanel::OnClose(wxCloseEvent& evt)
void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event)
{
auto mainframe = Slic3r::GUI::wxGetApp().mainframe;
if (mainframe && mainframe->m_webview == this) {
if (WebViewPanel::if_built() == this) {
auto* app_config = Slic3r::GUI::wxGetApp().app_config;
if (app_config && app_config->get_stealth_mode()) return;
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
+2 -1
View File
@@ -24,6 +24,7 @@
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include <wx/timer.h>
#include "Lazy.hpp"
namespace Slic3r {
@@ -33,7 +34,7 @@ class NetworkAgent;
namespace GUI {
class WebViewPanel : public wxPanel
class WebViewPanel : public wxPanel, public LazyInstance<WebViewPanel>
{
public:
WebViewPanel(wxWindow *parent);
+3
View File
@@ -5,6 +5,9 @@ add_executable(${_TEST_NAME}_tests
test_creality_cfs_match.cpp
test_dev_mapping.cpp
test_filament_bitmap_utils.cpp
test_lazy.cpp
test_prebuild_queue.cpp
test_staged_build.cpp
test_network_versions.cpp
test_action_source.cpp
test_plugin_host_api.cpp
+240
View File
@@ -0,0 +1,240 @@
#include <catch2/catch_all.hpp>
#include <functional>
#include <memory>
#include <stdexcept>
#include <vector>
#include "slic3r/GUI/Lazy.hpp"
using Slic3r::GUI::Lazy;
using Slic3r::GUI::LazyBase;
using Slic3r::GUI::LazyInstance;
using Slic3r::GUI::StagedBuild;
namespace {
struct Plain
{
int value{ 1 };
};
struct One : LazyInstance<One>
{
int value{ 2 };
};
// Two steps after the constructor.
struct Staged : StagedBuild, LazyInstance<Staged>
{
std::vector<int> ran;
Staged()
{
add_build_step([this] { ran.push_back(1); });
add_build_step([this] { ran.push_back(2); });
}
void add_step(std::function<void()> step) { add_build_step(std::move(step)); }
};
// Owns what the factories make, since a Lazy does not.
template <class T>
struct Made
{
std::vector<std::unique_ptr<T>> objects;
T* make()
{
objects.push_back(std::make_unique<T>());
return objects.back().get();
}
typename Lazy<T>::Factory factory()
{
return [this] { return make(); };
}
};
} // namespace
TEST_CASE("The factory runs on the first unit, not at construction", "[Lazy]")
{
Made<Plain> made;
Lazy<Plain> lazy("plain", 0, made.factory());
REQUIRE(made.objects.empty());
REQUIRE_FALSE(lazy.built());
REQUIRE(lazy.pending());
REQUIRE(lazy.get() == nullptr);
REQUIRE_FALSE(lazy.build_step()); // the only unit
REQUIRE(made.objects.size() == 1);
REQUIRE(lazy.built());
REQUIRE_FALSE(lazy.pending());
REQUIRE(lazy.get() == made.objects[0].get());
REQUIRE_FALSE(lazy.build_step());
REQUIRE(made.objects.size() == 1);
}
TEST_CASE("A staged type takes one unit for the constructor and one per step", "[Lazy]")
{
Made<Staged> made;
Lazy<Staged> lazy("staged", 0, made.factory());
REQUIRE(lazy.build_step());
REQUIRE(made.objects.size() == 1);
REQUIRE(lazy.get() == nullptr); // exists but incomplete
REQUIRE(lazy.build_step());
REQUIRE(made.objects[0]->ran == std::vector<int>{1});
REQUIRE_FALSE(lazy.build_step());
REQUIRE(made.objects[0]->ran == std::vector<int>{1, 2});
REQUIRE(lazy.get() == made.objects[0].get());
}
TEST_CASE("ensure builds whatever is left and is a no-op afterwards", "[Lazy]")
{
Made<Staged> made;
Lazy<Staged> lazy("staged", 0, made.factory());
lazy.build_step();
Staged* s = lazy.ensure();
REQUIRE(s == made.objects[0].get());
REQUIRE(s->ran == std::vector<int>{1, 2});
REQUIRE(lazy.ensure() == s);
REQUIRE(made.objects.size() == 1);
}
TEST_CASE("when_built waits for completion, then runs at once", "[Lazy]")
{
Made<Staged> made;
Lazy<Staged> lazy("staged", 0, made.factory());
std::vector<int> seen;
lazy.when_built([&](Staged& s) { seen.push_back(int(s.ran.size())); });
lazy.build_step();
lazy.build_step();
REQUIRE(seen.empty());
lazy.build_step();
REQUIRE(seen == std::vector<int>{2});
lazy.when_built([&](Staged&) { seen.push_back(9); });
REQUIRE(seen == std::vector<int>{2, 9});
}
TEST_CASE("A LazyInstance type reaches its holder through the statics", "[Lazy]")
{
REQUIRE(One::if_built() == nullptr);
REQUIRE(One::ensure() == nullptr);
Made<One> made;
{
Lazy<One> lazy("one", 0, made.factory());
REQUIRE(One::if_built() == nullptr);
One* one = One::ensure();
REQUIRE(one == made.objects[0].get());
REQUIRE(One::if_built() == one);
int seen = 0;
One::when_built([&](One& o) { seen = o.value; });
REQUIRE(seen == 2);
}
REQUIRE(One::if_built() == nullptr);
}
TEST_CASE("A newer holder replaces the registration; the older one leaves it alone", "[Lazy]")
{
Made<One> made;
auto first = std::make_unique<Lazy<One>>("first", 0, made.factory());
first->ensure();
Lazy<One> second("second", 0, made.factory());
REQUIRE(One::if_built() == nullptr); // the new holder has not built yet
second.ensure();
REQUIRE(One::if_built() == made.objects[1].get());
first.reset();
REQUIRE(One::if_built() == made.objects[1].get());
}
TEST_CASE("The holder reports the name and order it was given", "[Lazy]")
{
Made<Plain> made;
Lazy<Plain> lazy("plain", 7, made.factory());
LazyBase& base = lazy;
REQUIRE(base.name() == "plain");
REQUIRE(base.prebuild_order() == 7);
}
TEST_CASE("A unit that re-enters the holder builds nothing twice", "[Lazy]")
{
Made<Plain> made;
Lazy<Plain>* self = nullptr;
int nested_units = 0;
Lazy<Plain> lazy("plain", 0, [&] {
if (self->build_step()) // as if the constructor pumped the event loop into a slice
++nested_units;
return made.make();
});
self = &lazy;
REQUIRE_FALSE(lazy.build_step());
REQUIRE(nested_units == 0);
REQUIRE(made.objects.size() == 1);
REQUIRE(lazy.built());
}
TEST_CASE("A factory that returns null leaves the holder unbuilt and not pending", "[Lazy]")
{
int calls = 0;
Lazy<Plain> lazy("plain", 0, [&] { ++calls; return static_cast<Plain*>(nullptr); });
REQUIRE_FALSE(lazy.build_step());
REQUIRE_FALSE(lazy.built());
REQUIRE_FALSE(lazy.pending());
REQUIRE(lazy.get() == nullptr);
REQUIRE_FALSE(lazy.build_step()); // not retried
REQUIRE(calls == 1);
}
TEST_CASE("ensure returns null for a factory that returned null", "[Lazy]")
{
Lazy<Plain> lazy("plain", 0, [] { return static_cast<Plain*>(nullptr); });
REQUIRE(lazy.ensure() == nullptr);
REQUIRE_FALSE(lazy.built());
}
TEST_CASE("A nested ensure inside the factory returns null", "[Lazy]")
{
Made<Plain> made;
Lazy<Plain>* self = nullptr;
Plain* nested = reinterpret_cast<Plain*>(1);
Lazy<Plain> lazy("plain", 0, [&] {
nested = self->ensure(); // as if the constructor pumped the event loop into a caller
return made.make();
});
self = &lazy;
Plain* built = lazy.ensure();
REQUIRE(built == made.objects[0].get());
REQUIRE(nested == nullptr);
}
TEST_CASE("A nested ensure during a staged step returns null", "[Lazy]")
{
Made<Staged> made;
Lazy<Staged>* self = nullptr;
Staged* nested = reinterpret_cast<Staged*>(1);
Lazy<Staged> lazy("staged", 0, [&] {
Staged* s = made.make();
s->add_step([&] { nested = self->ensure(); }); // as if a step pumped the event loop into a caller
return s;
});
self = &lazy;
Staged* built = lazy.ensure();
REQUIRE(built == made.objects[0].get());
REQUIRE(nested == nullptr);
}
TEST_CASE("A unit that throws leaves the holder free to build the rest", "[Lazy]")
{
Made<Staged> made;
bool thrown = false;
Lazy<Staged> lazy("staged", 0, [&] {
Staged* s = made.make();
s->add_step([&] { thrown = true; throw std::runtime_error("step"); });
return s;
});
lazy.build_step();
lazy.build_step();
lazy.build_step();
REQUIRE_THROWS(lazy.build_step());
REQUIRE(thrown);
REQUIRE(lazy.pending());
REQUIRE_FALSE(lazy.build_step()); // the next unit runs
REQUIRE(lazy.built());
}
+183
View File
@@ -0,0 +1,183 @@
#include <catch2/catch_all.hpp>
#include <string>
#include <vector>
#include "slic3r/GUI/PrebuildQueue.hpp"
using Slic3r::GUI::LazyBase;
using Slic3r::GUI::PrebuildQueue;
namespace {
// A task with `left` units, each taking `unit_ms` of the shared fake clock and logging its id.
struct Counter : LazyBase
{
std::string id;
int left;
int order;
long long unit_ms{ 1 };
inline static std::vector<int> log;
inline static long long now = 0;
Counter(int id, int left, int order, long long unit_ms = 1) : id(std::to_string(id)), left(left), order(order), unit_ms(unit_ms) {}
const std::string& name() const override { return id; }
bool built() const override { return left == 0; }
bool build_step() override
{
now += unit_ms;
log.push_back(std::stoi(id));
return --left > 0;
}
int prebuild_order() const override { return order; }
};
// Resets the shared log and clock at the start of a case.
struct Reset
{
Reset() { Counter::log.clear(); Counter::now = 0; }
};
const auto fake_clock = [] { return Counter::now; };
const auto no_input = [] { return false; };
// Runs slices with an unlimited budget until nothing is pending; one task per slice.
void drain(PrebuildQueue& q)
{
while (q.pending())
q.run_slice(1000000, fake_clock, no_input);
}
} // namespace
TEST_CASE("Tasks run lowest order first, equal order in the order added", "[PrebuildQueue]")
{
Reset reset;
Counter a{ 1, 1, 10 }, b{ 2, 1, 10 }, c{ 3, 1, 50 }, d{ 4, 1, 100 };
PrebuildQueue q;
q.add(c);
q.add(a);
q.add(b);
q.add(d);
REQUIRE(q.names() == "1, 2, 3, 4");
drain(q);
REQUIRE(Counter::log == std::vector<int>{1, 2, 3, 4});
}
TEST_CASE("A task with nothing pending is skipped, not removed", "[PrebuildQueue]")
{
Reset reset;
Counter a{ 1, 0, 0 }, b{ 2, 2, 1 };
PrebuildQueue q;
q.add(a);
q.add(b);
REQUIRE(q.pending());
auto slice = q.run_slice(1, fake_clock, no_input); // one unit of b
REQUIRE(slice.units == 1);
REQUIRE(Counter::log == std::vector<int>{2});
a.left = 1; // a's work returned; it comes first again
q.run_slice(1, fake_clock, no_input);
REQUIRE(Counter::log == std::vector<int>{2, 1});
}
TEST_CASE("A slice with nothing pending runs no unit", "[PrebuildQueue]")
{
Reset reset;
PrebuildQueue q;
REQUIRE_FALSE(q.pending());
auto slice = q.run_slice(40, fake_clock, no_input);
REQUIRE(slice.units == 0);
REQUIRE_FALSE(slice.completed);
REQUIRE_FALSE(slice.remaining);
}
TEST_CASE("A slice stops once its budget is spent, after the unit that crossed it", "[PrebuildQueue]")
{
Reset reset;
Counter a{ 1, 10, 0, 15 };
PrebuildQueue q;
q.add(a);
auto slice = q.run_slice(40, fake_clock, no_input);
REQUIRE(slice.units == 3); // units end at 15, 30 and 45 ms; the one crossing 40 is the last
REQUIRE(slice.ms == 45);
REQUIRE_FALSE(slice.completed);
REQUIRE(slice.remaining);
REQUIRE(a.left == 7);
}
TEST_CASE("A slice stops after the unit during which input arrived", "[PrebuildQueue]")
{
Reset reset;
Counter a{ 1, 10, 0 };
bool input = false;
PrebuildQueue q;
q.add(a);
auto slice = q.run_slice(40, fake_clock, [&] { input = a.left == 8; return input; });
REQUIRE(slice.units == 2);
REQUIRE_FALSE(slice.completed);
REQUIRE(slice.remaining);
}
TEST_CASE("A slice reports completion, whether work remains, and each unit's time", "[PrebuildQueue]")
{
Reset reset;
Counter a{ 1, 2, 0, 5 }, b{ 2, 1, 1 };
std::vector<long long> unit_ms;
PrebuildQueue q;
q.add(a);
q.add(b);
auto slice = q.run_slice(40, fake_clock, no_input, [&](const std::string& name, long long ms) {
REQUIRE(name == "1");
unit_ms.push_back(ms);
});
REQUIRE(slice.units == 2);
REQUIRE(slice.completed);
REQUIRE(slice.name == "1");
REQUIRE(slice.remaining); // b
REQUIRE(unit_ms == std::vector<long long>{5, 5});
slice = q.run_slice(40, fake_clock, no_input);
REQUIRE(slice.completed);
REQUIRE_FALSE(slice.remaining);
REQUIRE_FALSE(q.pending());
}
TEST_CASE("A unit may add a task to the queue it runs from", "[PrebuildQueue]")
{
Reset reset;
PrebuildQueue q;
Counter later{ 2, 1, 5 };
struct Adder : LazyBase
{
PrebuildQueue& q;
Counter& later;
std::string id{ "1" };
bool done{ false };
Adder(PrebuildQueue& q, Counter& later) : q(q), later(later) {}
const std::string& name() const override { return id; }
bool built() const override { return done; }
bool build_step() override
{
Counter::log.push_back(1);
done = true;
q.add(later);
return false;
}
int prebuild_order() const override { return 0; }
} first{ q, later };
q.add(first);
drain(q);
REQUIRE(Counter::log == std::vector<int>{1, 2});
}
TEST_CASE("clear drops every task", "[PrebuildQueue]")
{
Reset reset;
Counter a{ 1, 1, 0 };
PrebuildQueue q;
q.add(a);
q.clear();
REQUIRE_FALSE(q.pending());
REQUIRE(q.run_slice(40, fake_clock, no_input).units == 0);
}
+105
View File
@@ -0,0 +1,105 @@
#include <catch2/catch_all.hpp>
#include <vector>
#include "slic3r/GUI/StagedBuild.hpp"
using Slic3r::GUI::StagedBuild;
namespace {
// Exposes the protected queueing calls and records the order steps ran in.
struct Staged : StagedBuild
{
std::vector<int> ran;
void queue(int id) { add_build_step([this, id] { ran.push_back(id); }); }
void queue_child(Staged& child) { add_build_steps_of(child); }
void queue_nested(int id, int nested)
{
add_build_step([this, id, nested] {
ran.push_back(id);
queue(nested);
});
}
};
} // namespace
TEST_CASE("Steps run in the order they were queued, one per build_step", "[StagedBuild]")
{
Staged s;
s.queue(1);
s.queue(2);
s.queue(3);
REQUIRE_FALSE(s.built());
REQUIRE(s.build_step());
REQUIRE(s.ran == std::vector<int>{1});
REQUIRE(s.build_step());
REQUIRE(s.ran == std::vector<int>{1, 2});
REQUIRE_FALSE(s.build_step());
REQUIRE(s.ran == std::vector<int>{1, 2, 3});
REQUIRE(s.built());
REQUIRE_FALSE(s.build_step());
REQUIRE(s.ran.size() == 3);
}
TEST_CASE("A panel with no steps is built from the start", "[StagedBuild]")
{
Staged s;
REQUIRE(s.built());
REQUIRE_FALSE(s.build_step());
}
TEST_CASE("A step may queue another step, which runs after the ones already queued", "[StagedBuild]")
{
Staged s;
s.queue_nested(1, 3);
s.queue(2);
REQUIRE(s.build_step());
REQUIRE_FALSE(s.built());
REQUIRE(s.build_step());
REQUIRE_FALSE(s.build_step());
REQUIRE(s.ran == std::vector<int>{1, 2, 3});
REQUIRE(s.built());
}
TEST_CASE("A parent waits for steps a child queues after being adopted", "[StagedBuild]")
{
Staged child;
child.queue_nested(1, 2); // step 1 queues step 2 while it runs
Staged parent;
parent.queue_child(child); // one forwarder, for step 1
parent.queue(10);
REQUIRE(parent.build_step()); // child step 1, which queues step 2
REQUIRE(parent.build_step()); // 10; own steps exhausted, the child still has 2
REQUIRE_FALSE(parent.built());
REQUIRE_FALSE(parent.build_step()); // child step 2
REQUIRE(child.ran == std::vector<int>{1, 2});
REQUIRE(parent.ran == std::vector<int>{10});
REQUIRE(parent.built());
}
TEST_CASE("A child's remaining steps are forwarded one per parent step", "[StagedBuild]")
{
Staged child;
child.queue(1);
child.queue(2);
child.queue(3);
REQUIRE(child.build_step()); // the parent adopts only what is left
Staged parent;
parent.queue_child(child);
parent.queue(10);
REQUIRE(parent.build_step());
REQUIRE(child.ran == std::vector<int>{1, 2});
REQUIRE(parent.build_step());
REQUIRE(child.ran == std::vector<int>{1, 2, 3});
REQUIRE(child.built());
REQUIRE_FALSE(parent.build_step());
REQUIRE(parent.ran == std::vector<int>{10});
REQUIRE(parent.built());
}