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

commit961c788b74d07e7a284dd55432b3d26dca0b5d21
parent844cf6e32a
authorLucas Galante <[email protected]>
date2026-07-21 15:22
feat: Render utility node — render/display controls (Show Wireframe)

A second default-present utility subnet beside Main (ensured like Main
in ensure_menubar_subnets, applied in apply_settings_from_menubar_
subnets): its Show Wireframe toggle drives State::wireframe, staged
onto the node-geometry draw (SceneDraw::wireframe — grid/origin/cube
chrome stays filled) and keyed into the viewport restage cache.

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

 src/app.rs      | 22 +++++++++++++++-------
 src/project.rs  | 32 ++++++++++++++++++++++++++++++++
 src/vk_smoke.rs |  6 +++---
 3 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index ac50c49..85ce924 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -948,6 +948,10 @@ pub struct State {
     pub last_viewport_height: u32,
     pub last_viewport_active_camera: String,
     pub last_viewport_show_viewport: bool,
+    /// Wireframe display of the node geometry (the Render utility node's
+    /// "Show Wireframe" toggle).
+    pub wireframe: bool,
+    pub last_viewport_wireframe: bool,
     pub last_viewport_rt_mode: bool,
     /// Sphere-geometry cache for the path tracer (a copy of the last
     /// `rebuild_scene_geometry` output, so entering RT mode never re-runs
@@ -2591,6 +2595,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             last_viewport_height: 0,
             last_viewport_active_camera: String::new(),
             last_viewport_show_viewport: false,
+            wireframe: false,
+            last_viewport_wireframe: false,
             last_viewport_rt_mode: false,
             rt_sphere_verts: Vec::new(),
             rt_geometry_version: 0,
@@ -4973,7 +4979,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                     || self.last_viewport_width != cw
                     || self.last_viewport_height != ch
                     || self.last_viewport_active_camera != self.active_camera
-                    || self.last_viewport_show_viewport != self.show_viewport;
+                    || self.last_viewport_show_viewport != self.show_viewport
+                    || self.last_viewport_wireframe != self.wireframe;
 
                 if viewport_changed {
                     if !rt_mode {
@@ -4994,21 +5001,21 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
 
                     // Same draw order as the wgpu pass: bg quad, grid, origin,
                     // pivot, cube, spheres.
-                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp }];
+                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false }];
                     if self.viewport().show_grid {
-                        draws.push(SceneDraw { mesh: meshes.grid, mvp });
+                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false });
                     }
                     if self.viewport().show_origin {
-                        draws.push(SceneDraw { mesh: meshes.origin, mvp });
+                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false });
                     }
                     if self.viewport().show_camera_pivot {
-                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot });
+                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false });
                     }
                     if self.viewport().show_cube {
-                        draws.push(SceneDraw { mesh: meshes.cube, mvp });
+                        draws.push(SceneDraw { mesh: meshes.cube, mvp, wireframe: false });
                     }
                     if self.vertex_count_spheres > 0 {
-                        draws.push(SceneDraw { mesh: meshes.spheres, mvp });
+                        draws.push(SceneDraw { mesh: meshes.spheres, mvp, wireframe: self.wireframe });
                     }
                     renderer.stage_scene((sx, sy, cw, ch), draws);
                     }
@@ -5031,6 +5038,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                     self.last_viewport_height = ch;
                     self.last_viewport_active_camera = self.active_camera.clone();
                     self.last_viewport_show_viewport = self.show_viewport;
+                    self.last_viewport_wireframe = self.wireframe;
                     self.last_viewport_rt_mode = rt_mode;
                     self.viewport_dirty = false;
                 }
diff --git a/src/project.rs b/src/project.rs
index 76e499f..aabb396 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -269,6 +269,7 @@ impl State {
         let show_parameters = self.show_parameters;
         let show_spreadsheet = self.show_spreadsheet;
         let show_playbar = self.show_playbar;
+        let wireframe = self.wireframe;
         let bool_str = |b: bool| if b { "true" } else { "false" };
 
         let camera_nodes: Vec<String> = self.current_dir().children.iter()
@@ -441,6 +442,27 @@ impl State {
                 }
             }
         }
+
+        // 2. Render subnet — render/display controls, present by default like
+        // Main. Toggles reflect live state so a reopened project shows real
+        // switches.
+        let render_node = find_or_create_subnet(&mut self.fs_root, "Render", "utility", (0.0, 1.0));
+        render_node.children.clear();
+
+        ensure_param(render_node, "Render Settings", "section", "", &[], None, None, None);
+        ensure_param(render_node, "Show Wireframe", "toggle", bool_str(wireframe), &[], None, None, None);
+
+        for p in render_node.params.iter_mut() {
+            match p.name.as_str() {
+                "Show Wireframe" => set_toggle(p, wireframe),
+                _ => {}
+            }
+            if p.param_type == "toggle" {
+                if let Some(rest) = p.name.strip_prefix("Show ") {
+                    p.label = rest.to_string();
+                }
+            }
+        }
     }
 
     pub(crate) fn apply_settings_from_menubar_subnets(&mut self) {
@@ -476,5 +498,15 @@ impl State {
                 }
             }
         }
+
+        if let Some(render_idx) = self.fs_root.children.iter().position(|c| c.name == "Render") {
+            let params = self.fs_root.children[render_idx].params.clone();
+            for p in &params {
+                match p.name.as_str() {
+                    "Show Wireframe" => if let Ok(val) = p.default.parse::<bool>() { self.wireframe = val; }
+                    _ => {}
+                }
+            }
+        }
     }
 }
diff --git a/src/vk_smoke.rs b/src/vk_smoke.rs
index bd463bd..a10c7cc 100644
--- a/src/vk_smoke.rs
+++ b/src/vk_smoke.rs
@@ -474,9 +474,9 @@ fn main() {
                 renderer.stage_scene(
                     pane,
                     vec![
-                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d() },
-                        SceneDraw { mesh: grid_mesh, mvp },
-                        SceneDraw { mesh: cube_mesh, mvp },
+                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d(), wireframe: false },
+                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false },
+                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false },
                     ],
                 );
             }