diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 9720cebcc9..39cc5de182 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -423,6 +423,7 @@ endif () include(OCCT/OCCT.cmake) include(OpenCV/OpenCV.cmake) include(python3/python3.cmake) +include(wxInspector/wxInspector.cmake) set(_dep_list dep_Boost @@ -446,6 +447,7 @@ set(_dep_list ${EXPAT_PKG} dep_libnoise dep_python3 + dep_wxInspector ) if (MSVC) diff --git a/deps/wxInspector/wxInspector.cmake b/deps/wxInspector/wxInspector.cmake new file mode 100644 index 0000000000..97c3810809 --- /dev/null +++ b/deps/wxInspector/wxInspector.cmake @@ -0,0 +1,13 @@ +orcaslicer_add_cmake_project( + wxInspector + URL https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip + URL_HASH SHA256=0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7 + DEPENDS ${WXWIDGETS_PKG} + CMAKE_ARGS + -DCMAKE_CXX_FLAGS="-DwxDEBUG_LEVEL=0" + -DCMAKE_POSITION_INDEPENDENT_CODE=ON +) + +if (MSVC) + add_debug_dep(dep_wxInspector) +endif () diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 682b28bac4..1e2cc85f78 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -26,6 +26,7 @@ orcaslicer_add_cmake_project( GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets" GIT_TAG v3.3.2 GIT_SHALLOW ON + GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG} CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md b/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md new file mode 100644 index 0000000000..5b3669ace4 --- /dev/null +++ b/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md @@ -0,0 +1,111 @@ +# Move `wxInspectable` into `DPIAware` — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Move `wxInspector::wxInspectable` from individual leaf classes into the common `DPIAware

` template so every DPIAware widget is automatically inspectable and gets the inspector keyboard shortcut. + +**Architecture:** `DPIAware

` gains `wxInspector::wxInspectable` as a second base class and calls `SetupInspectorAccelerator(this)` in its constructor. `DPIDialog` and `MainFrame` drop their now-redundant `wxInspectable` inheritance and `SetupInspectorAccelerator` calls. + +**Tech Stack:** C++17, wxWidgets, wxInspector + +## Global Constraints + +- Build with `D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe` +- Use `--config RelWithDebInfo` for all builds +- Cross-platform: must compile on Windows, macOS, and Linux +- Match existing code style: PascalCase classes, `#pragma once` +- Do NOT commit files under `.superpowers/` +- Do NOT commit `task.md` + +--- + +### Task 1: Move `wxInspectable` and `SetupInspectorAccelerator` into `DPIAware

` + +**Files:** +- Modify: `src/slic3r/GUI/GUI_Utils.hpp:92` (DPIAware template — add wxInspectable base + SetupInspectorAccelerator call) +- Modify: `src/slic3r/GUI/GUI_Utils.hpp:276` (DPIDialog — drop wxInspectable + SetupInspectorAccelerator) +- Modify: `src/slic3r/GUI/MainFrame.hpp:96` (MainFrame — drop wxInspectable) +- Modify: `src/slic3r/GUI/MainFrame.cpp:304` (MainFrame constructor — drop SetupInspectorAccelerator) + +**Interfaces:** +- Consumes: Nothing (standalone refactor) +- Produces: All DPIAware widgets automatically inherit `wxInspector::wxInspectable` and get Ctrl+Shift+I accelerator + +- [ ] **Step 1: Add `wxInspectable` to `DPIAware

` and call `SetupInspectorAccelerator`** + +In `src/slic3r/GUI/GUI_Utils.hpp`, line 92, change the base class: + +```cpp +// Before: +template class DPIAware : public P +// After: +template class DPIAware : public P, public wxInspector::wxInspectable +``` + +In the constructor body of `DPIAware

`, after `this->CenterOnParent();` (currently line 110), add: + +```cpp +SetupInspectorAccelerator(this); +``` + +(`` is already included at line 23.) + +- [ ] **Step 2: Remove redundant `wxInspectable` and `SetupInspectorAccelerator` from `DPIDialog`** + +In `src/slic3r/GUI/GUI_Utils.hpp`, line 276, change: + +```cpp +// Before: +class DPIDialog : public DPIAware, public wxInspector::wxInspectable +// After: +class DPIDialog : public DPIAware +``` + +In the `DPIDialog` constructor body, remove the `SetupInspectorAccelerator(this);` line (currently line 286). The rest of the constructor stays. + +- [ ] **Step 3: Remove redundant `wxInspectable` from `MainFrame`** + +In `src/slic3r/GUI/MainFrame.hpp`, line 96, change: + +```cpp +// Before: +class MainFrame : public DPIFrame, public wxInspector::wxInspectable +// After: +class MainFrame : public DPIFrame +``` + +`MainFrame` now gets `wxInspectable` through `DPIFrame` → `DPIAware`. + +- [ ] **Step 4: Remove redundant `SetupInspectorAccelerator` from `MainFrame` constructor** + +In `src/slic3r/GUI/MainFrame.cpp`, line 304, remove the line: + +```cpp +SetupInspectorAccelerator(this); +``` + +It is now called automatically by the `DPIAware` constructor. + +- [ ] **Step 5: Build to verify compilation** + +```powershell +$cmakePath = "D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" +& $cmakePath --build . --config RelWithDebInfo --target ALL_BUILD -- -m +``` + +Expected: Build succeeds with zero new errors or warnings. + +- [ ] **Step 6: Commit** + +```bash +git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/MainFrame.hpp src/slic3r/GUI/MainFrame.cpp +git commit -m "refactor: move wxInspectable and SetupInspectorAccelerator into DPIAware + +DPIAware

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. Remove redundant wxInspectable inheritance +and SetupInspectorAccelerator calls from DPIDialog and MainFrame. + +Co-Authored-By: Claude " +``` diff --git a/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md b/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md new file mode 100644 index 0000000000..c3061b223d --- /dev/null +++ b/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md @@ -0,0 +1,753 @@ +# wxInspector Plugins for OrcaSlicer — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build two wxInspector plugins (DPIAware + CustomWidgets) that expose OrcaSlicer custom control properties in the inspector's property grid. + +**Architecture:** Two plugins in a shared folder under `src/slic3r/Utils/wxInspectorPlugins/`. DPIAwarePlugin uses `dynamic_cast/` for detection; CustomWidgetsPlugin uses per-type `dynamic_cast`. Both registered as static singletons via a single inline function in `Registration.hpp`, called from `MainFrame` constructor. + +**Tech Stack:** C++17, wxWidgets, wxInspector plugin API (`wx/inspector/plugin.h`, `wx/inspector/inspector.h`), OrcaSlicer custom widget headers + +## Global Constraints + +- Plugins placed under `src/slic3r/Utils/wxInspectorPlugins/` +- Build with `D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe` +- Minimal source changes: only trivial (one-line) getters/setters added to existing classes +- Cross-platform: must compile on Windows, macOS, and Linux +- Match existing code style: PascalCase classes, snake_case functions, `#pragma once` + +--- + +### Task 1: Add getters/setters to existing Orca widget headers + +**Files:** +- Modify: `src/slic3r/GUI/GUI_Utils.hpp` (DPIAware template — add 4 methods) +- Modify: `src/slic3r/GUI/Widgets/Button.hpp` (add 3 getters) +- Modify: `src/slic3r/GUI/Widgets/CheckBox.hpp` (add 1 getter) +- Modify: `src/slic3r/GUI/Widgets/TextInput.hpp` (add 1 getter) +- Modify: `src/slic3r/GUI/Widgets/LabeledStaticBox.hpp` (add 4 getter declarations) +- Modify: `src/slic3r/GUI/Widgets/LabeledStaticBox.cpp` (add 4 getter implementations) + +**Interfaces:** +- Consumes: Nothing (prerequisite for all other tasks) +- Produces: + - `DPIAware

::set_scale_factor(float)`, `DPIAware

::set_prev_scale_factor(float)`, `DPIAware

::set_em_unit(int)`, `DPIAware

::force_rescale() const` + - `Button::GetStyle()`, `Button::GetType()`, `Button::IsSelected()` + - `CheckBox::IsHalfChecked()` + - `TextInput::GetCornerRadius()` + - `LabeledStaticBox::GetCornerRadius()`, `LabeledStaticBox::GetBorderWidth()`, `LabeledStaticBox::GetBorderColor()`, `LabeledStaticBox::GetScale()` + +- [ ] **Step 1: Add DPIAware setters/getter in GUI_Utils.hpp** + +After line 184 (`float prev_scale_factor() const { return m_prev_scale_factor; }`), add: + +```cpp +void set_scale_factor(float v) { m_scale_factor = v; } +void set_prev_scale_factor(float v) { m_prev_scale_factor = v; } +void set_em_unit(int v) { m_em_unit = v; } +bool force_rescale() const { return m_force_rescale; } +``` + +- [ ] **Step 2: Add Button getters in Button.hpp** + +After line 79 (`void SetSelected(bool selected = true) { m_selected = selected; }`), add: + +```cpp +ButtonStyle GetStyle() const { return m_style; } +ButtonType GetType() const { return m_type; } +bool IsSelected() const { return m_selected; } +``` + +- [ ] **Step 3: Add CheckBox getter in CheckBox.hpp** + +After line 16 (`void SetHalfChecked(bool value = true);`), add: + +```cpp +bool IsHalfChecked() const { return m_half_checked; } +``` + +- [ ] **Step 4: Add TextInput getter in TextInput.hpp** + +After line 44 (`void SetCornerRadius(double radius);`), add: + +```cpp +int GetCornerRadius() const { return static_cast(radius); } +``` + +(Note: `radius` is inherited from `StaticBox` which has it as a protected `double` member.) + +- [ ] **Step 5: Add LabeledStaticBox getter declarations in LabeledStaticBox.hpp** + +After line 46 (`bool Enable(bool enable) override;`), add: + +```cpp +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; } +``` + +(Note: all of `m_radius`, `m_border_width`, `border_color`, `m_scale` are protected members, accessible to inline methods.) + +- [ ] **Step 6: Commit** + +```bash +git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/Widgets/Button.hpp src/slic3r/GUI/Widgets/CheckBox.hpp src/slic3r/GUI/Widgets/TextInput.hpp src/slic3r/GUI/Widgets/LabeledStaticBox.hpp +git commit -m "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)." +``` + +--- + +### Task 2: Create Registration helper header + +**Files:** +- Create: `src/slic3r/Utils/wxInspectorPlugins/Registration.hpp` + +**Interfaces:** +- Consumes: Nothing (forward-declares plugin classes) +- Produces: `RegisterOrcaInspectorPlugins()` + +- [ ] **Step 1: Create directory** + +```bash +mkdir -p src/slic3r/Utils/wxInspectorPlugins +``` + +- [ ] **Step 2: Write Registration.hpp** + +```cpp +#pragma once + +namespace wxInspector { +class wxInspectorPlugin; +void RegisterPlugin(wxInspectorPlugin* plugin); +} + +// Forward declare our plugins +class DPIAwarePlugin; +class CustomWidgetsPlugin; + +inline void RegisterOrcaInspectorPlugins() +{ + static DPIAwarePlugin dpiaware; + static CustomWidgetsPlugin customWidgets; + wxInspector::RegisterPlugin(&dpiaware); + wxInspector::RegisterPlugin(&customWidgets); +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/slic3r/Utils/wxInspectorPlugins/Registration.hpp +git commit -m "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)." +``` + +--- + +### Task 3: Create DPIAwarePlugin + +**Files:** +- Create: `src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp` +- Create: `src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp` + +**Interfaces:** +- Consumes: Task 1 (DPIAware getters/setters), Task 2 (registration pattern) +- Produces: `class DPIAwarePlugin : public wxInspector::wxInspectorPlugin` + +- [ ] **Step 1: Write DPIAwarePlugin.hpp** + +```cpp +#pragma once + +#include + +class DPIAwarePlugin : public wxInspector::wxInspectorPlugin +{ +public: + wxString GetName() const override; + + bool CanProvideProperties(wxClassInfo* info) override; + + wxVector GetProperties( + wxInspector::InspectableObject& obj) override; +}; +``` + +- [ ] **Step 2: Write DPIAwarePlugin.cpp** + +```cpp +#include "DPIAwarePlugin.hpp" + +#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware

+ +#include + +namespace { + +template +void addDPIProps(T* dpi, wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Scale Factor", "DPI Scaling", PropertyType::String, + wxString::Format("%.2f", dpi->scale_factor()), false, {}, + [dpi]() { return wxString::Format("%.2f", dpi->scale_factor()); }, + [dpi](const wxString& v) { + double val; + if (wxSscanf(v, "%lf", &val) != 1) return false; + dpi->set_scale_factor((float) val); + return true; + }}); + + props.push_back({"Prev Scale Factor", "DPI Scaling", PropertyType::String, + wxString::Format("%.2f", dpi->prev_scale_factor()), false, {}, + [dpi]() { return wxString::Format("%.2f", dpi->prev_scale_factor()); }, + [dpi](const wxString& v) { + double val; + if (wxSscanf(v, "%lf", &val) != 1) return false; + dpi->set_prev_scale_factor((float) val); + return true; + }}); + + props.push_back({"EM Unit", "DPI Scaling", PropertyType::Integer, + wxString::Format("%d", dpi->em_unit()), false, {}, + [dpi]() { return wxString::Format("%d", dpi->em_unit()); }, + [dpi](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + dpi->set_em_unit((int) val); + return true; + }}); + + props.push_back({"Normal Font", "DPI Scaling", PropertyType::ReadOnly, + dpi->normal_font().GetNativeFontInfoDesc(), true, {}, + [dpi]() { return dpi->normal_font().GetNativeFontInfoDesc(); }, + nullptr}); + + props.push_back({"Force Rescale", "DPI Scaling", PropertyType::Boolean, + dpi->force_rescale() ? "true" : "false", true, {}, + [dpi]() { return dpi->force_rescale() ? "true" : "false"; }, + nullptr}); +} + +} // anonymous namespace + +wxString DPIAwarePlugin::GetName() const +{ + return "OrcaDPIAware"; +} + +bool DPIAwarePlugin::CanProvideProperties(wxClassInfo* info) +{ + return info->IsKindOf(CLASSINFO(wxWindow)); +} + +wxVector DPIAwarePlugin::GetProperties( + wxInspector::InspectableObject& obj) +{ + wxVector props; + wxWindow* win = obj.AsWindow(); + if (!win) return props; + + if (auto* frame = dynamic_cast(win)) { + addDPIProps(frame, props); + } else if (auto* dlg = dynamic_cast(win)) { + addDPIProps(dlg, props); + } + + return props; +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp +git commit -m "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." +``` + +--- + +### Task 4: Create CustomWidgetsPlugin + +**Files:** +- Create: `src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp` +- Create: `src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp` + +**Interfaces:** +- Consumes: Task 1 (all widget getters), Task 2 (registration pattern) +- Produces: `class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin` + +- [ ] **Step 1: Write CustomWidgetsPlugin.hpp** + +```cpp +#pragma once + +#include + +class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin +{ +public: + wxString GetName() const override; + + bool CanProvideProperties(wxClassInfo* info) override; + + wxVector GetProperties( + wxInspector::InspectableObject& obj) override; + +private: + void addButtonProps(class Button* btn, + wxVector& props); + void addCheckBoxProps(class CheckBox* cb, + wxVector& props); + void addTextInputProps(class TextInput* ti, + wxVector& props); + void addSwitchButtonProps(class SwitchButton* sb, + wxVector& props); + void addProgressBarProps(class ProgressBar* pb, + wxVector& props); + void addLabelProps(class Label* lbl, + wxVector& props); + void addLabeledStaticBoxProps(class LabeledStaticBox* lsb, + wxVector& props); +}; +``` + +- [ ] **Step 2: Write CustomWidgetsPlugin.cpp — includes and GetName/CanProvideProperties** + +```cpp +#include "CustomWidgetsPlugin.hpp" + +#include "slic3r/GUI/Widgets/Button.hpp" +#include "slic3r/GUI/Widgets/CheckBox.hpp" +#include "slic3r/GUI/Widgets/TextInput.hpp" +#include "slic3r/GUI/Widgets/SwitchButton.hpp" +#include "slic3r/GUI/Widgets/ProgressBar.hpp" +#include "slic3r/GUI/Widgets/Label.hpp" +#include "slic3r/GUI/Widgets/LabeledStaticBox.hpp" + +#include +#include + +wxString CustomWidgetsPlugin::GetName() const +{ + return "OrcaCustomWidgets"; +} + +bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info) +{ + return info->IsKindOf(CLASSINFO(wxWindow)); +} + +wxVector CustomWidgetsPlugin::GetProperties( + wxInspector::InspectableObject& obj) +{ + wxVector props; + wxWindow* win = obj.AsWindow(); + if (!win) return props; + + if (auto* btn = dynamic_cast(win)) + addButtonProps(btn, props); + if (auto* cb = dynamic_cast(win)) + addCheckBoxProps(cb, props); + if (auto* ti = dynamic_cast(win)) + addTextInputProps(ti, props); + if (auto* sb = dynamic_cast(win)) + addSwitchButtonProps(sb, props); + if (auto* pb = dynamic_cast(win)) + addProgressBarProps(pb, props); + if (auto* lbl = dynamic_cast(win)) + addLabelProps(lbl, props); + if (auto* lsb = dynamic_cast(win)) + addLabeledStaticBoxProps(lsb, props); + + return props; +} +``` + +- [ ] **Step 3: Write CustomWidgetsPlugin.cpp — addButtonProps** + +```cpp +void CustomWidgetsPlugin::addButtonProps(Button* btn, + wxVector& props) +{ + using namespace wxInspector; + + wxVector styleChoices; + styleChoices.push_back("Regular"); + styleChoices.push_back("Confirm"); + styleChoices.push_back("Alert"); + styleChoices.push_back("Disabled"); + + auto styleToStr = [](ButtonStyle s) -> wxString { + switch (s) { + case ButtonStyle::Regular: return "Regular"; + case ButtonStyle::Confirm: return "Confirm"; + case ButtonStyle::Alert: return "Alert"; + case ButtonStyle::Disabled: return "Disabled"; + } + return "Regular"; + }; + + props.push_back({"Button Style", "Orca Button", PropertyType::Choice, + styleToStr(btn->GetStyle()), false, styleChoices, + [btn, styleToStr]() { return styleToStr(btn->GetStyle()); }, + [btn](const wxString& v) { + ButtonStyle s = ButtonStyle::Regular; + if (v == "Confirm") s = ButtonStyle::Confirm; + else if (v == "Alert") s = ButtonStyle::Alert; + else if (v == "Disabled") s = ButtonStyle::Disabled; + btn->SetStyle(s, btn->GetType()); + return true; + }}); + + wxVector typeChoices; + typeChoices.push_back("Compact"); + typeChoices.push_back("Window"); + typeChoices.push_back("Choice"); + typeChoices.push_back("Parameter"); + typeChoices.push_back("Icon"); + typeChoices.push_back("Expanded"); + + auto typeToStr = [](ButtonType t) -> wxString { + switch (t) { + case ButtonType::Compact: return "Compact"; + case ButtonType::Window: return "Window"; + case ButtonType::Choice: return "Choice"; + case ButtonType::Parameter: return "Parameter"; + case ButtonType::Icon: return "Icon"; + case ButtonType::Expanded: return "Expanded"; + } + return "Compact"; + }; + + props.push_back({"Button Type", "Orca Button", PropertyType::Choice, + typeToStr(btn->GetType()), false, typeChoices, + [btn, typeToStr]() { return typeToStr(btn->GetType()); }, + [btn](const wxString& v) { + ButtonType t = ButtonType::Compact; + if (v == "Window") t = ButtonType::Window; + else if (v == "Choice") t = ButtonType::Choice; + else if (v == "Parameter") t = ButtonType::Parameter; + else if (v == "Icon") t = ButtonType::Icon; + else if (v == "Expanded") t = ButtonType::Expanded; + btn->SetStyle(btn->GetStyle(), t); + return true; + }}); + + props.push_back({"Selected", "Orca Button", PropertyType::Boolean, + btn->IsSelected() ? "true" : "false", false, {}, + [btn]() { return btn->IsSelected() ? "true" : "false"; }, + [btn](const wxString& v) { + btn->SetSelected(v == "true"); + btn->Refresh(); + return true; + }}); +} +``` + +- [ ] **Step 4: Write CustomWidgetsPlugin.cpp — addCheckBoxProps** + +```cpp +void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Half Checked", "Orca CheckBox", PropertyType::Boolean, + cb->IsHalfChecked() ? "true" : "false", false, {}, + [cb]() { return cb->IsHalfChecked() ? "true" : "false"; }, + [cb](const wxString& v) { + cb->SetHalfChecked(v == "true"); + return true; + }}); +} +``` + +- [ ] **Step 5: Write CustomWidgetsPlugin.cpp — addTextInputProps** + +```cpp +void CustomWidgetsPlugin::addTextInputProps(TextInput* ti, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Label", "Orca TextInput", PropertyType::String, + ti->GetLabel(), false, {}, + [ti]() { return ti->GetLabel(); }, + [ti](const wxString& v) { ti->SetLabel(v); return true; }}); + + props.push_back({"Text Value", "Orca TextInput", PropertyType::String, + ti->GetTextCtrl()->GetValue(), false, {}, + [ti]() { return ti->GetTextCtrl()->GetValue(); }, + [ti](const wxString& v) { ti->GetTextCtrl()->SetValue(v); return true; }}); + + props.push_back({"Corner Radius", "Orca TextInput", PropertyType::Integer, + wxString::Format("%d", ti->GetCornerRadius()), false, {}, + [ti]() { return wxString::Format("%d", ti->GetCornerRadius()); }, + [ti](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + ti->SetCornerRadius((double) val); + ti->Refresh(); + return true; + }}); +} +``` + +- [ ] **Step 6: Write CustomWidgetsPlugin.cpp — addSwitchButtonProps** + +```cpp +void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Value", "Orca SwitchButton", PropertyType::Boolean, + sb->GetValue() ? "true" : "false", false, {}, + [sb]() { return sb->GetValue() ? "true" : "false"; }, + [sb](const wxString& v) { + sb->SetValue(v == "true"); + return true; + }}); +} +``` + +(Note: `GetValue()` and `SetValue()` are inherited from `wxBitmapToggleButton` → `wxToggleButton`.) + +- [ ] **Step 7: Write CustomWidgetsPlugin.cpp — addProgressBarProps** + +```cpp +void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Proportion", "Orca ProgressBar", PropertyType::String, + wxString::Format("%.2f", pb->m_proportion), false, {}, + [pb]() { return wxString::Format("%.2f", pb->m_proportion); }, + [pb](const wxString& v) { + double val; + if (wxSscanf(v, "%lf", &val) != 1) return false; + pb->m_proportion = val; + pb->Refresh(); + return true; + }}); + + props.push_back({"Show Number", "Orca ProgressBar", PropertyType::Boolean, + pb->m_shownumber ? "true" : "false", false, {}, + [pb]() { return pb->m_shownumber ? "true" : "false"; }, + [pb](const wxString& v) { + pb->m_shownumber = (v == "true"); + pb->Refresh(); + return true; + }}); +} +``` + +(Note: `m_proportion` and `m_shownumber` are public members on `ProgressBar`.) + +- [ ] **Step 8: Write CustomWidgetsPlugin.cpp — addLabelProps** + +```cpp +void CustomWidgetsPlugin::addLabelProps(Label* lbl, + wxVector& props) +{ + using namespace wxInspector; + + bool isHyperlink = (lbl->GetWindowStyleFlag() & 0x0020) != 0; // LB_HYPERLINK + + props.push_back({"Is Hyperlink", "Orca Label", PropertyType::Boolean, + isHyperlink ? "true" : "false", true, {}, + [lbl]() { + return (lbl->GetWindowStyleFlag() & 0x0020) ? "true" : "false"; + }, + nullptr}); + + props.push_back({"Font Point Size", "Orca Label", PropertyType::ReadOnly, + wxString::Format("%d", lbl->GetFont().GetPointSize()), true, {}, + [lbl]() { + return wxString::Format("%d", lbl->GetFont().GetPointSize()); + }, + nullptr}); +} +``` + +- [ ] **Step 9: Write CustomWidgetsPlugin.cpp — addLabeledStaticBoxProps** + +```cpp +void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Corner Radius", "LabeledStaticBox", PropertyType::Integer, + wxString::Format("%d", lsb->GetCornerRadius()), false, {}, + [lsb]() { return wxString::Format("%d", lsb->GetCornerRadius()); }, + [lsb](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + lsb->SetCornerRadius((int) val); + return true; + }}); + + props.push_back({"Border Width", "LabeledStaticBox", PropertyType::Integer, + wxString::Format("%d", lsb->GetBorderWidth()), false, {}, + [lsb]() { return wxString::Format("%d", lsb->GetBorderWidth()); }, + [lsb](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + lsb->SetBorderWidth((int) val); + return true; + }}); + + // Border Color: display as hex string + wxColour bc = lsb->GetBorderColor().colorForStates(0); + props.push_back({"Border Color", "LabeledStaticBox", PropertyType::String, + bc.GetAsString(wxC2S_HTML_SYNTAX), false, {}, + [lsb]() { + return lsb->GetBorderColor() + .colorForStates(0) + .GetAsString(wxC2S_HTML_SYNTAX); + }, + [lsb](const wxString& v) { + wxColour c(v); + if (!c.IsOk()) return false; + lsb->SetBorderColor(StateColor(c)); + return true; + }}); + + props.push_back({"Scale", "LabeledStaticBox", PropertyType::ReadOnly, + wxString::Format("%.2f", lsb->GetScale()), true, {}, + [lsb]() { return wxString::Format("%.2f", lsb->GetScale()); }, + nullptr}); +} +``` + +- [ ] **Step 10: Commit** + +```bash +git add src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp +git commit -m "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." +``` + +--- + +### Task 5: Wire plugins into MainFrame and CMakeLists + +**Files:** +- Modify: `src/slic3r/GUI/MainFrame.cpp` (add include + registration call) +- Modify: `src/slic3r/CMakeLists.txt` (add 4 source files) + +**Interfaces:** +- Consumes: Tasks 1-4 (all plugins and registration helper) +- Produces: Registered plugins available at runtime, buildable project + +- [ ] **Step 1: Add include in MainFrame.cpp** + +After the existing includes (around line 30, near the other Utils includes), add: + +```cpp +#include "slic3r/Utils/wxInspectorPlugins/Registration.hpp" +``` + +- [ ] **Step 2: Add registration call in MainFrame constructor** + +After `SetupInspectorAccelerator(this);` (currently line ~303), add: + +```cpp +RegisterOrcaInspectorPlugins(); +``` + +- [ ] **Step 3: Add source files to CMakeLists.txt** + +Find the `SLIC3R_GUI_SOURCES` list in `src/slic3r/CMakeLists.txt`. After the existing `Utils/*.cpp` entries (around line 650-754), add: + +```cmake +Utils/wxInspectorPlugins/DPIAwarePlugin.hpp +Utils/wxInspectorPlugins/DPIAwarePlugin.cpp +Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp +Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp +Utils/wxInspectorPlugins/Registration.hpp +``` + +(Note: Add all 5 files — 2 .hpp + 2 .cpp + 1 Registration.hpp. wxWidgets cmake needs headers listed too for the resource system.) + +- [ ] **Step 4: Commit** + +```bash +git add src/slic3r/GUI/MainFrame.cpp src/slic3r/CMakeLists.txt +git commit -m "feat: wire wxInspector plugins into MainFrame and build + +- Call RegisterOrcaInspectorPlugins() after SetupInspectorAccelerator +- Add all plugin source files to SLIC3R_GUI_SOURCES" +``` + +--- + +### Task 6: Build and verify + +**Files:** +- None modified (verification only) + +- [ ] **Step 1: Configure the build** + +```powershell +$cmakePath = "D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" +& $cmakePath --build . --config Debug --target ALL_BUILD -- -m +``` + +Expected: Build succeeds with zero errors and zero warnings from our new files. + +- [ ] **Step 2: Fix any compilation errors** + +If the build fails: +- Check that `#include` paths resolve (the `slic3r/GUI/…` relative paths use `src/` as the include root — verify this is set up in CMake via `include_directories`) +- Check that `ButtonStyle` and `ButtonType` enums are visible (they're defined in `Button.hpp`) +- Check that `StateColor` constructor from `wxColour` is valid (it has `StateColor(wxColour const&)`) +- Check that `LabeledStaticBox::GetBorderColor()` returns by value (StateColor copy is fine) +- On macOS: static box margin removal call needs `#ifdef __WXOSX__` guard + +- [ ] **Step 3: Launch OrcaSlicer and verify inspector** + +Launch the built OrcaSlicer, press Ctrl+Shift+I to open the inspector: +1. Select the MainFrame in the tree — verify "DPI Scaling" category appears with Scale Factor, Prev Scale Factor, EM Unit, Normal Font, Force Rescale +2. Select an Orca Button — verify "Orca Button" category appears +3. Select an Orca CheckBox — verify "Orca CheckBox" category appears +4. Edit a property value (e.g., Scale Factor) — verify the setter applies correctly +5. Select a LabeledStaticBox — verify corner radius, border width, border color, scale appear + +- [ ] **Step 5: Commit (if fixes were needed) or mark complete** + +```bash +git status +``` + +If clean: verification complete. If changes were made: `git add` and commit with fix message. diff --git a/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md b/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md new file mode 100644 index 0000000000..3a4e873af6 --- /dev/null +++ b/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md @@ -0,0 +1,102 @@ +# Move `wxInspectable` into `DPIAware` — Design Spec + +Date: 2026-07-23 +Branch: `dev/layout-inspector` + +## Overview + +Move the `wxInspector::wxInspectable` base class from individual leaf classes (`DPIDialog`, `MainFrame`) into the common `DPIAware

` template. This makes every DPIAware widget automatically visible in the inspector tree without requiring each subclass to opt in. + +## Motivation + +Currently, only `DPIDialog` and `MainFrame` explicitly inherit `wxInspectable`. `DPIFrame` (which `MainFrame` inherits from) does not — `MainFrame` adds it manually. This means: + +- Any `DPIAware` widget that isn't `DPIDialog` or `MainFrame` is invisible in the inspector tree +- `DPIFrame` subclasses (`BaseTransparentDPIFrame`, `ImageDPIFrame`, `ModelMallDialog`, `MediaFileFrame`, `SecondaryCheckDialog`, `PrintErrorDialog`, etc.) don't appear +- Adding a new DPIAware widget type requires remembering to also inherit `wxInspectable` + +Moving `wxInspectable` to `DPIAware` fixes this for all current and future DPIAware widgets at once. + +## Design + +### Change 1: `GUI_Utils.hpp` — `DPIAware

` + +Add `wxInspector::wxInspectable` as a second base class, and call `SetupInspectorAccelerator(this)` in the constructor (after `this->CenterOnParent()`): + +```cpp +// Before: +template class DPIAware : public P + +// After: +template class DPIAware : public P, public wxInspector::wxInspectable +``` + +Add in the constructor body (after `this->CenterOnParent()` at line 110): +```cpp +SetupInspectorAccelerator(this); +``` + +This gives every `DPIAware` widget both inspectability and the Ctrl+Shift+I keyboard shortcut automatically. `#include ` is already present in the file. + +### Change 2: `GUI_Utils.hpp` — `DPIDialog` + +Remove the now-redundant `wxInspector::wxInspectable` and the `SetupInspectorAccelerator(this)` call: + +```cpp +// Before: +class DPIDialog : public DPIAware, public wxInspector::wxInspectable +// ... + SetupInspectorAccelerator(this); + +// After: +class DPIDialog : public DPIAware +// (SetupInspectorAccelerator call removed — now done in DPIAware constructor) +``` + +`DPIDialog` gets `wxInspectable` and the accelerator through `DPIAware` now. + +### Change 3: `MainFrame.hpp` — `MainFrame` + +Remove the now-redundant `wxInspector::wxInspectable`: + +```cpp +// Before: +class MainFrame : public DPIFrame, public wxInspector::wxInspectable + +// After: +class MainFrame : public DPIFrame +``` + +`MainFrame` gets `wxInspectable` through `DPIFrame` → `DPIAware`. + +### Change 4: `MainFrame.cpp` — `MainFrame` constructor + +Remove the now-redundant `SetupInspectorAccelerator(this)` call (line 304). It will be called automatically by the `DPIAware` constructor. + +## Impact + +| Widget | Before | After | +|--------|--------|-------| +| `DPIDialog` subclasses (~80) | ✓ inspectable | ✓ inspectable (transitive) | +| `MainFrame` | ✓ inspectable | ✓ inspectable (transitive) | +| `DPIFrame` subclasses (8 others) | ✗ invisible | ✓ inspectable | +| Future `DPIAware` | ✗ invisible | ✓ inspectable | + +## Files Modified + +| File | Change | +|------|--------| +| `src/slic3r/GUI/GUI_Utils.hpp` | `DPIAware

` gains `wxInspector::wxInspectable` + `SetupInspectorAccelerator(this)` call; `DPIDialog` drops redundant `wxInspector::wxInspectable` and `SetupInspectorAccelerator(this)` | +| `src/slic3r/GUI/MainFrame.hpp` | `MainFrame` drops redundant `wxInspector::wxInspectable` | +| `src/slic3r/GUI/MainFrame.cpp` | Remove redundant `SetupInspectorAccelerator(this)` from MainFrame constructor | + +## Non-Goals + +- The `DPIAwarePlugin` detection logic (`dynamic_cast` / `dynamic_cast`) is unchanged +- No new DPI properties — this is purely about tree visibility and accelerator setup + +## Risk Assessment + +- **Multiple inheritance**: `DPIAware

` already has a vtable (virtual destructor). Adding `wxInspectable` adds a second base but no additional data members. The `wxInspector::wxInspectable` class is expected to be a lightweight marker interface. +- **Build**: No new includes needed; `` is already included in `GUI_Utils.hpp`. +- **Cross-platform**: The change is standard C++ multiple inheritance — no platform-specific concerns. diff --git a/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md b/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md new file mode 100644 index 0000000000..5715635969 --- /dev/null +++ b/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md @@ -0,0 +1,244 @@ +# 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 + +1. **DPIAware properties** — Inspect and update `scale_factor`, `prev_scale_factor`, `em_unit`, and `normal_font` on any DPIAware-derived widget +2. **Custom widget properties** — Surface Orca-specific properties on `Button`, `CheckBox`, `TextInput`, `SwitchButton`, `ProgressBar`, `Label`, and `LabeledStaticBox` +3. **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` and `dynamic_cast` as detection gates. `DPIFrame` = `DPIAware`, `DPIDialog` = `DPIAware`. 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

` template class (public section): + +```cpp +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 + +```cpp +bool CanProvideProperties(wxClassInfo* info) override { + // Gated in GetProperties via dynamic_cast on the window itself + return info->IsKindOf(CLASSINFO(wxWindow)); +} +``` + +In `GetProperties`: +```cpp +auto* win = obj.AsWindow(); +bool isDPI = dynamic_cast(win) || dynamic_cast(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`) + +```cpp +auto* win = obj.AsWindow(); +if (auto* btn = dynamic_cast(win)) { addButtonProperties(btn, props); } +if (auto* cb = dynamic_cast(win)) { addCheckBoxProperties(cb, props); } +if (auto* ti = dynamic_cast(win)) { addTextInputProperties(ti, props); } +if (auto* sb = dynamic_cast(win)) { addSwitchButtonProperties(sb, props); } +if (auto* pb = dynamic_cast(win)) { addProgressBarProperties(pb, props); } +if (auto* lbl = dynamic_cast(win)) { addLabelProperties(lbl, props); } +if (auto* lsb = dynamic_cast(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: +```cpp +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: +```cpp +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`: +```cpp +int GetCornerRadius() const { return static_cast(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`: +```cpp +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

`: `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.cmake` fetches and builds wxInspector +- `src/CMakeLists.txt` lines 92-93 link `wxInspector::wxInspector` into `wxWidgets_LIBRARIES` +- The plugin files only need `#include ` and `#include ` — 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 `GetProperties` call (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` / `ToLong` with validation (matching built-in patterns). Bogus input is rejected — setter returns `false`, property grid shows error state. +- **DPI drift**: Setting `scale_factor` without triggering rescale means displayed sizes don't match the new factor. This is acceptable — the inspector is a developer tool; operators know to call `Layout()` after making changes. +- **Missing widget type**: If a `dynamic_cast` fails for all types, only built-in wxWindow properties are shown. No crash, no error — just reduced info. + +## Future Work (Out of Scope) + +- **StateColor visualization**: `StateColor` is 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. diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml index a1699f2b93..c33425f23f 100644 --- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml +++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml @@ -306,6 +306,12 @@ modules: sha256: c08bc65a81971c1dd5783182826503369466c7e67374d1646519adf05207b684 dest: external-packages/python3 + # wxInspector 1.0.0 + - type: file + url: https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip + sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7 + dest: external-packages/wxInspector + # --------------------------------------------------------------- # Fallback archives for deps normally provided by the GNOME SDK. # These are only used if find_package() fails to locate them. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 07c492e617..79b49cfd16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -88,6 +88,10 @@ if (SLIC3R_GUI) list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL) # list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) + + find_package(wxInspector REQUIRED) + list(APPEND wxWidgets_LIBRARIES "wxInspector::wxInspector") + message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") add_subdirectory(slic3r) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 06ebbbf41d..2dee4bc780 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -51,4 +51,9 @@ // Enable extension of tool position imgui dialog to show actual speed profile #define ENABLE_ACTUAL_SPEED_DEBUG 1 +// Disable layout inspector for public release +#if BBL_RELEASE_TO_PUBLIC +#define WXINSPECTOR_DISABLE +#endif + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 35cf96d171..b98396f943 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -752,6 +752,11 @@ set(SLIC3R_GUI_SOURCES Utils/WxFontUtils.hpp Utils/FileTransferUtils.cpp Utils/FileTransferUtils.hpp + Utils/wxInspectorPlugins/DPIAwarePlugin.hpp + Utils/wxInspectorPlugins/DPIAwarePlugin.cpp + Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp + Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp + Utils/wxInspectorPlugins/Registration.hpp ) add_subdirectory(GUI/DeviceCore) diff --git a/src/slic3r/GUI/DeviceCore/DevUtil.h b/src/slic3r/GUI/DeviceCore/DevUtil.h index 0b02dd25a9..6644b8b5b9 100644 --- a/src/slic3r/GUI/DeviceCore/DevUtil.h +++ b/src/slic3r/GUI/DeviceCore/DevUtil.h @@ -13,6 +13,7 @@ #include #include +#include #include "nlohmann/json.hpp" diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 29aad072f0..aa57f76598 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -96,6 +96,7 @@ #include "../Utils/PresetUpdater.hpp" #include "../Utils/PrintHost.hpp" #include "../Utils/Process.hpp" +#include "../Utils/wxInspectorPlugins/Registration.hpp" #include "../Utils/MacDarkMode.hpp" #include "../Utils/Http.hpp" #include "../Utils/InstanceID.hpp" @@ -2835,6 +2836,9 @@ bool GUI_App::on_init_inner() ::Label::initSysFont(); + // Register wxInspector plugins for Orca custom controls + RegisterOrcaInspectorPlugins(); + // Set initialization of image handlers before any UI actions - See GH issue #7469 wxInitAllImageHandlers(); #ifdef NDEBUG diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index d6767d3310..427ba9f0ad 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "Event.hpp" @@ -88,7 +89,7 @@ void update_dark_ui(wxWindow* window); extern std::deque dialogStack; -template class DPIAware : public P +template class DPIAware : public P, public wxInspector::wxInspectable { public: DPIAware(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, @@ -107,6 +108,7 @@ public: this->SetFont(m_normal_font); #endif this->CenterOnParent(); + SetupInspectorAccelerator(this); #ifdef _WIN32 update_dark_ui(this); #endif @@ -182,6 +184,11 @@ public: float scale_factor() const { return m_scale_factor; } float prev_scale_factor() const { return m_prev_scale_factor; } + // Only meant to be used by inspector, not public API + void set_scale_factor(float v) { m_scale_factor = v; } + void set_prev_scale_factor(float v) { m_prev_scale_factor = v; } + void set_em_unit(int v) { m_em_unit = v; } + bool force_rescale() const { return m_force_rescale; } int em_unit() const { return m_em_unit; } // int font_size() const { return m_font_size; } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 85923c87fc..5d7f629075 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -739,7 +739,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ return; } - if (evt.CmdDown() && evt.GetKeyCode() == 'I') { + if (evt.CmdDown() && evt.GetKeyCode() == 'I' && !evt.ShiftDown()) { if (!can_add_models()) return; if (m_plater) { m_plater->add_file(); } return; diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp index bc093b512a..94b245a75b 100644 --- a/src/slic3r/GUI/Widgets/Button.hpp +++ b/src/slic3r/GUI/Widgets/Button.hpp @@ -77,6 +77,11 @@ public: void SetSelected(bool selected = true) { m_selected = selected; } + // Only meant to be used by inspector, not public API + ButtonStyle GetStyle() const { return m_style; } + ButtonType GetType() const { return m_type; } + bool IsSelected() const { return m_selected; } + bool Enable(bool enable = true) override; void EnableTooltipEvenDisabled();// The tip will be shown even if the button is disabled diff --git a/src/slic3r/GUI/Widgets/CheckBox.hpp b/src/slic3r/GUI/Widgets/CheckBox.hpp index 01b801a215..abff45ecf1 100644 --- a/src/slic3r/GUI/Widgets/CheckBox.hpp +++ b/src/slic3r/GUI/Widgets/CheckBox.hpp @@ -15,6 +15,9 @@ public: void SetHalfChecked(bool value = true); + // Only meant to be used by inspector, not public API + bool IsHalfChecked() const { return m_half_checked; } + void Rescale(); #ifdef __WXOSX__ diff --git a/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp b/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp index 3418269e57..f42175ae05 100644 --- a/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp +++ b/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp @@ -46,6 +46,12 @@ public: bool Enable(bool enable) override; + // Only meant to be used by inspector, not public API + 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; } + private: void PickDC(wxDC& dc); diff --git a/src/slic3r/GUI/Widgets/TextInput.hpp b/src/slic3r/GUI/Widgets/TextInput.hpp index 20407b5de2..9aca7037c4 100644 --- a/src/slic3r/GUI/Widgets/TextInput.hpp +++ b/src/slic3r/GUI/Widgets/TextInput.hpp @@ -43,6 +43,9 @@ public: void SetCornerRadius(double radius); + // Only meant to be used by inspector, not public API + int GetCornerRadius() const { return static_cast(radius); } + void SetLabel(const wxString& label); void SetStaticTips(const wxString& tips, const wxBitmap& bitmap); diff --git a/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp new file mode 100644 index 0000000000..df1b495bff --- /dev/null +++ b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp @@ -0,0 +1,274 @@ +#include "CustomWidgetsPlugin.hpp" + +#include "slic3r/GUI/Widgets/Button.hpp" +#include "slic3r/GUI/Widgets/CheckBox.hpp" +#include "slic3r/GUI/Widgets/TextInput.hpp" +#include "slic3r/GUI/Widgets/SwitchButton.hpp" +#include "slic3r/GUI/Widgets/ProgressBar.hpp" +#include "slic3r/GUI/Widgets/Label.hpp" +#include "slic3r/GUI/Widgets/LabeledStaticBox.hpp" + +#include +#include + +wxString CustomWidgetsPlugin::GetName() const +{ + return "OrcaCustomWidgets"; +} + +bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info) +{ + return info->IsKindOf(CLASSINFO(wxWindow)); +} + +wxVector CustomWidgetsPlugin::GetProperties( + wxInspector::InspectableObject& obj) +{ + wxVector props; + wxWindow* win = obj.AsWindow(); + if (!win) return props; + + if (auto* btn = dynamic_cast(win)) + addButtonProps(btn, props); + if (auto* cb = dynamic_cast(win)) + addCheckBoxProps(cb, props); + if (auto* ti = dynamic_cast(win)) + addTextInputProps(ti, props); + if (auto* sb = dynamic_cast(win)) + addSwitchButtonProps(sb, props); + if (auto* pb = dynamic_cast(win)) + addProgressBarProps(pb, props); + if (auto* lbl = dynamic_cast(win)) + addLabelProps(lbl, props); + if (auto* lsb = dynamic_cast(win)) + addLabeledStaticBoxProps(lsb, props); + + return props; +} + +void CustomWidgetsPlugin::addButtonProps(Button* btn, + wxVector& props) +{ + using namespace wxInspector; + + wxVector styleChoices; + styleChoices.push_back("Regular"); + styleChoices.push_back("Confirm"); + styleChoices.push_back("Alert"); + styleChoices.push_back("Disabled"); + + auto styleToStr = [](ButtonStyle s) -> wxString { + switch (s) { + case ButtonStyle::Regular: return "Regular"; + case ButtonStyle::Confirm: return "Confirm"; + case ButtonStyle::Alert: return "Alert"; + case ButtonStyle::Disabled: return "Disabled"; + } + return "Regular"; + }; + + props.push_back({"Button Style", "Orca Button", PropertyType::Choice, + styleToStr(btn->GetStyle()), false, styleChoices, + [btn, styleToStr]() { return styleToStr(btn->GetStyle()); }, + [btn](const wxString& v) { + ButtonStyle s = ButtonStyle::Regular; + if (v == "Confirm") s = ButtonStyle::Confirm; + else if (v == "Alert") s = ButtonStyle::Alert; + else if (v == "Disabled") s = ButtonStyle::Disabled; + btn->SetStyle(s, btn->GetType()); + return true; + }}); + + wxVector typeChoices; + typeChoices.push_back("Compact"); + typeChoices.push_back("Window"); + typeChoices.push_back("Choice"); + typeChoices.push_back("Parameter"); + typeChoices.push_back("Icon"); + typeChoices.push_back("Expanded"); + + auto typeToStr = [](ButtonType t) -> wxString { + switch (t) { + case ButtonType::Compact: return "Compact"; + case ButtonType::Window: return "Window"; + case ButtonType::Choice: return "Choice"; + case ButtonType::Parameter: return "Parameter"; + case ButtonType::Icon: return "Icon"; + case ButtonType::Expanded: return "Expanded"; + } + return "Compact"; + }; + + props.push_back({"Button Type", "Orca Button", PropertyType::Choice, + typeToStr(btn->GetType()), false, typeChoices, + [btn, typeToStr]() { return typeToStr(btn->GetType()); }, + [btn](const wxString& v) { + ButtonType t = ButtonType::Compact; + if (v == "Window") t = ButtonType::Window; + else if (v == "Choice") t = ButtonType::Choice; + else if (v == "Parameter") t = ButtonType::Parameter; + else if (v == "Icon") t = ButtonType::Icon; + else if (v == "Expanded") t = ButtonType::Expanded; + btn->SetStyle(btn->GetStyle(), t); + return true; + }}); + + props.push_back({"Selected", "Orca Button", PropertyType::Boolean, + btn->IsSelected() ? "true" : "false", false, {}, + [btn]() { return btn->IsSelected() ? "true" : "false"; }, + [btn](const wxString& v) { + btn->SetSelected(v == "true"); + btn->Refresh(); + return true; + }}); +} + +void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Half Checked", "Orca CheckBox", PropertyType::Boolean, + cb->IsHalfChecked() ? "true" : "false", false, {}, + [cb]() { return cb->IsHalfChecked() ? "true" : "false"; }, + [cb](const wxString& v) { + cb->SetHalfChecked(v == "true"); + return true; + }}); +} + +void CustomWidgetsPlugin::addTextInputProps(TextInput* ti, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Label", "Orca TextInput", PropertyType::String, + ti->GetLabel(), false, {}, + [ti]() { return ti->GetLabel(); }, + [ti](const wxString& v) { ti->SetLabel(v); return true; }}); + + props.push_back({"Text Value", "Orca TextInput", PropertyType::String, + ti->GetTextCtrl()->GetValue(), false, {}, + [ti]() { return ti->GetTextCtrl()->GetValue(); }, + [ti](const wxString& v) { ti->GetTextCtrl()->SetValue(v); return true; }}); + + props.push_back({"Corner Radius", "Orca TextInput", PropertyType::Integer, + wxString::Format("%d", ti->GetCornerRadius()), false, {}, + [ti]() { return wxString::Format("%d", ti->GetCornerRadius()); }, + [ti](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + ti->SetCornerRadius((double) val); + ti->Refresh(); + return true; + }}); +} + +void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Value", "Orca SwitchButton", PropertyType::Boolean, + sb->GetValue() ? "true" : "false", false, {}, + [sb]() { return sb->GetValue() ? "true" : "false"; }, + [sb](const wxString& v) { + sb->SetValue(v == "true"); + return true; + }}); +} + +void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Proportion", "Orca ProgressBar", PropertyType::String, + wxString::Format("%.2f", pb->m_proportion), false, {}, + [pb]() { return wxString::Format("%.2f", pb->m_proportion); }, + [pb](const wxString& v) { + double val; + if (wxSscanf(v, "%lf", &val) != 1) return false; + pb->m_proportion = val; + pb->Refresh(); + return true; + }}); + + props.push_back({"Show Number", "Orca ProgressBar", PropertyType::Boolean, + pb->m_shownumber ? "true" : "false", false, {}, + [pb]() { return pb->m_shownumber ? "true" : "false"; }, + [pb](const wxString& v) { + pb->m_shownumber = (v == "true"); + pb->Refresh(); + return true; + }}); +} + +void CustomWidgetsPlugin::addLabelProps(Label* lbl, + wxVector& props) +{ + using namespace wxInspector; + + bool isHyperlink = (lbl->GetWindowStyleFlag() & LB_HYPERLINK) != 0; + + props.push_back({"Is Hyperlink", "Orca Label", PropertyType::Boolean, + isHyperlink ? "true" : "false", true, {}, + [lbl]() { + return (lbl->GetWindowStyleFlag() & LB_HYPERLINK) ? "true" : "false"; + }, + nullptr}); + + props.push_back({"Font Point Size", "Orca Label", PropertyType::ReadOnly, + wxString::Format("%d", lbl->GetFont().GetPointSize()), true, {}, + [lbl]() { + return wxString::Format("%d", lbl->GetFont().GetPointSize()); + }, + nullptr}); +} + +void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb, + wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Corner Radius", "LabeledStaticBox", PropertyType::Integer, + wxString::Format("%d", lsb->GetCornerRadius()), false, {}, + [lsb]() { return wxString::Format("%d", lsb->GetCornerRadius()); }, + [lsb](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + lsb->SetCornerRadius((int) val); + return true; + }}); + + props.push_back({"Border Width", "LabeledStaticBox", PropertyType::Integer, + wxString::Format("%d", lsb->GetBorderWidth()), false, {}, + [lsb]() { return wxString::Format("%d", lsb->GetBorderWidth()); }, + [lsb](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + lsb->SetBorderWidth((int) val); + return true; + }}); + + // Border Color: display as hex string + wxColour bc = lsb->GetBorderColor().colorForStates(0); + props.push_back({"Border Color", "LabeledStaticBox", PropertyType::String, + bc.GetAsString(wxC2S_HTML_SYNTAX), false, {}, + [lsb]() { + return lsb->GetBorderColor() + .colorForStates(0) + .GetAsString(wxC2S_HTML_SYNTAX); + }, + [lsb](const wxString& v) { + wxColour c(v); + if (!c.IsOk()) return false; + lsb->SetBorderColor(StateColor(c)); + return true; + }}); + + props.push_back({"Scale", "LabeledStaticBox", PropertyType::ReadOnly, + wxString::Format("%.2f", lsb->GetScale()), true, {}, + [lsb]() { return wxString::Format("%.2f", lsb->GetScale()); }, + nullptr}); +} diff --git a/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp new file mode 100644 index 0000000000..083ed8a74c --- /dev/null +++ b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin +{ +public: + wxString GetName() const override; + + bool CanProvideProperties(wxClassInfo* info) override; + + wxVector GetProperties( + wxInspector::InspectableObject& obj) override; + +private: + void addButtonProps(class Button* btn, + wxVector& props); + void addCheckBoxProps(class CheckBox* cb, + wxVector& props); + void addTextInputProps(class TextInput* ti, + wxVector& props); + void addSwitchButtonProps(class SwitchButton* sb, + wxVector& props); + void addProgressBarProps(class ProgressBar* pb, + wxVector& props); + void addLabelProps(class Label* lbl, + wxVector& props); + void addLabeledStaticBoxProps(class LabeledStaticBox* lsb, + wxVector& props); +}; diff --git a/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp new file mode 100644 index 0000000000..f52a5d4313 --- /dev/null +++ b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp @@ -0,0 +1,82 @@ +#include "DPIAwarePlugin.hpp" + +#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware

+ +#include +#include + +namespace { + +template +void addDPIProps(T* dpi, wxVector& props) +{ + using namespace wxInspector; + + props.push_back({"Scale Factor", "DPI Scaling", PropertyType::String, + wxString::Format("%.2f", dpi->scale_factor()), false, {}, + [dpi]() { return wxString::Format("%.2f", dpi->scale_factor()); }, + [dpi](const wxString& v) { + double val; + if (wxSscanf(v, "%lf", &val) != 1) return false; + dpi->set_scale_factor((float) val); + return true; + }}); + + props.push_back({"Prev Scale Factor", "DPI Scaling", PropertyType::String, + wxString::Format("%.2f", dpi->prev_scale_factor()), false, {}, + [dpi]() { return wxString::Format("%.2f", dpi->prev_scale_factor()); }, + [dpi](const wxString& v) { + double val; + if (wxSscanf(v, "%lf", &val) != 1) return false; + dpi->set_prev_scale_factor((float) val); + return true; + }}); + + props.push_back({"EM Unit", "DPI Scaling", PropertyType::Integer, + wxString::Format("%d", dpi->em_unit()), false, {}, + [dpi]() { return wxString::Format("%d", dpi->em_unit()); }, + [dpi](const wxString& v) { + long val; + if (!v.ToLong(&val)) return false; + dpi->set_em_unit((int) val); + return true; + }}); + + props.push_back({"Normal Font", "DPI Scaling", PropertyType::ReadOnly, + dpi->normal_font().GetNativeFontInfoDesc(), true, {}, + [dpi]() { return dpi->normal_font().GetNativeFontInfoDesc(); }, + nullptr}); + + props.push_back({"Force Rescale", "DPI Scaling", PropertyType::Boolean, + dpi->force_rescale() ? "true" : "false", true, {}, + [dpi]() { return dpi->force_rescale() ? "true" : "false"; }, + nullptr}); +} + +} // anonymous namespace + +wxString DPIAwarePlugin::GetName() const +{ + return "OrcaDPIAware"; +} + +bool DPIAwarePlugin::CanProvideProperties(wxClassInfo* info) +{ + return info->IsKindOf(CLASSINFO(wxWindow)); +} + +wxVector DPIAwarePlugin::GetProperties( + wxInspector::InspectableObject& obj) +{ + wxVector props; + wxWindow* win = obj.AsWindow(); + if (!win) return props; + + if (auto* frame = dynamic_cast(win)) { + addDPIProps(frame, props); + } else if (auto* dlg = dynamic_cast(win)) { + addDPIProps(dlg, props); + } + + return props; +} diff --git a/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp new file mode 100644 index 0000000000..5d78342967 --- /dev/null +++ b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +class DPIAwarePlugin : public wxInspector::wxInspectorPlugin +{ +public: + wxString GetName() const override; + + bool CanProvideProperties(wxClassInfo* info) override; + + wxVector GetProperties( + wxInspector::InspectableObject& obj) override; +}; diff --git a/src/slic3r/Utils/wxInspectorPlugins/Registration.hpp b/src/slic3r/Utils/wxInspectorPlugins/Registration.hpp new file mode 100644 index 0000000000..54bbc0dbe4 --- /dev/null +++ b/src/slic3r/Utils/wxInspectorPlugins/Registration.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "DPIAwarePlugin.hpp" +#include "CustomWidgetsPlugin.hpp" + +#include + +inline void RegisterOrcaInspectorPlugins() +{ + static DPIAwarePlugin dpiaware; + static CustomWidgetsPlugin customWidgets; + wxInspector::RegisterPlugin(&dpiaware); + wxInspector::RegisterPlugin(&customWidgets); +}