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

commit253f3e673a2be74c74296e9aa2c8c39bf99484e2
parentc7878d7027
authorLucas Galante <[email protected]>
date2026-09-01 10:58
feat: the parameters pane follows selection in either editor

A param_editor field names whichever network editor took the last
node click (selection stays per-editor); the four selection-driven
reads — pane fill, param writeback, pick-list upgrades, the Main
node's live toggles — go through param_editor_selected/_dir accessors,
so the pane shows and edits the node at THAT editor's level, no matter
the tab. Clicking back in the primary hands the pane back.

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

 src/app.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 52 insertions(+), 8 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index c6d7200..165de97 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -892,6 +892,10 @@ pub struct State {
     /// two graph views dive independently. Always valid against `fs_root`
     /// (every structural edit re-clamps it); starts at root.
     pub current_path2: Vec<usize>,
+    /// The editor whose SELECTION feeds the parameters pane (and the param
+    /// writeback): CONTENT_IDX or CONTENT2_IDX — whichever took the last
+    /// node click. Selection itself stays per-editor.
+    pub param_editor: usize,
     pub node_clipboard: Option<FsNode>,
     pub last_click: Option<(Instant, usize)>,
     pub last_frame: Instant,
@@ -1783,6 +1787,35 @@ impl State {
         node
     }
 
+    /// The selected slot in the editor the parameters pane follows.
+    pub fn param_editor_selected(&self) -> Option<usize> {
+        if self.param_editor == crate::slots::CONTENT2_IDX {
+            use cce_ui::widget::GraphController as _;
+            self.slots.content2.selected_node()
+        } else {
+            self.graph().selected_node()
+        }
+    }
+
+    /// The level that editor is showing — where its selection resolves.
+    pub fn param_editor_dir(&self) -> &FsNode {
+        if self.param_editor == crate::slots::CONTENT2_IDX {
+            self.dir_at(&self.current_path2)
+        } else {
+            self.current_dir()
+        }
+    }
+
+    /// [`Self::param_editor_dir`], mutable — the param writeback target.
+    pub fn param_editor_dir_mut(&mut self) -> &mut FsNode {
+        if self.param_editor == crate::slots::CONTENT2_IDX {
+            let p2 = self.current_path2.clone();
+            self.dir_at_mut(&p2)
+        } else {
+            self.current_dir_mut()
+        }
+    }
+
     /// Truncate the second editor's path to its valid prefix — run after any
     /// structural edit, so `dir_at` clamping and the drawn breadcrumb agree.
     pub fn clamp_path2(&mut self) {
@@ -1815,12 +1848,12 @@ impl State {
     pub fn sync_parameters_to_project(&mut self) {
         let mut file_to_open = None;
         if !self.is_detached_network {
-            if let Some(slot_idx) = self.graph().selected_node() {
+            if let Some(slot_idx) = self.param_editor_selected() {
                 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, self.show_playbar);
-                let dir = self.current_dir_mut();
+                let dir = self.param_editor_dir_mut();
                 if let Some(child) = dir.children.get_mut(slot_idx) {
                     let mut param_changed = false;
                     let mut triggered_buttons = Vec::new();
@@ -2228,7 +2261,7 @@ impl State {
             ("Show Reference Cube", self.viewport().show_cube),
             ("Show Origin Axes", self.viewport().show_origin),
         ];
-        let dir = self.current_dir_mut();
+        let dir = self.param_editor_dir_mut();
         let Some(child) = dir.children.get_mut(slot_idx) else { return };
         let live: &[(&str, bool)] = match child.name.as_str() {
             "Main" => &live_main,
@@ -2244,14 +2277,17 @@ impl State {
     }
 
     pub fn sync_parameters_pane(&mut self) {
+        // Selection reads through the param-editor accessors: whichever
+        // network editor took the last node click feeds the pane, at ITS
+        // level — no matter the tab.
         if !self.is_detached_network {
-            if let Some(slot_idx) = self.graph().selected_node() {
+            if let Some(slot_idx) = self.param_editor_selected() {
                 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();
+            if let Some(slot_idx) = self.param_editor_selected() {
+                let dir = self.param_editor_dir();
                 if slot_idx < dir.children.len() {
                     param_display(&dir.children[slot_idx].params)
                 } else {
@@ -2280,8 +2316,8 @@ impl State {
             if self.is_detached_network {
                 return params;
             }
-            let Some(slot) = self.graph().selected_node() else { return params };
-            let dir = self.current_dir();
+            let Some(slot) = self.param_editor_selected() else { return params };
+            let dir = self.param_editor_dir();
             let Some(node) = dir.children.get(slot) else { return params };
             let nt = node.node_type.to_lowercase();
             if nt != "attribute" && nt != "group" && nt != "relax" {
@@ -3410,6 +3446,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             node_templates,
             current_path,
             current_path2: Vec::new(),
+            param_editor: CONTENT_IDX,
             node_clipboard: None,
             last_click: None,
             last_frame: Instant::now(),
@@ -5475,7 +5512,14 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                                     self.focused_widget = None;
                                 }
                             }
+                            if i == crate::slots::CONTENT2_IDX {
+                                // A node click in the SECOND editor hands the
+                                // parameters pane to its selection.
+                                self.param_editor = crate::slots::CONTENT2_IDX;
+                                self.sync_parameters_pane();
+                            }
                             if i == CONTENT_IDX {
+                                self.param_editor = CONTENT_IDX;
                                 self.sync_parameters_pane();
                                 if let Some(slot_idx) = self.graph().selected_node() {
                                     let dir = self.current_dir();