graphic design tool
git clone https://git.lucas.co/cce-designer.git
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 ¶ms {
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);
+ }
}
}
}