git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit291fa126ec2c5b9560087310836f004b84c995a9
parent6e5a845950
authorLucas Galante <[email protected]>
date2026-07-05 02:17
Implement ReverseMosaicLayout with proportional scaling

 src/layout.rs                            |  35 ++++++++++
 src/widget/container/container_layout.rs | 106 +++++++++++++++++++++++++++++++
 src/widget/container/mod.rs              |   2 +-
 src/widget/mod.rs                        |   2 +-
 4 files changed, 143 insertions(+), 2 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index a40260b..09528a7 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -5716,5 +5716,40 @@ mod tests {
         assert_eq!(w3.x, 125.0);
         assert_eq!(w3.y, 25.0);
     }
+
+    #[test]
+    fn test_reverse_mosaic_layout() {
+        use crate::widget::ReverseMosaicLayout;
+        use crate::layout::LayoutStrategy;
+        
+        let layout = ReverseMosaicLayout {
+            gap: 10.0,
+            padding_x: 5.0,
+            padding_y: 5.0,
+        };
+
+        let mut dummy = crate::context::UiContext::new();
+        let mut w1 = MockWidget { x: 0.0, y: 0.0, w: 100.0, h: 50.0 };
+        let mut w2 = MockWidget { x: 0.0, y: 0.0, w: 100.0, h: 80.0 };
+        let mut w3 = MockWidget { x: 0.0, y: 0.0, w: 80.0, h: 40.0 };
+        
+        let children = vec![
+            &mut w1 as *mut MockWidget as *mut (dyn Element + 'static),
+            &mut w2 as *mut MockWidget as *mut (dyn Element + 'static),
+            &mut w3 as *mut MockWidget as *mut (dyn Element + 'static),
+        ];
+
+        let _ = layout.layout(10.0, 20.0, 250.0, 300.0, &children, &mut dummy);
+
+        assert_eq!(w1.x, 15.0);
+        assert_eq!(w1.y, 25.0);
+        assert!((w1.w - 126.315).abs() < 0.01);
+        assert!((w1.h - 103.57).abs() < 0.01);
+
+        assert!((w3.x - 153.947).abs() < 0.01);
+        assert_eq!(w3.y, 25.0);
+        assert!((w3.w - 101.05).abs() < 0.01);
+        assert!((w3.h - 82.857).abs() < 0.01);
+    }
 }
 
diff --git a/src/widget/container/container_layout.rs b/src/widget/container/container_layout.rs
index d8c7750..b099704 100644
--- a/src/widget/container/container_layout.rs
+++ b/src/widget/container/container_layout.rs
@@ -597,3 +597,109 @@ impl ContainerLayout for MosaicLayout {
         Box::new(*self)
     }
 }
+
+#[derive(Debug, Clone, Copy)]
+pub struct ReverseMosaicLayout {
+    pub gap: f32,
+    pub padding_x: f32,
+    pub padding_y: f32,
+}
+
+impl Default for ReverseMosaicLayout {
+    fn default() -> Self {
+        Self {
+            gap: 8.0,
+            padding_x: 8.0,
+            padding_y: 10.0,
+        }
+    }
+}
+
+impl crate::layout::LayoutStrategy for ReverseMosaicLayout {
+    fn init(&mut self, _left: f32, _top: f32, _width: f32, _height: f32) {}
+    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) { (0.0, 0.0, ww, wh) }
+    fn get_gap(&self) -> f32 { self.gap }
+
+    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn Element + 'static)], ctx: &mut UiContext) -> f32 {
+        let count = children.len();
+        if count == 0 {
+            return 0.0;
+        }
+        let total_padding_x = self.padding_x * 2.0;
+        let available_w = (w - total_padding_x).max(1.0);
+
+        let mut packer = Packer::new(self.padding_x, self.padding_y, available_w, self.gap);
+        let mut temp_positions = Vec::with_capacity(count);
+
+        for &child_ptr in children {
+            unsafe {
+                let child = &mut *child_ptr;
+                let child_rect = child.rect();
+                let child_w = child_rect.2;
+                let child_h = child.preferred_height().unwrap_or(child_rect.3);
+                let use_h = if child_h > 0.0 { child_h } else { 44.0 };
+                
+                let (px, py) = packer.pack(child_w, use_h);
+                temp_positions.push((px, py, child_w, use_h));
+            }
+        }
+
+        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_positions {
+            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 = (h - 2.0 * self.padding_y).max(1.0);
+
+        let scale_x = dst_w / src_w;
+        let scale_y = dst_h / src_h;
+
+        for (i, &child_ptr) in children.iter().enumerate() {
+            unsafe {
+                let child = &mut *child_ptr;
+                let (px, py, pw, ph) = temp_positions[i];
+
+                let new_x = x + self.padding_x + (px - x_min) * scale_x;
+                let new_y = y + self.padding_y + (py - y_min) * scale_y;
+                let new_w = pw * scale_x;
+                let new_h = ph * scale_y;
+
+                child.layout(
+                    Point { x: new_x, y: new_y },
+                    LayoutConstraints::new(new_w, new_w, new_h, new_h),
+                    ctx,
+                );
+            }
+        }
+
+        h
+    }
+
+    fn measure(&self, constraints: LayoutConstraints, _children: &[*mut (dyn Element + 'static)], _ctx: &UiContext) -> Size {
+        Size {
+            width: constraints.max_width,
+            height: constraints.max_height,
+        }
+    }
+
+    fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
+        Box::new(*self)
+    }
+}
+
+impl ContainerLayout for ReverseMosaicLayout {
+    fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
+        Box::new(*self)
+    }
+}
diff --git a/src/widget/container/mod.rs b/src/widget/container/mod.rs
index a4ffb73..92e1b12 100644
--- a/src/widget/container/mod.rs
+++ b/src/widget/container/mod.rs
@@ -24,7 +24,7 @@ pub mod hbox;
 
 pub use container::Container;
 pub use control_panel::ControlPanel;
-pub use container_layout::{ContainerLayout, OverlayLayout, VerticalLayout, GridLayout, AdaptiveGridLayout, ColumnsLayout, MosaicLayout};
+pub use container_layout::{ContainerLayout, OverlayLayout, VerticalLayout, GridLayout, AdaptiveGridLayout, ColumnsLayout, MosaicLayout, ReverseMosaicLayout};
 pub use section_container::SectionContainer;
 pub use header::Header;
 pub use content_bg::ContentBg;
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index abf5f76..92c73d5 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -685,7 +685,7 @@ pub use self::input::{
 };
 pub use self::container::{
     Container, ContainerLayout, OverlayLayout, VerticalLayout, GridLayout, AdaptiveGridLayout,
-    ColumnsLayout, MosaicLayout,
+    ColumnsLayout, MosaicLayout, ReverseMosaicLayout,
     SectionContainer, Header, ContentBg, ViewportBg, ParametersBg, List,
     ScrollBox, Menu, MenuBar, Spreadsheet, Breadcrumb, Plate,
     Switcher, Layer, Page, Backplate, Paginator, ScrollBar, TreeList, TreeElement, ControlPanel