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

commit60bd334bba34a3734a803dbca5179d74af41a785
parente67fcbc291
authorLucas Galante <[email protected]>
date2026-06-09 17:52
Integrate context switcher dropdown with the Paginator sidebar

 src/main.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 120 insertions(+), 21 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 9894144..63eb850 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1337,7 +1337,7 @@ impl State {
     
     fn in_network_pane(&self) -> bool {
         if self.circular_network_pane {
-            self.circular_network_layout.hit_test_content(self.cursor_x, self.cursor_y, MENUBAR_H, BREADCRUMB_H)
+            self.circular_network_layout.hit_test_content(self.cursor_x, self.cursor_y, 0.0, BREADCRUMB_H)
         } else {
             let (cx, cy, cw, ch) = self.positions[CONTENT_IDX];
             self.cursor_x >= cx
@@ -2685,8 +2685,15 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         } else {
             vec![]
         };
+        let context_opts = vec![
+            "0: Network".to_string(),
+            "1: Viewport".to_string(),
+            "2: Parameters".to_string(),
+            "3: Spreadsheet".to_string(),
+            "Main Menu".to_string(),
+        ];
         let mut widgets: Vec<Box<dyn Element>> = vec![
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_label("Main Menu Bar").with_item("File", &["New Project", "Open", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane"]).with_item("Help", &["About"]).with_z_index(110)),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_label("Main Menu Bar").with_item("File", &["New Project", "Open", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane"]).with_item("Help", &["About"]).with_z_index(110).with_context_options(context_opts.clone(), 4)),
             Box::new(Graph::new()),
             Box::new(Splitter::new(SPLITTER_W)),
             Box::new(ViewportBg::new()),
@@ -2694,23 +2701,27 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             Box::new(Plate::new(0.0, 0.0, 0.0, 0.0).with_color(colors::PARAM_BG).with_blur(true)),
             Box::new(ParametersBg::new()),
             Box::new(Canvas::new()),
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Open", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_item("Settings", &[])),
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_label("Viewport Menu Bar").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["Square Aspect"]).with_item("Guides", &["Show Grid", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_item("Settings", &[])),
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default", "Custom"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"])),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Open", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_item("Settings", &[]).with_context_options(context_opts.clone(), 0)),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_label("Viewport Menu Bar").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["Square Aspect"]).with_item("Guides", &["Show Grid", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_item("Settings", &[]).with_context_options(context_opts.clone(), 1)),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default", "Custom"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 2)),
             Box::new(StatusBar::new().with_text("Ready")),
             Box::new(Breadcrumb::new()),
             Box::new(NodePalette::new()),
             Box::new(Spreadsheet::new()),
         ];
         
-        let mut spreadsheet_menubar = MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("3: Spreadsheet").with_label("Spreadsheet Menu Bar").with_item("View", &["Close Pane"]);
+        let mut spreadsheet_menubar = MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("3: Spreadsheet").with_label("Spreadsheet Menu Bar").with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 3);
         spreadsheet_menubar.set_visible(false);
         widgets.push(Box::new(spreadsheet_menubar));
 
         let network_panel = Plate::new(0.0, 0.0, 0.0, 0.0).with_color([0.10, 0.10, 0.13, 0.95]);
         widgets.push(Box::new(network_panel));
 
-        let paginator = Paginator::new(56.0, vec![]).with_sidebar_mode(true).with_column_layout(true).with_tabs_rotated(false);
+        let paginator = Paginator::new(56.0, vec![])
+            .with_sidebar_mode(true)
+            .with_column_layout(true)
+            .with_tabs_rotated(false)
+            .with_context_options(context_opts.clone(), 0);
         widgets.push(Box::new(paginator));
 
         let mut positions = Vec::with_capacity(PAGINATOR_IDX + 1);
@@ -3117,12 +3128,12 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.circular_network_layout.r = r;
 
             self.positions[0] = (0.0, 0.0, 0.0, 0.0);
-            self.positions[LEFT_MENUBAR_IDX] = (cx - r, cy - r, 2.0 * r, 35.0);
+            self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
             if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<clear_ui::widget::MenuBar>() {
-                menubar.set_curved_circle(Some((cx, cy, r)));
+                menubar.set_curved_circle(None);
             }
