tessera_components/
divider.rs1use tessera_ui::{
7 Color, ComputedData, Dp, LayoutResult, MeasurementError, Modifier, Px,
8 layout::{MeasureScope, layout},
9 tessera, use_context,
10};
11
12use crate::{
13 pipelines::divider::DividerCommand,
14 theme::{MaterialColorScheme, MaterialTheme},
15};
16
17fn resolve_thickness_px(thickness: Dp) -> f32 {
18 let thickness = if thickness.0.is_finite() {
19 thickness
20 } else {
21 DividerDefaults::THICKNESS
22 };
23 if thickness == Dp::ZERO {
24 1.0
25 } else {
26 thickness.to_pixels_f32().max(0.0)
27 }
28}
29
30pub struct DividerDefaults;
32
33impl DividerDefaults {
34 pub const THICKNESS: Dp = Dp(1.0);
36
37 pub fn color() -> Color {
41 MaterialColorScheme::default().outline_variant
42 }
43}
44
45#[derive(Clone, Copy, PartialEq)]
46enum DividerOrientation {
47 Horizontal,
48 Vertical,
49}
50
51#[derive(Clone, Copy, PartialEq)]
52struct DividerLayout {
53 thickness: f32,
54 color: Color,
55 orientation: DividerOrientation,
56}
57
58impl tessera_ui::LayoutModifierNode for DividerLayout {
59 fn measure_eq(&self, other: &Self) -> bool {
60 self.thickness == other.thickness && self.orientation == other.orientation
61 }
62
63 fn placement_eq(&self, other: &Self) -> bool {
64 self.measure_eq(other)
65 }
66
67 fn measure(&self, input: &mut MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
68 let (width, height) = match self.orientation {
69 DividerOrientation::Horizontal => (
70 input
71 .parent_constraint()
72 .width()
73 .resolve_max()
74 .unwrap_or(input.parent_constraint().width().resolve_min()),
75 input
76 .parent_constraint()
77 .height()
78 .clamp(Px(self.thickness.round() as i32)),
79 ),
80 DividerOrientation::Vertical => (
81 input
82 .parent_constraint()
83 .width()
84 .clamp(Px(self.thickness.round() as i32)),
85 input
86 .parent_constraint()
87 .height()
88 .resolve_max()
89 .unwrap_or(input.parent_constraint().height().resolve_min()),
90 ),
91 };
92
93 Ok(LayoutResult::new(ComputedData { width, height }))
94 }
95}
96
97impl tessera_ui::DrawModifierNode for DividerLayout {
98 fn draw(
99 &self,
100 ctx: &mut tessera_ui::DrawModifierContext<'_, '_>,
101 content: &mut dyn tessera_ui::DrawModifierContent,
102 ) {
103 let input = &mut *ctx.render_input;
104 let size = input
105 .metadata_mut()
106 .computed_data()
107 .expect("divider must be measured");
108 let vertical = self.orientation == DividerOrientation::Vertical;
109 let cross = if vertical { size.width } else { size.height };
110 let stroke = if cross < Px(self.thickness.round() as i32) {
111 self.thickness.min(cross.to_f32())
112 } else {
113 self.thickness
114 };
115 if size.width > Px::ZERO && size.height > Px::ZERO && stroke > 0.0 {
116 input
117 .metadata_mut()
118 .fragment_mut()
119 .push_draw_command(DividerCommand {
120 color: self.color,
121 stroke,
122 vertical,
123 });
124 }
125 content.draw(input);
126 }
127}
128
129#[tessera]
157pub fn horizontal_divider(modifier: Option<Modifier>, thickness: Option<Dp>, color: Option<Color>) {
158 let thickness_px = resolve_thickness_px(thickness.unwrap_or(DividerDefaults::THICKNESS));
159 let color = if let Some(color) = color {
160 color
161 } else {
162 use_context::<MaterialTheme>()
163 .expect("MaterialTheme must be provided")
164 .get()
165 .color_scheme
166 .outline_variant
167 };
168
169 let policy = DividerLayout {
170 thickness: thickness_px,
171 color,
172 orientation: DividerOrientation::Horizontal,
173 };
174 layout()
175 .modifier(modifier.unwrap_or_default())
176 .policy(policy)
177 .draw(policy);
178}
179
180#[tessera]
208pub fn vertical_divider(modifier: Option<Modifier>, thickness: Option<Dp>, color: Option<Color>) {
209 let thickness_px = resolve_thickness_px(thickness.unwrap_or(DividerDefaults::THICKNESS));
210 let color = if let Some(color) = color {
211 color
212 } else {
213 use_context::<MaterialTheme>()
214 .expect("MaterialTheme must be provided")
215 .get()
216 .color_scheme
217 .outline_variant
218 };
219
220 let policy = DividerLayout {
221 thickness: thickness_px,
222 color,
223 orientation: DividerOrientation::Vertical,
224 };
225 layout()
226 .modifier(modifier.unwrap_or_default())
227 .policy(policy)
228 .draw(policy);
229}
230
231#[cfg(test)]
232mod tests;
233
234#[cfg(all(test, not(target_family = "wasm")))]
235mod drawing_tests;