git.lucas.co / cce-gallery
widget gallery and compositor test bench

commit95c28c9c8065808c3209d9594029fe72bcc0fcbb
parentdef73af6a4
authorLucas Galante <[email protected]>
date2026-07-05 02:20
Add Layout selection dropdown to Controls page to switch layouts dynamically

 src/main.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 187 insertions(+), 12 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index fe4a145..2b3eec8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -56,6 +56,7 @@ struct State {
 
     sender: calloop::channel::Sender<String>,
     text_items: Vec<TextItem>,
+    layout_idx: usize,
 }
 
 fn save_bevel_ramp(keys: &[RampKey], line_type: &str) {
@@ -230,7 +231,7 @@ impl State {
         }
         match index {
             0..=2 | 46 => true,
-            3..=13 | 30 | 31 | 36 | 37 | 47 | 49 | 50 => self.current_page == Page::Controls,
+            3..=13 | 30 | 31 | 36 | 37 | 47 | 49 | 50 | 52 => self.current_page == Page::Controls,
             14..=29 | 38..=45 | 48 | 51 => self.current_page == Page::Windows,
             32..=35 => self.current_page == Page::Xdg,
             _ => false,
@@ -281,6 +282,13 @@ impl State {
             let dx = sb_rect.0 + sb_rect.2 - dw - pad_x;
             let dy = sb_rect.1 + pad_y;
             self.widgets[46].set_rect(dx, dy, dw, ddh);
+
+            if self.is_widget_visible(52) {
+                let l_dx = dx - dw - 10.0;
+                self.widgets[52].set_rect(l_dx, dy, dw, ddh);
+            } else {
+                self.widgets[52].set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            }
         }
     }
 
@@ -652,6 +660,19 @@ cascades in cce."
                 Box::new(Ramp::new()), // 49
                 Box::new(Button::new(0.0, 0.0, 120.0, 28.0).with_label("Ramp...")), // 50
                 Box::new(ControlPanel::new().with_label("ControlPanel")), // 51
+                Box::new(Dropdown::new(vec![
+                    "Vertical".to_string(),
+                    "Columns".to_string(),
+                    "Grid".to_string(),
+                    "Adaptive Grid".to_string(),
+                    "Overlay".to_string(),
+                    "Flex".to_string(),
+                    "Splitter".to_string(),
+                    "Radial".to_string(),
+                    "Circular Pane".to_string(),
+                    "Mosaic".to_string(),
+                    "Reverse Mosaic".to_string(),
+                ], 9).with_label("Layout")), // 52
             ]
         };
 
@@ -659,7 +680,7 @@ cascades in cce."
             child_positions(c_w, c_h, use_backplate, use_menubar, use_statusbar, child_type.as_deref())
         } else {
             let sidebar_w = widgets[1].as_page_selector().unwrap().sidebar_w();
-            demo_positions(c_w, c_h, sidebar_w)
+            demo_positions(c_w, c_h, sidebar_w, 9)
         };
 
         let (loaded_keys, loaded_type) = load_bevel_ramp();
@@ -697,6 +718,7 @@ cascades in cce."
             child_shape: child_shape.clone(),
             sender,
             text_items: Vec::new(),
+            layout_idx: 9,
         };
 
         if !is_child {
@@ -711,6 +733,11 @@ cascades in cce."
             state.widgets[46].set_parent(Some(statusbar_ptr), &mut state.ui_context);
             let dropdown_ptr = state.widgets[46].as_ptr_mut();
             state.widgets[2].add_child(dropdown_ptr, &mut state.ui_context);
+
+            // Set layout selector (52) parent to StatusBar (2)
+            state.widgets[52].set_parent(Some(statusbar_ptr), &mut state.ui_context);
+            let layout_dropdown_ptr = state.widgets[52].as_ptr_mut();
+            state.widgets[2].add_child(layout_dropdown_ptr, &mut state.ui_context);
         }
 
         state.apply_layout();
@@ -852,7 +879,7 @@ cascades in cce."
                 child_positions(self.width, self.height, self.use_backplate, self.use_menubar, self.use_statusbar, self.child_type.as_deref())
             } else {
                 let sidebar_w = self.widgets[1].as_page_selector().unwrap().sidebar_w();
-                demo_positions(self.width, self.height, sidebar_w)
+                demo_positions(self.width, self.height, sidebar_w, self.layout_idx)
             };
             self.apply_layout();
             let text = self.status_text.clone();
@@ -1292,6 +1319,11 @@ cascades in cce."
                         }
                         self.apply_layout();
                         changed = true;
