GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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