Files
OrcaSlicer/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md
Noisyfox 63044b7661 feat: Add layout debugging/inspecting tool (#14919)
* 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>
2026-07-26 20:25:25 +08:00

25 KiB

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<DPIFrame*>/<DPIDialog*> 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<P>::set_scale_factor(float), DPIAware<P>::set_prev_scale_factor(float), DPIAware<P>::set_em_unit(int), DPIAware<P>::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:

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:

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:

bool IsHalfChecked() const { return m_half_checked; }
  • Step 4: Add TextInput getter in TextInput.hpp

After line 44 (void SetCornerRadius(double radius);), add:

int GetCornerRadius() const { return static_cast<int>(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:

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
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

mkdir -p src/slic3r/Utils/wxInspectorPlugins
  • Step 2: Write Registration.hpp
#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
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

#pragma once

#include <wx/inspector/plugin.h>

class DPIAwarePlugin : public wxInspector::wxInspectorPlugin
{
public:
    wxString GetName() const override;

    bool CanProvideProperties(wxClassInfo* info) override;

    wxVector<wxInspector::PropertyDef> GetProperties(
        wxInspector::InspectableObject& obj) override;
};
  • Step 2: Write DPIAwarePlugin.cpp
#include "DPIAwarePlugin.hpp"

#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware<P>

#include <wx/window.h>

namespace {

template<typename T>
void addDPIProps(T* dpi, wxVector<wxInspector::PropertyDef>& 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<wxInspector::PropertyDef> DPIAwarePlugin::GetProperties(
    wxInspector::InspectableObject& obj)
{
    wxVector<wxInspector::PropertyDef> props;
    wxWindow* win = obj.AsWindow();
    if (!win) return props;

    if (auto* frame = dynamic_cast<DPIFrame*>(win)) {
        addDPIProps(frame, props);
    } else if (auto* dlg = dynamic_cast<DPIDialog*>(win)) {
        addDPIProps(dlg, props);
    }

    return props;
}
  • Step 3: Commit
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

#pragma once

#include <wx/inspector/plugin.h>

class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin
{
public:
    wxString GetName() const override;

    bool CanProvideProperties(wxClassInfo* info) override;

    wxVector<wxInspector::PropertyDef> GetProperties(
        wxInspector::InspectableObject& obj) override;

private:
    void addButtonProps(class Button* btn,
        wxVector<wxInspector::PropertyDef>& props);
    void addCheckBoxProps(class CheckBox* cb,
        wxVector<wxInspector::PropertyDef>& props);
    void addTextInputProps(class TextInput* ti,
        wxVector<wxInspector::PropertyDef>& props);
    void addSwitchButtonProps(class SwitchButton* sb,
        wxVector<wxInspector::PropertyDef>& props);
    void addProgressBarProps(class ProgressBar* pb,
        wxVector<wxInspector::PropertyDef>& props);
    void addLabelProps(class Label* lbl,
        wxVector<wxInspector::PropertyDef>& props);
    void addLabeledStaticBoxProps(class LabeledStaticBox* lsb,
        wxVector<wxInspector::PropertyDef>& props);
};
  • Step 2: Write CustomWidgetsPlugin.cpp — includes and GetName/CanProvideProperties
#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 <wx/window.h>
#include <wx/tglbtn.h>

wxString CustomWidgetsPlugin::GetName() const
{
    return "OrcaCustomWidgets";
}

bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info)
{
    return info->IsKindOf(CLASSINFO(wxWindow));
}

wxVector<wxInspector::PropertyDef> CustomWidgetsPlugin::GetProperties(
    wxInspector::InspectableObject& obj)
{
    wxVector<wxInspector::PropertyDef> props;
    wxWindow* win = obj.AsWindow();
    if (!win) return props;

    if (auto* btn = dynamic_cast<Button*>(win))
        addButtonProps(btn, props);
    if (auto* cb = dynamic_cast<CheckBox*>(win))
        addCheckBoxProps(cb, props);
    if (auto* ti = dynamic_cast<TextInput*>(win))
        addTextInputProps(ti, props);
    if (auto* sb = dynamic_cast<SwitchButton*>(win))
        addSwitchButtonProps(sb, props);
    if (auto* pb = dynamic_cast<ProgressBar*>(win))
        addProgressBarProps(pb, props);
    if (auto* lbl = dynamic_cast<Label*>(win))
        addLabelProps(lbl, props);
    if (auto* lsb = dynamic_cast<LabeledStaticBox*>(win))
        addLabeledStaticBoxProps(lsb, props);

    return props;
}
  • Step 3: Write CustomWidgetsPlugin.cpp — addButtonProps
void CustomWidgetsPlugin::addButtonProps(Button* btn,
    wxVector<wxInspector::PropertyDef>& props)
{
    using namespace wxInspector;

    wxVector<wxString> 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<wxString> 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
void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb,
    wxVector<wxInspector::PropertyDef>& 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
void CustomWidgetsPlugin::addTextInputProps(TextInput* ti,
    wxVector<wxInspector::PropertyDef>& 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
void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb,
    wxVector<wxInspector::PropertyDef>& 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 wxBitmapToggleButtonwxToggleButton.)

  • Step 7: Write CustomWidgetsPlugin.cpp — addProgressBarProps
void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb,
    wxVector<wxInspector::PropertyDef>& 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
void CustomWidgetsPlugin::addLabelProps(Label* lbl,
    wxVector<wxInspector::PropertyDef>& 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
void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb,
    wxVector<wxInspector::PropertyDef>& 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
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:

#include "slic3r/Utils/wxInspectorPlugins/Registration.hpp"
  • Step 2: Add registration call in MainFrame constructor

After SetupInspectorAccelerator(this); (currently line ~303), add:

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:

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
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

$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
git status

If clean: verification complete. If changes were made: git add and commit with fix message.