git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commita5635617561a707efd39bb06b08c397ea3d36199
parent03c551dc3e
authorLucas Galante <[email protected]>
date2026-09-21 14:16
Wireframe: setting a wire colour turns single-colour mode on

The wire pass draws in the wire colour only with the Render node's
"Wire Single Color" on; off, the wires carry the geometry's own colours
and the colour is their alpha alone. Twice in a day a colour set to
black "did not take" because the switch was off. A CHANGE to the wire
colour — through the node, the params pane or the Settings dialog — now
turns single-colour mode on if it was off, on the node as well as live,
so the switch shows moved. A load is not a change: the loaded tree's
value is the baseline (last_applied_wire_color is reset on load), so a
file that says off stays off whatever colour it carries; and switching
it off by hand sticks until the colour changes again.

Tested: an edit through the node flips the switch and the node agrees;
off by hand stays off; a load with a different colour and the switch
off leaves it off.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/app.rs     |  6 +++++-
 src/main.rs    | 42 ++++++++++++++++++++++++++++++++++++++++++
 src/project.rs | 21 +++++++++++++++++++++
 3 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/src/app.rs b/src/app.rs
index 642d890..63e69b0 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1344,6 +1344,9 @@ pub struct State {
     pub wire_width: f32,
     pub last_viewport_wire_single_color: bool,
     pub last_viewport_wire_color: [f32; 4],
+    /// The wire colour as last read off the Render node — so a CHANGE to
+    /// it can be told from a load. `None` until the first read.
+    pub last_applied_wire_color: Option<[f32; 4]>,
     pub last_viewport_wire_width: f32,
     /// Opacity of the rendered node geometry (the Render node's "Opacity"
     /// slider): 1.0 opaque, straight-alpha blended toward the viewport bg.
@@ -2785,7 +2788,7 @@ impl State {
     /// `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) {
+    pub(crate) fn write_render_toggle(&mut self, name: &str, val: bool) {
         if let Some(p) = self
             .fs_root
             .children
@@ -4347,6 +4350,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
             wire_width: 1.0,
             last_viewport_wire_single_color: false,
             last_viewport_wire_color: [1.0, 1.0, 1.0, 1.0],
+            last_applied_wire_color: None,
             last_viewport_wire_width: 1.0,
             geo_opacity: 1.0,
             last_viewport_geo_opacity: 1.0,
diff --git a/src/main.rs b/src/main.rs
index c7a3d27..77a2e82 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4181,6 +4181,48 @@ mod tests {
         let _ = std::fs::remove_dir_all(&dir);
     }
 
+    /// Changing the wire colour turns single-colour mode on — on the node
+    /// and live — so the colour shows; a load does not (a file that says
+    /// off stays off, whatever colour it carries), and turning the switch
+    /// off afterwards sticks until the colour changes again.
+    #[test]
+    fn changing_the_wire_colour_turns_single_colour_mode_on() {
+        let mut state = State::new(false);
+        state.ensure_menubar_subnets();
+        state.apply_settings_from_menubar_subnets();
+        assert!(!state.wire_single_color);
+        let render_param = |state: &State, name: &str| -> String {
+            state.fs_root.children.iter().find(|c| c.node_type == "meta").unwrap()
+                .children.iter().find(|c| c.name == "Render").unwrap()
+                .params.iter().find(|p| p.name == name).unwrap().default.clone()
+        };
+        // An edit through the node, as the params pane and the dialog make it.
+        {
+            let meta = state.fs_root.children.iter_mut().find(|c| c.node_type == "meta").unwrap();
+            let render = meta.children.iter_mut().find(|c| c.name == "Render").unwrap();
+            render.params.iter_mut().find(|p| p.name == "Wire Color").unwrap().default = "#000000ff".to_string();
+        }
+        state.apply_settings_from_menubar_subnets();
+        assert!(state.wire_single_color, "a colour change switches single-colour mode on");
+        assert_eq!(render_param(&state, "Wire Single Color"), "true", "and the node's switch shows it");
+        // Off again by hand stays off while the colour is unchanged.
+        state.write_render_toggle("Wire Single Color", false);
+        state.apply_settings_from_menubar_subnets();
+        assert!(!state.wire_single_color);
+
+        // A load: the file's colour differs from the fresh state's, its
+        // switch is off, and it stays off.
+        let dir = std::env::temp_dir().join(format!("cce-designer-wire-colour-{}", std::process::id()));
+        let _ = std::fs::remove_dir_all(&dir);
+        state.save_to_file(&dir).expect("save");
+        let mut fresh = State::new(false);
+        fresh.ensure_menubar_subnets();
+        fresh.load_from_file(&dir).expect("load");
+        assert_eq!(fresh.wire_color, [0.0, 0.0, 0.0, 1.0]);
+        assert!(!fresh.wire_single_color, "a load never flips the switch");
+        let _ = std::fs::remove_dir_all(&dir);
+    }
+
     /// 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
diff --git a/src/project.rs b/src/project.rs
index 5434b51..34ed974 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -379,6 +379,9 @@ impl State {
             // (so a chord-flipped toggle shows on the node), which on a load
             // stamped the preferences file's values over the file's and lost
             // them before the apply below could read them (2026-09-21).
+            // A load is not a colour change: the loaded tree's value is the
+            // baseline, so the auto-enable of single-colour mode stays quiet.
+            self.last_applied_wire_color = None;
             self.apply_settings_from_menubar_subnets();
             self.ensure_menubar_subnets();
             self.apply_settings_from_menubar_subnets();
@@ -438,6 +441,9 @@ impl State {
         self.fs_root = proj.root;
         // As in the default-project branch: the file's viewport settings
         // land on the live state before ensure re-seeds the nodes from it.
+        // A load is not a colour change: the loaded tree's value is the
+        // baseline, so the auto-enable of single-colour mode stays quiet.
+        self.last_applied_wire_color = None;
         self.apply_settings_from_menubar_subnets();
         self.ensure_menubar_subnets();
         self.apply_settings_from_menubar_subnets();
@@ -1194,6 +1200,7 @@ impl State {
         }
 
         if let Some(params) = session_params(&self.fs_root, "Render") {
+            let before = self.wire_color;
             for p in &params {
                 match p.name.as_str() {
                     "Show Wireframe" => if let Ok(val) = p.default.parse::<bool>() { self.wireframe = val; }
@@ -1207,6 +1214,20 @@ impl State {
                     _ => {}
                 }
             }
+            // Setting a wire colour means wanting to see it: a CHANGE to the
+            // colour (not a load — the first read of a tree sets the
+            // baseline, and a load's value is what it is) turns single-colour
+            // mode on if it was off, on the node as well as live, so the
+            // switch shows moved. Off, the wires carry the geometry's own
+            // colours and the colour row is their alpha alone — which twice
+            // read as "the colour did not take" (2026-09-21).
+            let changed = self.last_applied_wire_color.is_some_and(|last| last != self.wire_color)
+                && before != self.wire_color;
+            self.last_applied_wire_color = Some(self.wire_color);
+            if changed && !self.wire_single_color {
+                self.wire_single_color = true;
+                self.write_render_toggle("Wire Single Color", true);
+            }
         }
     }
 }