graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: exclusive per-directory geometry visibility (display-flag semantics)
Enabling a node's geometry visibility now clears every sibling's in the
same directory, so at most one node per path displays its geometry —
Houdini display-flag semantics rather than an independent per-node flag.
Disabling stays local to the node.
All four toggle routes funnel through the new
FsNode::set_child_geometry_visible: the network pane's 'e' key, both
graph editors' click toggles (which now sync_nodes so cleared siblings
repaint in both views), and MCP/context-menu ToggleGeometry.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/app.rs | 30 +++++++++++++++++++++++++++---
src/main.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++
src/window.rs | 2 +-
3 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 9263051..89392c5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -148,6 +148,25 @@ impl FsNode {
matches!(self.node_type.as_str(), "node" | "utility" | "simnet" | "session")
|| !self.children.is_empty()
}
+
+ /// Set one child's geometry visibility. Enabling is EXCLUSIVE within the
+ /// directory — at most one node per path shows its geometry, so turning a
+ /// node on turns every sibling off (a display flag, not a per-node render
+ /// flag). Disabling touches only the named child. Every toggle route
+ /// (keyboard `e`, the graph widgets' click toggles, MCP/context-menu
+ /// ToggleGeometry) must go through here or the invariant silently rots.
+ pub fn set_child_geometry_visible(&mut self, slot: usize, visible: bool) {
+ if slot >= self.children.len() {
+ return;
+ }
+ if visible {
+ for (i, child) in self.children.iter_mut().enumerate() {
+ child.geometry_visible = i == slot;
+ }
+ } else {
+ self.children[slot].geometry_visible = false;
+ }
+ }
}
/// Fresh ids for a node and its whole subtree — required whenever an
@@ -5881,7 +5900,11 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
if let Some((i, visible)) = self.graph_mut().take_node_geom_toggle() {
- self.current_dir_mut().children[i].geometry_visible = visible;
+ self.current_dir_mut().set_child_geometry_visible(i, visible);
+ // The widget only flipped its own copy of the clicked node;
+ // the exclusivity rule may have cleared siblings (in both
+ // editors' views), so push the model back out.
+ self.sync_nodes();
self.rebuild_scene_geometry();
changed = true;
}
@@ -5946,7 +5969,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
let p2 = self.current_path2.clone();
let dir = self.dir_at_mut(&p2);
if i < dir.children.len() {
- dir.children[i].geometry_visible = visible;
+ dir.set_child_geometry_visible(i, visible);
+ self.sync_nodes();
self.rebuild_scene_geometry();
changed = true;
}
@@ -6174,7 +6198,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
let dir = self.current_dir();
if slot_idx < dir.children.len() && dir.children[slot_idx].node_type != "utility" {
let visible = !dir.children[slot_idx].geometry_visible;
- self.current_dir_mut().children[slot_idx].geometry_visible = visible;
+ self.current_dir_mut().set_child_geometry_visible(slot_idx, visible);
self.sync_nodes();
self.rebuild_scene_geometry();
changed = true;
diff --git a/src/main.rs b/src/main.rs
index f1225a3..fe7ba7a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -136,6 +136,52 @@ mod tests {
assert!(state.has_unsaved_changes(), "the toggle must dirty the title");
}
+ /// Geometry visibility is exclusive per directory: enabling one node's
+ /// display turns every sibling's off (a display flag, not a per-node
+ /// render flag), while disabling touches only that node. All toggle
+ /// routes (keyboard `e`, the graph click toggles, MCP ToggleGeometry)
+ /// funnel through `set_child_geometry_visible`.
+ #[test]
+ fn test_geometry_visibility_is_exclusive_per_directory() {
+ let child = |name: &str| FsNode {
+ id: name.to_string(),
+ name: name.to_string(),
+ node_type: "opencl".to_string(),
+ children: vec![],
+ params: vec![],
+ geometry_visible: true,
+ position: (0.0, 0.0),
+ inputs: 1,
+ outputs: 1,
+ };
+ let mut dir = FsNode {
+ id: "root".to_string(),
+ name: "root".to_string(),
+ node_type: "node".to_string(),
+ children: vec![child("a"), child("b"), child("c")],
+ params: vec![],
+ geometry_visible: true,
+ position: (0.0, 0.0),
+ inputs: 0,
+ outputs: 0,
+ };
+
+ // Enabling slot 1 clears its siblings, even ones already visible.
+ dir.set_child_geometry_visible(1, true);
+ let vis: Vec<bool> = dir.children.iter().map(|c| c.geometry_visible).collect();
+ assert_eq!(vis, [false, true, false]);
+
+ // Disabling is not exclusive — only the named node changes.
+ dir.set_child_geometry_visible(1, false);
+ let vis: Vec<bool> = dir.children.iter().map(|c| c.geometry_visible).collect();
+ assert_eq!(vis, [false, false, false]);
+
+ // Out-of-bounds is a no-op, never a panic or a sibling sweep.
+ dir.children[2].geometry_visible = true;
+ dir.set_child_geometry_visible(9, true);
+ assert!(dir.children[2].geometry_visible);
+ }
+
/// The button must exist on Main, inside the File section, before Exit.
#[test]
fn test_main_node_offers_set_as_default() {
diff --git a/src/window.rs b/src/window.rs
index 990d48d..1ada59a 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -777,7 +777,7 @@ impl State {
Err("Cannot toggle geometry visibility on utility nodes".to_string())
} else {
let visible = !state.current_dir().children[slot].geometry_visible;
- state.current_dir_mut().children[slot].geometry_visible = visible;
+ state.current_dir_mut().set_child_geometry_visible(slot, visible);
state.sync_nodes();
state.rebuild_scene_geometry();
needs_redraw = true;