-            self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0 + MENUBAR_H, 2.0 * r, BREADCRUMB_H);
-            self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + MENUBAR_H + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + MENUBAR_H + BREADCRUMB_H));
+            self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0, 2.0 * r, BREADCRUMB_H);
+            self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + BREADCRUMB_H));
             self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
             self.widgets[NETWORK_PANEL_IDX].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
             if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Plate>() {
@@ -3139,6 +3150,21 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.positions[PARAM_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[STATUS_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[NODE_PALETTE_IDX] = (0.0, 0.0, self.width, self.height);
+
+            self.widgets[0].set_visible(false);
+            self.widgets[STATUS_IDX].set_visible(false);
+            self.widgets[CONTENT_IDX].set_visible(true);
+            self.widgets[NETWORK_PANEL_IDX].set_visible(true);
+            self.widgets[LEFT_MENUBAR_IDX].set_visible(false);
+            self.widgets[BREADCRUMB_IDX].set_visible(true);
+            self.widgets[SPLITTER1_IDX].set_visible(false);
+            self.widgets[SPLITTER2_IDX].set_visible(false);
+            self.widgets[VIEWPORT_IDX].set_visible(false);
+            self.widgets[RIGHT_MENUBAR_IDX].set_visible(false);
+            self.widgets[PARAM_IDX].set_visible(false);
+            self.widgets[PARAM_MENUBAR_IDX].set_visible(false);
+            self.widgets[SPREADSHEET_IDX].set_visible(false);
+            self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(false);
         } else if self.detached_circular_network {
             // Parent process: Network pane is detached (hidden from main window)
             let left_visible = false;
@@ -3733,6 +3759,20 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.update_recent_files_layout();
     }
 
+    fn sync_context_dropdowns(&mut self) {
+        let selected_idx = match self.focused_pane {
+            LEFT_MENUBAR_IDX => 0,
+            RIGHT_MENUBAR_IDX => 1,
+            PARAM_MENUBAR_IDX => 2,
+            SPREADSHEET_MENUBAR_IDX => 3,
+            HEADER_IDX => 4,
+            _ => return,
+        };
+        for &widget_idx in &[HEADER_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX, PAGINATOR_IDX] {
+            self.widgets[widget_idx].set_context_selected(selected_idx);
+        }
+    }
+
     fn sync_pane_focus(&mut self) {
         for &menubar_idx in &[HEADER_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX] {
             self.widgets[menubar_idx].set_selected(menubar_idx == self.focused_pane);
@@ -3741,6 +3781,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.widgets[PARAM_IDX].unfocus();
             self.sync_parameters_to_project();
         }
+        self.sync_context_dropdowns();
         self.update_paginator();
     }
 
@@ -4077,7 +4118,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             if i == CONTENT_IDX && show_cursor {
                 let _cx = active_clip_circle[0] / self.scale as f32 - self.circular_network_layout.r + self.pan_x; // Wait, let's keep the exact cursor coordinates!
                 let active_node_area_y = if self.circular_network_pane {
-                    self.circular_network_layout.y - self.circular_network_layout.r + 45.0 + MENUBAR_H + BREADCRUMB_H
+                    self.circular_network_layout.y - self.circular_network_layout.r + 45.0 + BREADCRUMB_H
                 } else {
                     node_area_y
                 };
@@ -4962,11 +5003,11 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX;
                             let inside = if self.circular_network_pane && is_network_part {
                                 if i == CONTENT_IDX {
-                                    self.circular_network_layout.hit_test_content(cx, cy, MENUBAR_H, BREADCRUMB_H)
+                                    self.circular_network_layout.hit_test_content(cx, cy, 0.0, BREADCRUMB_H)
                                 } else if i == LEFT_MENUBAR_IDX {
-                                    self.circular_network_layout.hit_test_menubar(cx, cy, MENUBAR_H)
+                                    false
                                 } else if i == BREADCRUMB_IDX {
-                                    self.circular_network_layout.hit_test_breadcrumb(cx, cy, MENUBAR_H, BREADCRUMB_H)
+                                    self.circular_network_layout.hit_test_breadcrumb(cx, cy, 0.0, BREADCRUMB_H)
                                 } else if i == NETWORK_PANEL_IDX {
                                     self.widgets[NETWORK_PANEL_IDX].hit_test(cx, cy, &self.ui_context)
                                 } else {
@@ -5056,11 +5097,11 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 let hits_widget = |state: &State, i: usize, x: f32, y: f32| -> bool {
                     if state.circular_network_pane && (i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX) {
                         if i == CONTENT_IDX {
-                            state.circular_network_layout.hit_test_content(x, y, MENUBAR_H, BREADCRUMB_H)
+                            state.circular_network_layout.hit_test_content(x, y, 0.0, BREADCRUMB_H)
                         } else if i == LEFT_MENUBAR_IDX {
-                            state.circular_network_layout.hit_test_menubar(x, y, MENUBAR_H)
+                            false
                         } else if i == BREADCRUMB_IDX {
-                            state.circular_network_layout.hit_test_breadcrumb(x, y, MENUBAR_H, BREADCRUMB_H)
+                            state.circular_network_layout.hit_test_breadcrumb(x, y, 0.0, BREADCRUMB_H)
                         } else {
                             false
                         }
@@ -5070,7 +5111,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 };
 
                 let in_circle_network_pane = if self.circular_network_pane {
-                    self.circular_network_layout.hit_test_content(self.cursor_x, self.cursor_y, MENUBAR_H, BREADCRUMB_H)
+                    self.circular_network_layout.hit_test_content(self.cursor_x, self.cursor_y, 0.0, BREADCRUMB_H)
                 } else {
                     in_network_pane
                 };
@@ -5099,7 +5140,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
 
                         if *button == MouseButton::Left && self.circular_network_pane {
                             let on_border = self.circular_network_layout.hit_test_border(self.cursor_x, self.cursor_y, 12.0);
-                            let hit_menubar = self.circular_network_layout.hit_test_menubar(self.cursor_x, self.cursor_y, MENUBAR_H);
+                            let hit_menubar = false;
                             if hit_menubar {
                                 if self.widgets[LEFT_MENUBAR_IDX].get_menu_items_at(self.cursor_x, self.cursor_y).is_some() {
                                     self.focused_pane = LEFT_MENUBAR_IDX;
@@ -5107,7 +5148,6 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                     if self.widgets[LEFT_MENUBAR_IDX].mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y, &mut self.ui_context) {
                                         changed = true;
                                     }
-                                    return true;
                                 }
                             }
                             if on_border || hit_menubar {
@@ -7010,6 +7050,65 @@ impl AppState {
                 changed = true;
             }
 
+            // Check context switcher dropdown changes
+            for &widget_idx in &[HEADER_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX, PAGINATOR_IDX] {
+                if let Some(new_sel) = state.widgets[widget_idx].take_context_change() {
+                    let target_pane = match new_sel {
+                        0 => LEFT_MENUBAR_IDX,
+                        1 => RIGHT_MENUBAR_IDX,
+                        2 => PARAM_MENUBAR_IDX,
+                        3 => SPREADSHEET_MENUBAR_IDX,
+                        4 => HEADER_IDX,
+                        _ => continue,
+                    };
+                    if state.focused_pane != target_pane {
+                        state.focused_pane = target_pane;
+                        // Make sure the switched pane is visible!
+                        match target_pane {
+                            LEFT_MENUBAR_IDX => {
+                                if !state.show_network {
+                                    state.show_network = true;
+                                    state.widgets[CONTENT_IDX].set_visible(true);
+                                    state.widgets[LEFT_MENUBAR_IDX].set_visible(true);
+                                    state.widgets[BREADCRUMB_IDX].set_visible(true);
+                                    state.widgets[HEADER_IDX].set_item_checked(2, 4, true);
+                                }
+                            }
+                            RIGHT_MENUBAR_IDX => {
+                                if !state.show_viewport {
+                                    state.show_viewport = true;
+                                    state.widgets[VIEWPORT_IDX].set_visible(true);
+                                    state.widgets[RIGHT_MENUBAR_IDX].set_visible(true);
+                                    state.widgets[HEADER_IDX].set_item_checked(2, 5, true);
+                                }
+                            }
+                            PARAM_MENUBAR_IDX => {
+                                if !state.show_parameters {
+                                    state.show_parameters = true;
+                                    state.widgets[PARAM_IDX].set_visible(true);
+                                    state.widgets[PARAM_MENUBAR_IDX].set_visible(true);
+                                    state.widgets[HEADER_IDX].set_item_checked(2, 6, true);
+                                }
+                            }
+                            SPREADSHEET_MENUBAR_IDX => {
+                                if !state.show_spreadsheet {
+                                    state.show_spreadsheet = true;
+                                    state.widgets[SPREADSHEET_IDX].set_visible(true);
+                                    state.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(true);
+                                    state.widgets[HEADER_IDX].set_item_checked(2, 7, true);
+                                }
+                            }
+                            _ => {}
+                        }
+                        state.rebuild_positions();
+                        state.apply_layout();
+                        state.sync_pane_focus();
+                        state.sync_nodes();
+                        changed = true;
+                    }
+                }
+            }
+
             if let Some((menu_idx, item_idx)) = state.widgets[HEADER_IDX].menu_click() {
                 if menu_idx == 0 { // File
                     match item_idx {