/// VStack, HStack, ZStack — the primary layout building blocks. /// /// These are the component API. FlexLayout is the engine underneath. /// VStack and HStack wrap automatically by default — that's what makes /// the layout responsive without writing breakpoints. use crate::flex::{CrossAxisAlignment, FlexLayout, HAlign, VAlign}; use el_style::modifier::{StyleModifier, StyleSet}; /// A vertical stack — children arranged from top to bottom. /// /// Wraps automatically when height is constrained (mobile-first). /// The default gap is 8dp. Cross-axis fills the container width. #[derive(Debug, Clone)] pub struct VStack { /// Space between children in dp (default: 8). pub spacing: u32, /// Horizontal alignment of children (default: Leading). pub alignment: HAlign, /// Whether to wrap to a new column when out of vertical space (default: true). pub wrap: bool, pub style: StyleSet, } impl Default for VStack { fn default() -> Self { Self { spacing: 8, alignment: HAlign::Leading, wrap: true, style: StyleSet::default(), } } } impl VStack { pub fn new() -> Self { Self::default() } pub fn spacing(mut self, dp: u32) -> Self { self.spacing = dp; self } pub fn alignment(mut self, align: HAlign) -> Self { self.alignment = align; self } /// Enable or disable wrapping. pub fn wrap(mut self, wrap: bool) -> Self { self.wrap = wrap; self } /// Convert to FlexLayout spec. pub fn to_flex(&self) -> FlexLayout { let mut flex = FlexLayout::vstack(self.spacing, self.wrap); flex.cross_axis_alignment = match self.alignment { HAlign::Leading => CrossAxisAlignment::Start, HAlign::Center => CrossAxisAlignment::Center, HAlign::Trailing => CrossAxisAlignment::End, }; flex } } impl StyleModifier for VStack { fn style_mut(&mut self) -> &mut StyleSet { &mut self.style } } /// A horizontal stack — children arranged from leading to trailing. /// /// Respects reading direction (RTL reverses automatically). /// Wraps to new rows by default when width is limited (mobile-first). #[derive(Debug, Clone)] pub struct HStack { /// Space between children in dp (default: 8). pub spacing: u32, /// Vertical alignment of children (default: Center). pub alignment: VAlign, /// Whether to wrap to a new row when out of horizontal space (default: true). pub wrap: bool, pub style: StyleSet, } impl Default for HStack { fn default() -> Self { Self { spacing: 8, alignment: VAlign::Center, wrap: true, style: StyleSet::default(), } } } impl HStack { pub fn new() -> Self { Self::default() } pub fn spacing(mut self, dp: u32) -> Self { self.spacing = dp; self } pub fn alignment(mut self, align: VAlign) -> Self { self.alignment = align; self } pub fn wrap(mut self, wrap: bool) -> Self { self.wrap = wrap; self } /// Convert to FlexLayout spec. pub fn to_flex(&self) -> FlexLayout { let mut flex = FlexLayout::hstack(self.spacing, self.wrap); flex.cross_axis_alignment = match self.alignment { VAlign::Top => CrossAxisAlignment::Start, VAlign::Center => CrossAxisAlignment::Center, VAlign::Bottom => CrossAxisAlignment::End, VAlign::Baseline => CrossAxisAlignment::Baseline, }; flex } } impl StyleModifier for HStack { fn style_mut(&mut self) -> &mut StyleSet { &mut self.style } } /// A depth stack — children layered on top of each other. /// /// ZStack places all children at the same position, overlapping. /// Later children appear on top of earlier children. #[derive(Debug, Clone)] pub struct ZStack { pub h_align: HAlign, pub v_align: VAlign, pub style: StyleSet, } impl Default for ZStack { fn default() -> Self { Self { h_align: HAlign::Center, v_align: VAlign::Center, style: StyleSet::default(), } } } impl ZStack { pub fn new() -> Self { Self::default() } pub fn h_align(mut self, align: HAlign) -> Self { self.h_align = align; self } pub fn v_align(mut self, align: VAlign) -> Self { self.v_align = align; self } } impl StyleModifier for ZStack { fn style_mut(&mut self) -> &mut StyleSet { &mut self.style } } /// A spacer that expands to fill available space in a stack. /// /// In HStack: expands horizontally. In VStack: expands vertically. #[derive(Debug, Clone, Default)] pub struct Spacer { /// Minimum size in dp (0 = truly flexible). pub min_size: u32, } impl Spacer { pub fn new() -> Self { Self::default() } pub fn min(mut self, dp: u32) -> Self { self.min_size = dp; self } } #[cfg(test)] mod tests { use super::*; use crate::flex::CrossAxisAlignment; use el_style::modifier::StyleModifier; use el_style::color::Color; #[test] fn vstack_default_spacing() { let v = VStack::new(); assert_eq!(v.spacing, 8); } #[test] fn vstack_default_wraps() { let v = VStack::new(); assert!(v.wrap); } #[test] fn vstack_to_flex_direction() { let v = VStack::new(); let flex = v.to_flex(); assert!(flex.direction.is_vertical()); } #[test] fn vstack_center_alignment() { let v = VStack::new().alignment(HAlign::Center); let flex = v.to_flex(); assert_eq!(flex.cross_axis_alignment, CrossAxisAlignment::Center); } #[test] fn hstack_default_alignment() { let h = HStack::new(); assert_eq!(h.alignment, VAlign::Center); } #[test] fn hstack_default_wraps() { let h = HStack::new(); assert!(h.wrap); } #[test] fn hstack_to_flex_direction() { let h = HStack::new(); let flex = h.to_flex(); assert!(flex.direction.is_horizontal()); } #[test] fn hstack_no_wrap() { let h = HStack::new().wrap(false); let flex = h.to_flex(); assert!(!flex.direction.wraps()); } #[test] fn zstack_defaults() { let z = ZStack::new(); assert_eq!(z.h_align, HAlign::Center); assert_eq!(z.v_align, VAlign::Center); } #[test] fn vstack_style_modifier() { let v = VStack::new().background(Color::Surface); assert_eq!(v.style.background, Some(Color::Surface)); } #[test] fn spacer_min_size() { let s = Spacer::new().min(16); assert_eq!(s.min_size, 16); } #[test] fn spacer_default_zero() { let s = Spacer::new(); assert_eq!(s.min_size, 0); } }