graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: Main node boolean settings render as toggles
Convert the Main node's boolean choice dropdowns (Show Grid Guide,
Circular Pane, Square Aspect, Show Reference Cube, Show Origin Axes,
Show Camera Pivot, Ray Traced Preview) and the pane-visibility buttons
(Show Network/Viewport/Parameters/Spreadsheet Pane) into toggle params.
Older saves that stored these as choice/button migrate to toggle on
load. Pane toggles route through execute_menu_action when their target
differs from live state; a live-state refresh keeps the switches
accurate after panes/settings change via menus or keyboard.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
src/app.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/project.rs | 53 ++++++++++++++++++++++++++++++++++++++++++-----------
src/window.rs | 2 +-
3 files changed, 97 insertions(+), 12 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 267a0eb..e7762d8 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1357,10 +1357,14 @@ impl State {
if !self.is_detached_network {
if let Some(slot_idx) = self.graph().selected_node() {
let updated_params = self.param().node_params();
+ // Live pane state, so a pane toggle only fires the visibility
+ // action when it actually flips relative to what's on screen.
+ let cur_show = (self.show_network, self.show_viewport, self.show_parameters, self.show_spreadsheet);
let dir = self.current_dir_mut();
if let Some(child) = dir.children.get_mut(slot_idx) {
let mut param_changed = false;
let mut triggered_buttons = Vec::new();
+ let mut pane_actions = Vec::new();
for (u_name, u_val, _) in &updated_params {
if let Some(p) = child.params.iter_mut().find(|p| p.name == *u_name) {
if p.default != *u_val {
@@ -1370,6 +1374,21 @@ impl State {
triggered_buttons.push(p.name.clone());
p.default = "".to_string();
}
+ if p.param_type == "toggle" {
+ let desired = p.default == "true";
+ let cur = match p.name.as_str() {
+ "Show Network Pane" => Some(cur_show.0),
+ "Show Viewport Pane" => Some(cur_show.1),
+ "Show Parameters Pane" => Some(cur_show.2),
+ "Show Spreadsheet Pane" => Some(cur_show.3),
+ _ => None,
+ };
+ // execute_menu_action flips the pane, so only
+ // fire it when the target differs from now.
+ if cur == Some(!desired) {
+ pane_actions.push(p.name.clone());
+ }
+ }
if p.name == "Open" && p.default != "- Select -" && !p.default.is_empty() {
file_to_open = Some(p.default.clone());
p.default = "- Select -".to_string();
@@ -1398,6 +1417,9 @@ impl State {
for btn_name in triggered_buttons {
self.execute_menu_action(&btn_name);
}
+ for pane_name in pane_actions {
+ self.execute_menu_action(&pane_name);
+ }
}
}
@@ -1674,7 +1696,39 @@ impl State {
true
}
+ /// Rewrite the Main node's setting toggles from live app state, so the
+ /// switches show the real value even after panes/settings were changed
+ /// through the menus or keyboard while another node was selected.
+ fn refresh_main_node_live_toggles(&mut self, slot_idx: usize) {
+ let live: [(&str, bool); 11] = [
+ ("Show Network Pane", self.show_network),
+ ("Show Viewport Pane", self.show_viewport),
+ ("Show Parameters Pane", self.show_parameters),
+ ("Show Spreadsheet Pane", self.show_spreadsheet),
+ ("Circular Pane", self.circular_network_pane),
+ ("Square Aspect", self.square_viewport),
+ ("Show Grid Guide", self.viewport().show_grid),
+ ("Show Reference Cube", self.viewport().show_cube),
+ ("Show Origin Axes", self.viewport().show_origin),
+ ("Show Camera Pivot", self.viewport().show_camera_pivot),
+ ("Ray Traced Preview", self.viewport().rt_mode),
+ ];
+ let dir = self.current_dir_mut();
+ let Some(child) = dir.children.get_mut(slot_idx) else { return };
+ if child.name != "Main" { return; }
+ for (name, on) in live {
+ if let Some(p) = child.params.iter_mut().find(|p| p.name == name && p.param_type == "toggle") {
+ p.default = if on { "true" } else { "false" }.to_string();
+ }
+ }
+ }
+
pub fn sync_parameters_pane(&mut self) {
+ if !self.is_detached_network {
+ if let Some(slot_idx) = self.graph().selected_node() {
+ self.refresh_main_node_live_toggles(slot_idx);
+ }
+ }
let params = if !self.is_detached_network {
if let Some(slot_idx) = self.graph().selected_node() {
let dir = self.current_dir();
diff --git a/src/project.rs b/src/project.rs
index 339dd99..f7582a1 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -264,6 +264,11 @@ impl State {
let vp_bg_color = self.viewport().bg_color;
let vp_grid_color = self.viewport().grid_color;
let vp_rt_mode = self.viewport().rt_mode;
+ let show_network = self.show_network;
+ let show_viewport = self.show_viewport;
+ let show_parameters = self.show_parameters;
+ let show_spreadsheet = self.show_spreadsheet;
+ let bool_str = |b: bool| if b { "true" } else { "false" };
let camera_nodes: Vec<String> = self.current_dir().children.iter()
.filter(|c| c.node_type == "camera")
@@ -321,6 +326,12 @@ impl State {
}
}
+ fn set_toggle(p: &mut ParamDef, on: bool) {
+ p.param_type = "toggle".to_string();
+ p.options.clear();
+ p.default = if on { "true" } else { "false" }.to_string();
+ }
+
// Retain only the Main utility subnet, removing the rest
self.fs_root.children.retain(|c| c.name != "Network" && c.name != "Viewport" && c.name != "Parameters" && c.name != "Spreadsheet");
@@ -353,14 +364,14 @@ impl State {
ensure_param(main_node, "Zoom Out", "button", "", &[], None, None, None);
ensure_param(main_node, "Reset Zoom", "button", "", &[], None, None, None);
ensure_param(main_node, "Detach Circular Window", "button", "", &[], None, None, None);
- ensure_param(main_node, "Show Network Pane", "button", "", &[], None, None, None);
- ensure_param(main_node, "Show Viewport Pane", "button", "", &[], None, None, None);
- ensure_param(main_node, "Show Parameters Pane", "button", "", &[], None, None, None);
- ensure_param(main_node, "Show Spreadsheet Pane", "button", "", &[], None, None, None);
+ ensure_param(main_node, "Show Network Pane", "toggle", bool_str(show_network), &[], None, None, None);
+ ensure_param(main_node, "Show Viewport Pane", "toggle", bool_str(show_viewport), &[], None, None, None);
+ ensure_param(main_node, "Show Parameters Pane", "toggle", bool_str(show_parameters), &[], None, None, None);
+ ensure_param(main_node, "Show Spreadsheet Pane", "toggle", bool_str(show_spreadsheet), &[], None, None, None);
// Network Settings
ensure_param(main_node, "Network Settings", "section", "", &[], None, None, None);
- ensure_param(main_node, "Circular Pane", "choice", if self.circular_network_pane { "true" } else { "false" }, &["false", "true"], None, None, None);
+ ensure_param(main_node, "Circular Pane", "toggle", bool_str(self.circular_network_pane), &[], None, None, None);
// Node color is config-owned (style.surface.graph.node.color in
// config.kdl) — drop the retired per-project params from older saves.
main_node.params.retain(|p| !matches!(p.name.as_str(), "Node Color R" | "Node Color G" | "Node Color B"));
@@ -378,12 +389,12 @@ impl State {
ensure_param(main_node, "Active Camera", "choice", &self.active_camera, &camera_options_refs, None, None, None);
}
- ensure_param(main_node, "Square Aspect", "choice", if self.square_viewport { "true" } else { "false" }, &["false", "true"], None, None, None);
- ensure_param(main_node, "Show Grid Guide", "choice", if vp_show_grid { "true" } else { "false" }, &["false", "true"], None, None, None);
- ensure_param(main_node, "Show Reference Cube", "choice", if vp_show_cube { "true" } else { "false" }, &["false", "true"], None, None, None);
- ensure_param(main_node, "Show Origin Axes", "choice", if vp_show_origin { "true" } else { "false" }, &["false", "true"], None, None, None);
- ensure_param(main_node, "Show Camera Pivot", "choice", if vp_show_camera_pivot { "true" } else { "false" }, &["false", "true"], None, None, None);
- ensure_param(main_node, "Ray Traced Preview", "choice", if vp_rt_mode { "true" } else { "false" }, &["false", "true"], None, None, None);
+ ensure_param(main_node, "Square Aspect", "toggle", bool_str(self.square_viewport), &[], None, None, None);
+ ensure_param(main_node, "Show Grid Guide", "toggle", bool_str(vp_show_grid), &[], None, None, None);
+ ensure_param(main_node, "Show Reference Cube", "toggle", bool_str(vp_show_cube), &[], None, None, None);
+ ensure_param(main_node, "Show Origin Axes", "toggle", bool_str(vp_show_origin), &[], None, None, None);
+ ensure_param(main_node, "Show Camera Pivot", "toggle", bool_str(vp_show_camera_pivot), &[], None, None, None);
+ ensure_param(main_node, "Ray Traced Preview", "toggle", bool_str(vp_rt_mode), &[], None, None, None);
ensure_param(main_node, "Grid Thickness", "spinbox", &((self.grid_thickness * 1000.0) as i32).to_string(), &[], Some(2.0), Some(200.0), Some(1.0));
ensure_param(main_node, "Origin Guide Size", "spinbox", &((self.origin_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
ensure_param(main_node, "Camera Pivot Size", "spinbox", &((self.camera_pivot_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
@@ -392,6 +403,26 @@ impl State {
ensure_param(main_node, "Help", "section", "", &[], None, None, None);
ensure_param(main_node, "About", "button", "", &[], None, None, None);
+
+ // Boolean settings and pane-visibility items render as toggles. Older
+ // saves stored these as choice dropdowns / buttons; retype them and
+ // reflect live pane state so a reopened project shows real switches.
+ for p in main_node.params.iter_mut() {
+ match p.name.as_str() {
+ "Circular Pane" | "Square Aspect" | "Show Grid Guide"
+ | "Show Reference Cube" | "Show Origin Axes"
+ | "Show Camera Pivot" | "Ray Traced Preview" => {
+ p.param_type = "toggle".to_string();
+ p.options.clear();
+ if p.default != "true" { p.default = "false".to_string(); }
+ }
+ "Show Network Pane" => set_toggle(p, show_network),
+ "Show Viewport Pane" => set_toggle(p, show_viewport),
+ "Show Parameters Pane" => set_toggle(p, show_parameters),
+ "Show Spreadsheet Pane" => set_toggle(p, show_spreadsheet),
+ _ => {}
+ }
+ }
}
pub(crate) fn apply_settings_from_menubar_subnets(&mut self) {
diff --git a/src/window.rs b/src/window.rs
index 197dd87..8a60326 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -859,7 +859,7 @@ impl State {
// panics its MenuBar downcast, and out-of-range menu/item indices
// used to reply "Menu clicked" while dispatching nowhere. NB the
// pane-toggle items ("Show Spreadsheet Pane", ...) are NOT in these
- // menubars — they are button params in the menu pane, drained by
+ // menubars — they are toggle params in the menu pane, drained by
// sync_parameters_to_project's label match, unreachable from here.
let validated: Result<String, String> = if widget_idx >= WIDGET_COUNT {
Err(format!("widget_idx {widget_idx} out of range (widget slots: 0..{WIDGET_COUNT})"))