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

commit3ca6d29ce66fcc3868bc54fb7dd2780cff3842d1
parenta07114e516
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-28 10:52
feat: the row menu reads out the parameter's expression flag

`Expression: true` / `false` after Value — the bit ParamDef::expr
stores, which Edit Expression sets and Delete Expression clears. Value
already reads `expression` when it is set; the flag row states the bit
itself.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

 CLAUDE.md   |  5 ++++-
 src/app.rs  |  7 ++++++-
 src/main.rs | 14 +++++++-------
 3 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 4e44648..eb0bc58 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -724,7 +724,10 @@ kind the template gave it (`ParamKind::name` — slider, float3,
 attribute…) and `Value:` what its text holds right now
 (`ParamDef::value_type` — float, integer, vec3, boolean, enum, string,
 expression, or invalid with the reason; a programmer's terms, by
-request), `Default:` the template's value as written there
+request), `Expression:` the row's expression FLAG as `true` / `false`
+(the bit Edit Expression sets and Delete Expression clears, stated on
+its own even though Value reads `expression` when it is set), `Default:`
+the template's value as written there
 (`State::template_default`, which takes a subnet template's override for
 a child inside an instance — the Embryo's `sphere1` defaults its Radius to
 `chf("../Radius")` — and is absent for a parameter no template names),
diff --git a/src/app.rs b/src/app.rs
index 53eb5ac..9e3ec28 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -4466,7 +4466,11 @@ impl State {
     /// expression), left out for a parameter no template names. A control
     /// with a range adds `Range: lo..hi`, with its step when one is set
     /// (`ParamDef::range`, the pane's own numbers); a choice adds
-    /// `Options: a, b, c`. Around those, `Name:` heads the list — the
+    /// `Options: a, b, c`. `Expression:` is the row's expression FLAG,
+    /// `true` or `false` — the thing `ParamDef::expr` stores, which is
+    /// what Edit Expression sets and Delete Expression clears; `Value:`
+    /// already reads `expression` when it is set, and the flag row states
+    /// the bit itself. Around those, `Name:` heads the list — the
     /// parameter's name, which is what a `ch()` path and a wire spell —
     /// with `Label:` after it only when the template gives one (the pane
     /// shows the name otherwise, and a Label row repeating it would say
@@ -4486,6 +4490,7 @@ impl State {
         }
         options.push(format!("Control: {control}"));
         options.push(format!("Value: {value}"));
+        options.push(format!("Expression: {is_expr}"));
         if let Some(d) = self.template_default(dir, child, pname) {
             options.push(format!("Default: {}", d.text()));
         }
diff --git a/src/main.rs b/src/main.rs
index 5d6aded..c26143c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7756,7 +7756,7 @@ mod tests {
         assert!(!state.viewport_menu_open());
         assert_eq!(
             state.param_menu_actions,
-            vec![ParamMenuAction::Info, ParamMenuAction::Info, ParamMenuAction::Info, ParamMenuAction::Info, ParamMenuAction::Info, ParamMenuAction::Separator, ParamMenuAction::CopyParameter, ParamMenuAction::Separator, ParamMenuAction::EditExpression],
+            vec![ParamMenuAction::Info; 6].into_iter().chain([ParamMenuAction::Separator, ParamMenuAction::CopyParameter, ParamMenuAction::Separator, ParamMenuAction::EditExpression]).collect::<Vec<_>>(),
             "nothing copied yet, and the row holds a value"
         );
         // The header rows read the parameter out: its name, its control's
@@ -7767,11 +7767,11 @@ mod tests {
         // row appears.
         let shown = cce_ui::widget::context_menu::options();
         assert_eq!(
-            &shown[..6],
-            &["Name: Radius".to_string(), "Control: slider".to_string(), "Value: float".to_string(), "Default: 0.5".to_string(), "Range: 0..2".to_string(), "-".to_string()]
+            &shown[..7],
+            &["Name: Radius".to_string(), "Control: slider".to_string(), "Value: float".to_string(), "Expression: false".to_string(), "Default: 0.5".to_string(), "Range: 0..2".to_string(), "-".to_string()]
         );
         let (_, _, headers) = state.param_menu_rows(sphere, "Radius");
-        assert_eq!(headers, 5);
+        assert_eq!(headers, 6);
         // The headers are the rows before the separator; each one is
         // looked up by its readout, not its position.
         let headers_of = |state: &State, pname: &str| -> Vec<String> {
@@ -7790,11 +7790,11 @@ mod tests {
         for want in ["Default: UV", "Options: UV, Icosphere, Cube"] {
             assert!(rows.contains(&want.to_string()), "{want} missing from {rows:?}");
         }
-        assert_eq!(headers_of(&state, "Color"), vec!["Name: Color", "Control: toggle", "Value: boolean", "Default: true"], "a toggle has neither a range nor options");
+        assert_eq!(headers_of(&state, "Color"), vec!["Name: Color", "Control: toggle", "Value: boolean", "Expression: false", "Default: true"], "a toggle has neither a range nor options");
         // A parameter no template names has no default row; one with a
         // label shows it under the name.
         state.apply_action(McpAction::AddParam { slot: sphere, name: "Extra".into(), param_type: "float".into(), default: "3".into() }, &mut redraw).unwrap();
-        assert_eq!(headers_of(&state, "Extra"), vec!["Name: Extra", "Control: float", "Value: float"]);
+        assert_eq!(headers_of(&state, "Extra"), vec!["Name: Extra", "Control: float", "Value: float", "Expression: false"]);
         state.current_dir_mut().children[sphere].params.iter_mut().find(|p| p.name == "Extra").unwrap().label = "Extra Size".into();
         assert_eq!(headers_of(&state, "Extra")[..2], ["Name: Extra".to_string(), "Label: Extra Size".to_string()]);
         // Inside a subnet instance the SUBNET template's override is the
@@ -7849,7 +7849,7 @@ mod tests {
         // the value is now an expression. Method is a choice holding an
         // enum, Rows a spinbox holding an integer.
         let (rows, _, _) = state.param_menu_rows(ball, "Radius");
-        assert_eq!(&rows[1..3], &["Control: slider".to_string(), "Value: expression".to_string()]);
+        assert_eq!(&rows[1..4], &["Control: slider".to_string(), "Value: expression".to_string(), "Expression: true".to_string()]);
         let (rows, _, _) = state.param_menu_rows(ball, "Method");
         assert_eq!(&rows[1..3], &["Control: choice".to_string(), "Value: enum".to_string()]);
         let (rows, _, _) = state.param_menu_rows(ball, "Rows");