* Add wxInspector dep
* Initial intergration of wxInspector
* docs: add wxInspector plugins design spec
Design spec for two wxInspector plugins (DPIAware + CustomWidgets) that expose
OrcaSlicer's custom control properties in the inspector property grid.
Covers: DPIAware scale-factor properties, Button, CheckBox, TextInput,
SwitchButton, ProgressBar, Label, and LabeledStaticBox.
* docs: add wxInspector plugins implementation plan
6-task plan covering: source changes to existing widget headers,
DPIAwarePlugin, CustomWidgetsPlugin, registration helper,
MainFrame/CMake wiring, and build verification.
* feat: add getters/setters for wxInspector plugin access
Add minimal public accessors to DPIAware (set_scale_factor,
set_prev_scale_factor, set_em_unit, force_rescale), Button
(GetStyle, GetType, IsSelected), CheckBox (IsHalfChecked),
TextInput (GetCornerRadius), and LabeledStaticBox
(GetCornerRadius, GetBorderWidth, GetBorderColor, GetScale).
* feat: add wxInspector plugin registration helper
Add RegisterOrcaInspectorPlugins() inline function that creates
and registers the DPIAwarePlugin and CustomWidgetsPlugin as
static instances (matching wxInspector's built-in pattern).
* feat: add DPIAware wxInspector plugin
Exposes DPI scaling properties (scale_factor, prev_scale_factor,
em_unit, normal_font, force_rescale) on DPIFrame and DPIDialog
widgets. Uses dynamic_cast for detection and a template helper
to capture the correct static type for lambda accessors.
* feat: add OrcaCustomWidgets wxInspector plugin
Exposes Orca-specific properties on 7 widget types:
- Button: Style, Type, Selected
- CheckBox: Half Checked
- TextInput: Label, Text Value, Corner Radius
- SwitchButton: Value
- ProgressBar: Proportion, Show Number
- Label: Is Hyperlink, Font Point Size
- LabeledStaticBox: Corner Radius, Border Width, Border Color, Scale
Each widget type uses dynamic_cast for safe detection.
* feat: wire wxInspector plugins into MainFrame and build
Call RegisterOrcaInspectorPlugins() in MainFrame constructor after
SetupInspectorAccelerator(). Add all 5 plugin source files to
SLIC3R_GUI_SOURCES in CMakeLists.txt.
* fix: move plugin registration to GUI_App::on_init_inner
Register plugins once in app init rather than in MainFrame
constructor, which may be recreated during the application
lifetime.
* fix: include plugin headers in Registration.hpp for complete types
Static locals require complete type. Include DPIAwarePlugin.hpp and
CustomWidgetsPlugin.hpp instead of forward-declaring. Also remove
unused include from MainFrame.cpp (registration moved to GUI_App).
* fix: qualify DPIFrame/DPIDialog with Slic3r::GUI namespace
* Make DPIDialog inspectable. For other dialogs, we will add them if necessary later.
* docs: add spec for moving wxInspectable into DPIAware template
Move wxInspector::wxInspectable base class from DPIDialog and MainFrame
into the common DPIAware<P> template, making all DPIAware widgets
automatically visible in the inspector tree.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: add implementation plan for moving wxInspectable into DPIAware
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: update spec/plan — move SetupInspectorAccelerator into DPIAware too
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: move wxInspectable and SetupInspectorAccelerator into DPIAware
DPIAware<P> now inherits wxInspector::wxInspectable and calls
SetupInspectorAccelerator in its constructor, making all DPIAware
widgets automatically appear in the inspector tree with the
Ctrl+Shift+I shortcut. DPIDialog now uses 'using' to inherit the
constructor. Remove redundant wxInspectable inheritance and
SetupInspectorAccelerator calls from DPIDialog and MainFrame.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use LB_HYPERLINK constant instead of magic number 0x0020
Co-Authored-By: Claude <noreply@anthropic.com>
* Clean up
* Fix Linux build
* Don't build wxInspector sample
* Use shallow clone
* Try fix flatpak build
* Attempt to fix build again
* Fix build failure caused by 436c16135e
* wxWidgets build only download required submodules
* This should fix build on Windows on ARM
* Enable PIC
* Disable layout inspector by default for public release
* Use wxInspector 1.0.0 release
---------
Co-authored-by: Claude <noreply@anthropic.com>
13 KiB
wxInspector Plugins for OrcaSlicer Custom Controls — Design Spec
Date: 2026-07-23
Branch: dev/layout-inspector
Overview
Create wxInspector plugins that expose OrcaSlicer's custom widget properties in the inspector's property grid. Without these plugins, the inspector shows only generic wxWidgets properties — missing all DPI-awareness data, custom styling, and Orca-specific control state.
Goals
- DPIAware properties — Inspect and update
scale_factor,prev_scale_factor,em_unit, andnormal_fonton any DPIAware-derived widget - Custom widget properties — Surface Orca-specific properties on
Button,CheckBox,TextInput,SwitchButton,ProgressBar,Label, andLabeledStaticBox - Minimal source changes — Only add trivial (one-line) getters/setters to existing classes; no architectural refactoring of Orca's widget hierarchy
Non-Goals
- Custom inspector panels or AUI tabs (use the existing property grid and method invoker)
- Python-plugin integration (this is C++ wxInspector, not Orca's Python plugin system)
- Event logging customization (the built-in event logger already works)
Architecture
Two Plugins
| Plugin | Class | Files |
|---|---|---|
| DPIAware plugin | DPIAwarePlugin |
DPIAwarePlugin.hpp, DPIAwarePlugin.cpp |
| Custom widgets plugin | CustomWidgetsPlugin |
CustomWidgetsPlugin.hpp, CustomWidgetsPlugin.cpp |
| Registration helper | inline function | Registration.hpp |
All files live under src/slic3r/Utils/wxInspectorPlugins/.
Plugin Detection Strategy
DPIAware plugin: Uses dynamic_cast<DPIFrame*> and dynamic_cast<DPIDialog*> as detection gates. DPIFrame = DPIAware<wxFrame>, DPIDialog = DPIAware<wxDialog>. Since these are concrete typedefs, dynamic_cast works at runtime. This covers MainFrame, SettingsDialog, and all 8 calibration dialogs (which inherit DPIDialog).
Custom widgets plugin: Gates broadly on CLASSINFO(wxWindow), then uses per-type dynamic_cast inside GetProperties to check each Orca-specific type. Only matching types append properties.
Registration
A single RegisterOrcaInspectorPlugins() inline function in Registration.hpp creates both plugins as function-local statics (matching the wxInspector built-in provider pattern) and registers them via wxInspector::RegisterPlugin().
Called once from MainFrame::MainFrame() after SetupInspectorAccelerator(this).
Why Separate Plugins?
- DPIAware is a C++ template concept (not a wxClassInfo-isKindOf check), so it needs its own detection logic
- Custom widgets use standard wxClassInfo-based detection, matching the built-in provider pattern
- Two focused files are easier to review and maintain than one monolithic plugin
- Compile-time failure isolation: if a widget header changes, only one plugin breaks
DPIAware Plugin — Property Specification
Source Changes (GUI_Utils.hpp)
Four one-liner methods added to the DPIAware<P> template class (public section):
float scale_factor() const { return m_scale_factor; } // already exists
float prev_scale_factor() const { return m_prev_scale_factor; } // already exists
int em_unit() const { return m_em_unit; } // already exists
void set_scale_factor(float v) { m_scale_factor = v; } // NEW
void set_prev_scale_factor(float v) { m_prev_scale_factor = v; } // NEW
void set_em_unit(int v) { m_em_unit = v; } // NEW
bool force_rescale() const { return m_force_rescale; } // NEW
// m_normal_font getter already exists: normal_font()
Detection
bool CanProvideProperties(wxClassInfo* info) override {
// Gated in GetProperties via dynamic_cast on the window itself
return info->IsKindOf(CLASSINFO(wxWindow));
}
In GetProperties:
auto* win = obj.AsWindow();
bool isDPI = dynamic_cast<DPIFrame*>(win) || dynamic_cast<DPIDialog*>(win);
if (!isDPI) return props;
Property Table (category: "DPI Scaling")
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Scale Factor | String (float) | Yes | dpi->scale_factor() |
dpi->set_scale_factor(v) |
| Prev Scale Factor | String (float) | Yes | dpi->prev_scale_factor() |
dpi->set_prev_scale_factor(v) |
| EM Unit | Integer | Yes | dpi->em_unit() |
dpi->set_em_unit(v) |
| Normal Font | ReadOnly | No | dpi->normal_font().GetNativeFontInfoDesc() |
— |
| Force Rescale | Boolean (ReadOnly) | No | dpi->force_rescale() |
— |
Note on setters: The setters simply store values. They do NOT trigger a widget rescale/layout. To see the effect of a changed scale factor, use the inspector's Methods panel to call Layout() or resize the window — which triggers the DPI_CHANGED event path naturally.
Custom Widgets Plugin — Property Specification
All properties are appended to the built-in wxWindow properties. Each widget type is independently detected via dynamic_cast.
Detection gates (in GetProperties)
auto* win = obj.AsWindow();
if (auto* btn = dynamic_cast<Button*>(win)) { addButtonProperties(btn, props); }
if (auto* cb = dynamic_cast<CheckBox*>(win)) { addCheckBoxProperties(cb, props); }
if (auto* ti = dynamic_cast<TextInput*>(win)) { addTextInputProperties(ti, props); }
if (auto* sb = dynamic_cast<SwitchButton*>(win)) { addSwitchButtonProperties(sb, props); }
if (auto* pb = dynamic_cast<ProgressBar*>(win)) { addProgressBarProperties(pb, props); }
if (auto* lbl = dynamic_cast<Label*>(win)) { addLabelProperties(lbl, props); }
if (auto* lsb = dynamic_cast<LabeledStaticBox*>(win)) { addLabeledStaticBoxProperties(lsb, props); }
Orca Button (Button) — category: "Orca Button"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Button Style | Choice | Yes | enum→string | string→enum |
| Button Type | Choice | Yes | enum→string | string→enum |
| Selected | Boolean | Yes | m_selected (needs getter) |
SetSelected(v) |
| Active Icon | ReadOnly | No | icon name string | — |
| Inactive Icon | ReadOnly | No | icon name string | — |
Choices for Button Style: Regular, Confirm, Alert, Disabled
Choices for Button Type: Compact, Window, Choice, Parameter, Icon, Expanded
Source changes needed: Button's m_selected is private. Add one-liner getter:
bool IsSelected() const { return m_selected; }
Orca CheckBox (CheckBox) — category: "Orca CheckBox"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Half Checked | Boolean | Yes | m_half_checked (needs getter) |
SetHalfChecked(v) |
Source changes needed: m_half_checked is private. Add one-liner getter:
bool IsHalfChecked() const { return m_half_checked; }
Orca TextInput (TextInput) — category: "Orca TextInput"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Label | String | Yes | GetLabel() (inherited from wxWindow) |
SetLabel(v) (exists) |
| Text Value | String | Yes | GetTextCtrl()->GetValue() (GetTextCtrl is public) |
GetTextCtrl()->SetValue(v) |
| Corner Radius | Integer | Yes | GetCornerRadius() (NEW) |
SetCornerRadius(v) (exists) |
Source changes needed: Add one getter to TextInput:
int GetCornerRadius() const { return static_cast<int>(radius); }
(radius is inherited from StaticBox. SetCornerRadius(double) already exists. GetTextCtrl() is already public.)
Orca SwitchButton (SwitchButton) — category: "Orca SwitchButton"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Value | Boolean | Yes | existing getter | existing setter |
Orca ProgressBar (ProgressBar) — category: "Orca ProgressBar"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Proportion | Float (0-1) | Yes | pb->m_proportion (public member) |
pb->m_proportion = v |
| Show Number | Boolean | Yes | pb->m_shownumber (public member) |
pb->m_shownumber = v |
No source changes needed: m_proportion and m_shownumber are already public members. SetValue(int) and SetProgress(int) already exist as public methods.
Orca Label (Label) — category: "Orca Label"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Is Hyperlink | Boolean | No | existing flag check | — |
| Font Size | ReadOnly | No | GetFont().GetPointSize() |
— |
LabeledStaticBox — category: "LabeledStaticBox"
| Name | Type | Editable | Getter | Setter |
|---|---|---|---|---|
| Corner Radius | Integer | Yes | GetCornerRadius() (NEW) |
SetCornerRadius(v) (exists) |
| Border Width | Integer | Yes | GetBorderWidth() (NEW) |
SetBorderWidth(v) (exists) |
| Border Color | String (hex) | Yes | GetBorderColor() (NEW) |
SetBorderColor(v) (exists) |
| Scale | Float (ReadOnly) | No | m_scale (protected, needs getter) |
— |
Source changes needed: Four one-liner getters added to LabeledStaticBox:
int GetCornerRadius() const { return m_radius; }
int GetBorderWidth() const { return m_border_width; }
StateColor GetBorderColor() const { return border_color; }
float GetScale() const { return m_scale; }
Files Modified (Existing Code)
| File | Changes |
|---|---|
src/slic3r/GUI/GUI_Utils.hpp |
+4 methods in DPIAware<P>: set_scale_factor(), set_prev_scale_factor(), set_em_unit(), force_rescale() |
src/slic3r/GUI/Widgets/LabeledStaticBox.hpp |
+4 getter declarations: GetCornerRadius(), GetBorderWidth(), GetBorderColor(), GetScale() |
src/slic3r/GUI/Widgets/LabeledStaticBox.cpp |
+4 getter implementations |
src/slic3r/GUI/Widgets/Button.hpp |
+1 getter: IsSelected() |
src/slic3r/GUI/Widgets/CheckBox.hpp |
+1 getter: IsHalfChecked() |
src/slic3r/GUI/Widgets/TextInput.hpp |
+1 getter: GetCornerRadius() |
src/slic3r/GUI/Widgets/ProgressBar.hpp |
None (public members are used directly) |
src/slic3r/GUI/MainFrame.cpp |
+1 #include, +1 call to RegisterOrcaInspectorPlugins() |
src/slic3r/CMakeLists.txt |
+4 entries in SLIC3R_GUI_SOURCES (the .cpp plugin files) |
Files Created
src/slic3r/Utils/wxInspectorPlugins/
├── DPIAwarePlugin.hpp
├── DPIAwarePlugin.cpp
├── CustomWidgetsPlugin.hpp
├── CustomWidgetsPlugin.cpp
└── Registration.hpp
Build & Linking
The wxInspector dependency is already wired:
deps/wxInspector/wxInspector.cmakefetches and builds wxInspectorsrc/CMakeLists.txtlines 92-93 linkwxInspector::wxInspectorintowxWidgets_LIBRARIES- The plugin files only need
#include <wx/inspector/plugin.h>and#include <wx/inspector/inspector.h>— both available from the installed dependency
No new CMake dependencies needed. Only the new source files need listing in SLIC3R_GUI_SOURCES.
Error Handling & Edge Cases
- Stale pointers: Plugin lambdas capture raw pointers, regenerated on every
GetPropertiescall (matching wxInspector's built-in provider pattern). Pointers live only until the next tree selection. - Widget destruction: If a widget is destroyed while the inspector is showing its properties,
InspectableObject::IsValid()returns false and properties are not displayed. The inspector won't show stale data. - Invalid property values: Setters use
sscanf/ToLongwith validation (matching built-in patterns). Bogus input is rejected — setter returnsfalse, property grid shows error state. - DPI drift: Setting
scale_factorwithout triggering rescale means displayed sizes don't match the new factor. This is acceptable — the inspector is a developer tool; operators know to callLayout()after making changes. - Missing widget type: If a
dynamic_castfails for all types, only built-in wxWindow properties are shown. No crash, no error — just reduced info.
Future Work (Out of Scope)
- StateColor visualization:
StateColoris a multi-value type (maps bitmask states to colors). A full solution would need a custom property editor (e.g., a table showing each state→color pair). Keep it simple for now. - ScalableBitmap display: Could show the bitmap as an inline thumbnail. Complex property editor work — deferred.
- More widget types:
SwitchBoard,MultiSwitchButton,StepCtrl,FanControl,DropDown,ComboBox,AMS*widgets could all benefit. Add as needed. - Property refresh on tree selection: Currently properties are static snapshots. A "refresh" button or auto-poll could keep values current for rapidly-changing widgets (progress bars, etc.). The built-in wxInspector already provides a tree-refresh button.