+                    } else if self.widgets[52].take_click() {
+                        self.layout_idx = self.widgets[52].value() as usize;
+                        self.positions = demo_positions(self.width, self.height, self.widgets[1].as_page_selector().unwrap().sidebar_w(), self.layout_idx);
+                        self.apply_layout();
+                        changed = true;
                     } else if self.current_page == Page::Controls {
                         let mut run_opacity = false;
                         let mut run_blur = false;
@@ -1699,7 +1731,7 @@ full screen background.",
     }
 }
 
-fn demo_positions(sw: f32, sh: f32, sidebar_w: f32) -> Vec<(f32, f32, f32, f32)> {
+fn demo_positions(sw: f32, sh: f32, sidebar_w: f32, layout_idx: usize) -> Vec<(f32, f32, f32, f32)> {
     let base_x = sidebar_w + 20.0;
     let sph = cce_ui::layout::spinbox_height();
     let tgh = cce_ui::layout::toggle_height();
@@ -1707,7 +1739,7 @@ fn demo_positions(sw: f32, sh: f32, sidebar_w: f32) -> Vec<(f32, f32, f32, f32)>
     let bh = cce_ui::layout::button_height();
     let ddh = cce_ui::layout::dropdown_height();
     
-    let mut vec = vec![(0.0, 0.0, 0.0, 0.0); 52];
+    let mut vec = vec![(0.0, 0.0, 0.0, 0.0); 53];
     
     // Common layout elements
     vec[0] = (0.0, 0.0, sw, 40.0); // 0 MenuBar
@@ -1750,8 +1782,9 @@ fn demo_positions(sw: f32, sh: f32, sidebar_w: f32) -> Vec<(f32, f32, f32, f32)>
     vec[46] = (sw - 140.0, sh - 14.0 - ddh / 2.0, 120.0, ddh); // 46 Dropdown: Page selector
     vec[48] = (base_x + 420.0, 580.0, 140.0, bh); // 48 Button: Bevel Shape
     vec[51] = (sw - 270.0, 60.0, 250.0, sh - 100.0); // 51 ControlPanel
+    vec[52] = (0.0, 0.0, 0.0, 0.0); // 52 Dropdown: Layout selector
     
-    // Dynamically position Page 0 (Controls) elements using MosaicLayout style bin packing
+    // Dynamically position Page 0 (Controls) elements using the selected layout index
     let available_w = (sw - base_x - 290.0).max(300.0);
     
     struct DemoPacker {
@@ -1820,8 +1853,6 @@ fn demo_positions(sw: f32, sh: f32, sidebar_w: f32) -> Vec<(f32, f32, f32, f32)>
         }
     }
 
-    let mut packer = DemoPacker::new(base_x, 60.0, available_w, 15.0);
-    
     let items = vec![
         // Diagnostics Buttons
         (3, 140.0, bh),
@@ -1844,10 +1875,154 @@ fn demo_positions(sw: f32, sh: f32, sidebar_w: f32) -> Vec<(f32, f32, f32, f32)>
         (50, 120.0, bh),
         (49, 200.0, 120.0),
     ];
-    
-    for (idx, w_item, h_item) in items {
-        let (px, py) = packer.pack(w_item, h_item);
-        vec[idx] = (px, py, w_item.min(available_w), h_item);
+
+    match layout_idx {
+        0 => {
+            // Vertical Layout
+            let mut cur_y = 60.0;
+            for (idx, _w_item, h_item) in items {
+                vec[idx] = (base_x, cur_y, available_w, h_item);
+                cur_y += h_item + 15.0;
+            }
+        }
+        1 => {
+            // Columns Layout
+            let num_cols = 3;
+            let col_w = (available_w - (num_cols - 1) as f32 * 15.0) / num_cols as f32;
+            let mut col_y = vec![60.0; num_cols];
+            for (i, (idx, _w_item, h_item)) in items.into_iter().enumerate() {
+                let col = i % num_cols;
+                let cx = base_x + col as f32 * (col_w + 15.0);
+                let cy = col_y[col];
+                vec[idx] = (cx, cy, col_w, h_item);
+                col_y[col] += h_item + 15.0;
+            }
+        }
+        2 | 3 => {
+            // Grid Layout and Adaptive Grid Layout
+            let num_cols = if layout_idx == 2 {
+                3
+            } else {
+                let min_col_w = 200.0;
+                (((available_w + 15.0) / (min_col_w + 15.0)).floor().max(1.0)) as usize
+            };
+            let col_w = (available_w - (num_cols - 1) as f32 * 15.0) / num_cols as f32;
+            let mut col_y = vec![60.0; num_cols];
+            for (idx, _w_item, h_item) in items {
+                let mut shortest_col = 0;
+                let mut min_h = col_y[0];
+                for col in 1..num_cols {
+                    if col_y[col] < min_h {
+                        min_h = col_y[col];
+                        shortest_col = col;
+                    }
+                }
+                let cx = base_x + shortest_col as f32 * (col_w + 15.0);
+                let cy = col_y[shortest_col];
+                vec[idx] = (cx, cy, col_w, h_item);
+                col_y[shortest_col] += h_item + 15.0;
+            }
+        }
+        4 => {
+            // Overlay Layout
+            for (idx, _w_item, h_item) in items {
+                vec[idx] = (base_x, 60.0, available_w, h_item);
+            }
+        }
+        5 => {
+            // Flex Layout
+            let mut cur_x = base_x;
+            let mut cur_y = 60.0;
+            let mut row_h = 0.0f32;
+            for (idx, w_item, h_item) in items {
+                let target_w = w_item.min(available_w);
+                if cur_x + target_w > base_x + available_w && cur_x > base_x {
+                    cur_x = base_x;
+                    cur_y += row_h + 15.0;
+                    row_h = 0.0;
+                }
+                vec[idx] = (cur_x, cur_y, target_w, h_item);
+                cur_x += target_w + 15.0;
+                row_h = row_h.max(h_item);
+            }
+        }
+        6 => {
+            // Splitter Layout
+            let count = items.len();
+            let total_h = (sh - 120.0).max(300.0);
+            let single_h = (total_h - (count - 1) as f32 * 10.0) / count as f32;
+            let mut cur_y = 60.0;
+            for (idx, _w_item, _h_item) in items {
+                vec[idx] = (base_x, cur_y, available_w, single_h);
+                cur_y += single_h + 10.0;
+            }
+        }
+        7 => {
+            // Radial Layout
+            let cx = base_x + available_w / 2.0;
+            let cy = 300.0;
+            let radius = 180.0;
+            let count = items.len();
+            for (i, (idx, w_item, h_item)) in items.into_iter().enumerate() {
+                let angle = (i as f32 / count as f32) * 2.0 * std::f32::consts::PI;
+                let px = cx + radius * angle.cos() - w_item / 2.0;
+                let py = cy + radius * angle.sin() - h_item / 2.0;
+                vec[idx] = (px, py, w_item, h_item);
+            }
+        }
+        8 => {
+            // Circular Pane Layout
+            let cx = base_x + available_w / 2.0;
+            let cy = 300.0;
+            let count = items.len();
+            for (i, (idx, w_item, h_item)) in items.into_iter().enumerate() {
+                let radius = 100.0 + (i as f32 * 12.0);
+                let angle = (i as f32 / count as f32) * 2.0 * std::f32::consts::PI;
+                let px = cx + radius * angle.cos() - w_item / 2.0;
+                let py = cy + radius * angle.sin() - h_item / 2.0;
+                vec[idx] = (px, py, w_item, h_item);
+            }
+        }
+        9 => {
+            // Mosaic Layout (Guillotine Packer)
+            let mut packer = DemoPacker::new(base_x, 60.0, available_w, 15.0);
+            for (idx, w_item, h_item) in items {
+                let (px, py) = packer.pack(w_item, h_item);
+                vec[idx] = (px, py, w_item.min(available_w), h_item);
+            }
+        }
+        _ => {
+            // Reverse Mosaic Layout
+            let mut packer = DemoPacker::new(base_x, 60.0, available_w, 15.0);
+            let mut temp = Vec::with_capacity(items.len());
+            for &(idx, w_item, h_item) in &items {
+                let (px, py) = packer.pack(w_item, h_item);
+                temp.push((px, py, w_item, h_item, idx));
+            }
+            let mut x_min = f32::MAX;
+            let mut x_max = f32::MIN;
+            let mut y_min = f32::MAX;
+            let mut y_max = f32::MIN;
+            for &(px, py, pw, ph, _) in &temp {
+                x_min = x_min.min(px);
+                x_max = x_max.max(px + pw);
+                y_min = y_min.min(py);
+                y_max = y_max.max(py + ph);
+            }
+            let src_w = (x_max - x_min).max(1.0);
+            let src_h = (y_max - y_min).max(1.0);
+            let dst_w = available_w;
+            let dst_h = (sh - 120.0).max(300.0);
+            let scale_x = dst_w / src_w;
+            let scale_y = dst_h / src_h;
+            for (px, py, pw, ph, idx) in temp {
+                let new_x = base_x + (px - x_min) * scale_x;
+                let new_y = 60.0 + (py - y_min) * scale_y;
+                let new_w = pw * scale_x;
+                let new_h = ph * scale_y;
+                vec[idx] = (new_x, new_y, new_w, new_h);
+            }
+        }
     }
     
     vec[6] = (-1000.0, -1000.0, 0.0, 0.0); // 6 is spacer Label, keep hidden/out of sight