graphic design tool
git clone https://git.lucas.co/cce-designer.git
Command palette: toggle_wireframe, the Render node's Show Wireframe as a command
The wireframe pass — wires drawn over the filled geometry — was only
reachable as the Render utility node's "Show Wireframe" toggle in the
parameter pane. A registry row (id toggle_wireframe, label "Show
Wireframe", Viewport context, unbound) makes it a palette entry, a
rebindable chord and an MCP run_command, beside Show Grid / Show Cube /
Show Origin.
Action::ToggleWireframe flips the live flag AND writes the Render node's
toggle: apply_settings_from_menubar_subnets reads render settings back
from that node on every parameter edit, so a flag flipped alone would
have reverted silently at the next unrelated edit. The parameter pane's
live-toggle refresh gains a Render arm so the switch shows moved while
the node is selected.
Tested: the command flips both, and the read-back path keeps the value.
Verified in a scale-2 shadow: "wire" in the palette lists the row first,
and running it over MCP draws the lattice on the sphere.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/app.rs | 32 ++++++++++++++++++++++++++++++++
src/command.rs | 1 +
src/main.rs | 37 +++++++++++++++++++++++++++++++++++++
src/shortcut.rs | 1 +
4 files changed, 71 insertions(+)
diff --git a/src/app.rs b/src/app.rs
index 4a0d994..992be70 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2762,6 +2762,23 @@ impl State {
}
}
+ /// Write a toggle on the session's Render utility node — the source
+ /// `apply_settings_from_menubar_subnets` reads render settings from —
+ /// so a command that changed the live state leaves the node agreeing
+ /// with it. Nothing when the project has no Render node yet.
+ fn write_render_toggle(&mut self, name: &str, val: bool) {
+ if let Some(p) = self
+ .fs_root
+ .children
+ .iter_mut()
+ .find(|c| c.node_type == "meta")
+ .and_then(|s| s.children.iter_mut().find(|c| c.name == "Render"))
+ .and_then(|n| n.params.iter_mut().find(|p| p.name == name))
+ {
+ p.default = if val { "true" } else { "false" }.to_string();
+ }
+ }
+
pub(crate) fn refresh_main_node_live_toggles(&mut self, slot_idx: usize) {
let live_main: [(&str, bool); 2] = [
("Circular Pane", self.circular_network_pane),
@@ -2780,12 +2797,17 @@ impl State {
("Show Reference Cube", self.viewport().show_cube),
("Show Origin Axes", self.viewport().show_origin),
];
+ let live_render: [(&str, bool); 2] = [
+ ("Show Wireframe", self.wireframe),
+ ("Wire Single Color", self.wire_single_color),
+ ];
let dir = self.param_editor_dir_mut();
let Some(child) = dir.children.get_mut(slot_idx) else { return };
let live: &[(&str, bool)] = match child.name.as_str() {
"Main" => &live_main,
"View" => &live_view,
"Guides" => &live_guides,
+ "Render" => &live_render,
_ => return,
};
for &(name, on) in live {
@@ -5577,6 +5599,16 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 3, val);
settings_changed = true;
}
+ Action::ToggleWireframe => {
+ // The Render utility node's "Show Wireframe" toggle is the
+ // value `apply_settings_from_menubar_subnets` reads back on
+ // EVERY parameter edit, so flipping the flag alone would
+ // revert on the next unrelated edit: the node's toggle is
+ // written too, and the pane shows the switch moved.
+ let val = !self.wireframe;
+ self.wireframe = val;
+ self.write_render_toggle("Show Wireframe", val);
+ }
Action::ToggleSquareViewport => {
self.square_viewport = !self.square_viewport;
let val = self.square_viewport;
diff --git a/src/command.rs b/src/command.rs
index bed43f4..a1e8b17 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -175,6 +175,7 @@ pub const COMMANDS: &[Command] = &[
Command { id: "toggle_cube", label: "Show Cube", context: Context::Viewport, run: Run::Key(Action::ToggleCube), default_chord: Some("Ctrl+e") },
Command { id: "toggle_origin", label: "Show Origin", context: Context::Viewport, run: Run::Key(Action::ToggleOrigin), default_chord: None },
Command { id: "toggle_camera_pivot", label: "Show Camera Pivot", context: Context::Viewport, run: Run::Key(Action::ToggleCameraPivot), default_chord: None },
+ Command { id: "toggle_wireframe", label: "Show Wireframe", context: Context::Viewport, run: Run::Key(Action::ToggleWireframe), default_chord: None },
Command { id: "toggle_square_viewport", label: "Square Aspect", context: Context::Viewport, run: Run::Key(Action::ToggleSquareViewport), default_chord: Some("Ctrl+a") },
// --- Parameters ---
diff --git a/src/main.rs b/src/main.rs
index d464a17..218a4eb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3985,6 +3985,43 @@ mod tests {
assert_eq!(fuzzy_rank("S G", &items), fuzzy_rank("sg", &items));
}
+ /// The wireframe toggle is a palette row that flips the live flag AND
+ /// the Render node's "Show Wireframe" switch. The node matters: it is
+ /// what `apply_settings_from_menubar_subnets` reads back on every
+ /// parameter edit, so a flag flipped alone would revert on the next
+ /// unrelated edit. No settings write is involved, so running it here
+ /// touches nothing outside the test.
+ #[test]
+ fn test_toggle_wireframe_flips_the_flag_and_the_render_node() {
+ use crate::command::{by_id, Run};
+ let cmd = by_id("toggle_wireframe").expect("no toggle_wireframe command");
+ assert_eq!(cmd.label, "Show Wireframe");
+ assert_eq!(cmd.run, Run::Key(crate::shortcut::Action::ToggleWireframe));
+
+ let render_toggle = |state: &State| -> String {
+ state
+ .fs_root
+ .children
+ .iter()
+ .find(|c| c.node_type == "meta")
+ .and_then(|s| s.children.iter().find(|c| c.name == "Render"))
+ .and_then(|n| n.params.iter().find(|p| p.name == "Show Wireframe"))
+ .map(|p| p.default.clone())
+ .expect("a Render node with a Show Wireframe toggle")
+ };
+ let mut state = State::new(false);
+ assert!(!state.wireframe, "wireframe is off unless the project turned it on");
+ assert!(state.run_command("toggle_wireframe"));
+ assert!(state.wireframe);
+ assert_eq!(render_toggle(&state), "true");
+ // The read-back path agrees with the flag instead of reverting it.
+ state.apply_settings_from_menubar_subnets();
+ assert!(state.wireframe);
+ assert!(state.run_command("toggle_wireframe"));
+ assert!(!state.wireframe);
+ assert_eq!(render_toggle(&state), "false");
+ }
+
/// The registry's own invariants. Ids are what `input.kdl` binds and
/// labels are what the palette maps a chosen row back to, so a duplicate
/// of either silently runs the wrong command.
diff --git a/src/shortcut.rs b/src/shortcut.rs
index 0f31e1d..8a62f6a 100644
--- a/src/shortcut.rs
+++ b/src/shortcut.rs
@@ -10,6 +10,7 @@ pub enum Action {
ToggleSpreadsheet,
ToggleOrigin,
ToggleCameraPivot,
+ ToggleWireframe,
ToggleCircularPane,
DetachCircularWindow,
Save,