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

commit9b2a114695e827291f44195d2d96f340d61655b1
parent1df55a40f3
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-28 11:40
feat: the row menu reads out the parameter's raw type string

`Type:` under `Control:` — the type string exactly as the template or
the file wrote it (`slider:-2:2`, `choice:UV,Icosphere,Cube`, `string`
for an absent one), where Control is the kind that string parses to.

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

 CLAUDE.md   |  4 +++-
 src/app.rs  |  6 +++++-
 src/main.rs | 26 +++++++++++++-------------
 3 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 1606cf1..f5860b3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -721,7 +721,9 @@ parameter's name, what a `ch()` path spells, with `Label:` under it only
 when the template gives one (the pane shows the name otherwise, and a
 Label row repeating it would claim there is one); `Control:` is the
 kind the template gave it (`ParamKind::name` — slider, float3,
-attribute…) and `Value:` what its text holds right now
+attribute…), `Type:` the raw type string that kind parsed from
+(`slider:-2:2`, `choice:UV,Icosphere,Cube`, `string` for an absent one)
+exactly as the template or the file wrote it, 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), `Expression:` the row's expression FLAG as `true` / `false`
diff --git a/src/app.rs b/src/app.rs
index cf16271..eecf20b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -4466,7 +4466,10 @@ 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`. Ahead of `Range:`, `Min:` / `Max:` / `Step:`
+    /// `Options: a, b, c`. `Type:` is the raw type STRING under
+    /// `Control:` — `slider:-2:2`, `choice:UV,Icosphere,Cube`, or `string`
+    /// for an absent one — exactly as the template or the file wrote it,
+    /// where Control is the kind that string parses to. Ahead of `Range:`, `Min:` / `Max:` / `Step:`
     /// are what the template DECLARES (`ParamDef::declared_range`, an
     /// inline `slider:-2:2` included), `none` where it declares nothing —
     /// so Range is the clamp the pane applies and the three above it say
@@ -4493,6 +4496,7 @@ impl State {
             options.push(format!("Label: {label}"));
         }
         options.push(format!("Control: {control}"));
+        options.push(format!("Type: {}", param.map(|p| p.ty()).unwrap_or("?")));
         options.push(format!("Value: {value}"));
         options.push(format!("Expression: {is_expr}"));
         if let Some(d) = self.template_default(dir, child, pname) {
diff --git a/src/main.rs b/src/main.rs
index e083ccb..6d71737 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; 9].into_iter().chain([ParamMenuAction::Separator, ParamMenuAction::CopyParameter, ParamMenuAction::Separator, ParamMenuAction::EditExpression]).collect::<Vec<_>>(),
+            vec![ParamMenuAction::Info; 10].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,15 +7767,15 @@ mod tests {
         // row appears.
         let shown = cce_ui::widget::context_menu::options();
         assert_eq!(
-            &shown[..10],
+            &shown[..11],
             &[
-                "Name: Radius".to_string(), "Control: slider".to_string(), "Value: float".to_string(), "Expression: false".to_string(),
-                "Default: 0.5".to_string(), "Min: none".to_string(), "Max: none".to_string(), "Step: none".to_string(),
-                "Range: 0..2".to_string(), "-".to_string(),
+                "Name: Radius".to_string(), "Control: slider".to_string(), "Type: slider".to_string(), "Value: float".to_string(),
+                "Expression: false".to_string(), "Default: 0.5".to_string(), "Min: none".to_string(), "Max: none".to_string(),
+                "Step: none".to_string(), "Range: 0..2".to_string(), "-".to_string(),
             ]
         );
         let (_, _, headers) = state.param_menu_rows(sphere, "Radius");
-        assert_eq!(headers, 9);
+        assert_eq!(headers, 10);
         // 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> {
@@ -7786,7 +7786,7 @@ mod tests {
         // An inline range, a spinbox's range and step, a choice's options,
         // and a conditional row's condition.
         let rows = headers_of(&state, "Center X");
-        for want in ["Min: -2", "Max: 2", "Step: none", "Range: -2..2"] {
+        for want in ["Type: slider:-2:2", "Min: -2", "Max: 2", "Step: none", "Range: -2..2"] {
             assert!(rows.contains(&want.to_string()), "{want} missing from {rows:?}");
         }
         let rows = headers_of(&state, "Rows");
@@ -7794,14 +7794,14 @@ mod tests {
             assert!(rows.contains(&want.to_string()), "{want} missing from {rows:?}");
         }
         let rows = headers_of(&state, "Method");
-        for want in ["Default: UV", "Options: UV, Icosphere, Cube"] {
+        for want in ["Type: choice:UV,Icosphere,Cube", "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", "Expression: false", "Default: true"], "a toggle has neither a range nor options");
+        assert_eq!(headers_of(&state, "Color"), vec!["Name: Color", "Control: toggle", "Type: 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", "Expression: false"]);
+        assert_eq!(headers_of(&state, "Extra"), vec!["Name: Extra", "Control: float", "Type: 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
@@ -7856,11 +7856,11 @@ 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..4], &["Control: slider".to_string(), "Value: expression".to_string(), "Expression: true".to_string()]);
+        assert_eq!(&rows[1..5], &["Control: slider".to_string(), "Type: 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()]);
+        assert_eq!((&rows[1], &rows[3]), (&"Control: choice".to_string(), &"Value: enum".to_string()));
         let (rows, _, _) = state.param_menu_rows(ball, "Rows");
-        assert_eq!(&rows[1..3], &["Control: spinbox".to_string(), "Value: integer".to_string()]);
+        assert_eq!((&rows[1], &rows[3]), (&"Control: spinbox".to_string(), &"Value: integer".to_string()));
 
         // And a reference typed straight into a row (or scripted) becomes one.
         state.apply_action(McpAction::SetParam { slot: ball, name: "Rows".into(), value: "chi(\"../sphere1/Rows\") * 2".into() }, &mut redraw).unwrap();