/// StyleModifier trait — fluent, zero-cost style chaining. /// /// Every el-ui component implements StyleModifier so callers can chain style /// adjustments in a natural builder pattern. The trait is compile-time only; /// there is no runtime allocation or dynamic dispatch. use crate::color::Color; use crate::radius::Radius; use crate::shadow::Shadow; use crate::typography::TextStyle; /// A dimension — how wide or tall something should be. #[derive(Debug, Clone, PartialEq)] pub enum Dimension { /// Exact density-independent pixels. Fixed(u32), /// Fill all available space from the parent. Fill, /// Shrink to fit the content. Wrap, /// Fraction of the parent's dimension (0.0–1.0). Fraction(f32), /// Minimum of two dimensions. Min(Box, Box), /// Maximum of two dimensions. Max(Box, Box), } impl Dimension { pub fn fraction(f: f32) -> Self { Dimension::Fraction(f.clamp(0.0, 1.0)) } pub fn half() -> Self { Dimension::Fraction(0.5) } pub fn full() -> Self { Dimension::Fill } } /// A complete set of style properties that can be applied to a component. /// /// All fields are optional — only the ones set by the caller are applied. /// The platform backend reads these when rendering and maps them to native /// style properties. #[derive(Debug, Clone, Default)] pub struct StyleSet { pub padding: Option<[u32; 4]>, // top, right, bottom, left pub margin: Option<[u32; 4]>, pub background: Option, pub foreground: Option, pub font: Option, pub radius: Option<[u32; 4]>, // top-left, top-right, bottom-right, bottom-left pub shadow: Option, pub opacity: Option, pub border_width: Option, pub border_color: Option, pub width: Option, pub height: Option, pub max_width: Option, pub max_height: Option, pub min_width: Option, pub min_height: Option, pub flex_grow: Option, pub flex_shrink: Option, pub z_index: Option, pub hidden: bool, pub clip: bool, } /// The core trait every styled el-ui component implements. /// /// Returns `Self` — the methods consume and return the component for /// fluent chaining without allocation. pub trait StyleModifier: Sized { /// Access the mutable StyleSet for this component. fn style_mut(&mut self) -> &mut StyleSet; /// Apply uniform padding on all four sides. fn padding(mut self, value: u32) -> Self { self.style_mut().padding = Some([value; 4]); self } /// Apply horizontal (x) and vertical (y) padding. fn padding_xy(mut self, x: u32, y: u32) -> Self { self.style_mut().padding = Some([y, x, y, x]); self } /// Apply padding individually: top, right, bottom, left. fn padding_sides(mut self, top: u32, right: u32, bottom: u32, left: u32) -> Self { self.style_mut().padding = Some([top, right, bottom, left]); self } /// Apply uniform margin on all four sides. fn margin(mut self, value: u32) -> Self { self.style_mut().margin = Some([value; 4]); self } /// Apply horizontal and vertical margin. fn margin_xy(mut self, x: u32, y: u32) -> Self { self.style_mut().margin = Some([y, x, y, x]); self } /// Set the background color. fn background(mut self, color: Color) -> Self { self.style_mut().background = Some(color); self } /// Set the foreground (text/icon) color. fn foreground(mut self, color: Color) -> Self { self.style_mut().foreground = Some(color); self } /// Set the text/font style. fn font(mut self, style: TextStyle) -> Self { self.style_mut().font = Some(style); self } /// Set a uniform border radius on all four corners. fn radius(mut self, radius: Radius) -> Self { let r = radius.dp(); self.style_mut().radius = Some([r; 4]); self } /// Set the shadow/elevation. fn shadow(mut self, elevation: Shadow) -> Self { self.style_mut().shadow = Some(elevation); self } /// Set the opacity (0.0 = invisible, 1.0 = fully visible). fn opacity(mut self, value: f32) -> Self { self.style_mut().opacity = Some(value.clamp(0.0, 1.0)); self } /// Set a border with width and color. fn border(mut self, width: u32, color: Color) -> Self { let s = self.style_mut(); s.border_width = Some(width); s.border_color = Some(color); self } /// Set the width. fn width(mut self, value: Dimension) -> Self { self.style_mut().width = Some(value); self } /// Set the height. fn height(mut self, value: Dimension) -> Self { self.style_mut().height = Some(value); self } /// Set the maximum width. fn max_width(mut self, value: Dimension) -> Self { self.style_mut().max_width = Some(value); self } /// Set the maximum height. fn max_height(mut self, value: Dimension) -> Self { self.style_mut().max_height = Some(value); self } /// Set the minimum width. fn min_width(mut self, value: Dimension) -> Self { self.style_mut().min_width = Some(value); self } /// Set the minimum height. fn min_height(mut self, value: Dimension) -> Self { self.style_mut().min_height = Some(value); self } /// How much this component grows to fill available space (flex-grow). fn grow(mut self, factor: f32) -> Self { self.style_mut().flex_grow = Some(factor); self } /// Z-index for layering. fn z_index(mut self, z: i32) -> Self { self.style_mut().z_index = Some(z); self } /// Clip content that overflows the component's bounds. fn clip(mut self) -> Self { self.style_mut().clip = true; self } /// Hide this component (still occupies space in layout). fn hidden(mut self, value: bool) -> Self { self.style_mut().hidden = value; self } } /// Pre-composed style variants for common component types. /// Button style variants. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ButtonVariant { /// Filled primary-color button (the main CTA). Primary, /// Filled secondary-color button. Secondary, /// Destructive action (red). Destructive, /// Text-only, no fill or border. Ghost, /// Inline hyperlink style. Link, } /// Card style variants. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CardStyle { /// Card with a drop shadow. Elevated, /// Card with an outline border, no shadow. Outlined, /// Card with a filled background color, no shadow. Filled, } /// Text input style variants. #[derive(Debug, Clone, PartialEq, Eq)] pub enum InputStyle { /// Standard input (platform default). Default, /// Outlined/bordered input. Outlined, /// Filled background input. Filled, } #[cfg(test)] mod tests { use super::*; use crate::color::Color; use crate::radius::Radius; use crate::shadow::Shadow; use crate::typography::TextStyle; /// A minimal test component that implements StyleModifier. #[derive(Default)] struct TestWidget { style: StyleSet, } impl StyleModifier for TestWidget { fn style_mut(&mut self) -> &mut StyleSet { &mut self.style } } #[test] fn padding_sets_all_sides() { let w = TestWidget::default().padding(16); assert_eq!(w.style.padding, Some([16; 4])); } #[test] fn padding_xy_sets_correctly() { let w = TestWidget::default().padding_xy(8, 16); // [top, right, bottom, left] = [y, x, y, x] assert_eq!(w.style.padding, Some([16, 8, 16, 8])); } #[test] fn background_stored() { let w = TestWidget::default().background(Color::Primary); assert_eq!(w.style.background, Some(Color::Primary)); } #[test] fn foreground_stored() { let w = TestWidget::default().foreground(Color::OnPrimary); assert_eq!(w.style.foreground, Some(Color::OnPrimary)); } #[test] fn font_stored() { let w = TestWidget::default().font(TextStyle::Body); assert_eq!(w.style.font, Some(TextStyle::Body)); } #[test] fn radius_stored() { let w = TestWidget::default().radius(Radius::Md); assert_eq!(w.style.radius, Some([8; 4])); } #[test] fn shadow_stored() { let w = TestWidget::default().shadow(Shadow::Md); assert_eq!(w.style.shadow, Some(Shadow::Md)); } #[test] fn opacity_clamped() { let w = TestWidget::default().opacity(2.5); assert_eq!(w.style.opacity, Some(1.0)); } #[test] fn opacity_valid() { let w = TestWidget::default().opacity(0.5); assert!((w.style.opacity.unwrap() - 0.5).abs() < 0.001); } #[test] fn border_stored() { let w = TestWidget::default().border(2, Color::Outline); assert_eq!(w.style.border_width, Some(2)); assert_eq!(w.style.border_color, Some(Color::Outline)); } #[test] fn chain_multiple_modifiers() { let w = TestWidget::default() .padding(16) .background(Color::Surface) .radius(Radius::Lg) .shadow(Shadow::Sm) .opacity(0.9); assert!(w.style.padding.is_some()); assert!(w.style.background.is_some()); assert!(w.style.radius.is_some()); assert!(w.style.shadow.is_some()); assert!(w.style.opacity.is_some()); } #[test] fn hidden_flag() { let w = TestWidget::default().hidden(true); assert!(w.style.hidden); } #[test] fn clip_flag() { let w = TestWidget::default().clip(); assert!(w.style.clip); } #[test] fn dimension_fraction_clamped() { let d = Dimension::fraction(1.5); assert_eq!(d, Dimension::Fraction(1.0)); } #[test] fn width_stored() { let w = TestWidget::default().width(Dimension::Fill); assert_eq!(w.style.width, Some(Dimension::Fill)); } #[test] fn max_width_stored() { let w = TestWidget::default().max_width(Dimension::Fixed(600)); assert_eq!(w.style.max_width, Some(Dimension::Fixed(600))); } }