1use taffy::{
2 Dimension, Display, FlexDirection, FlexWrap, GridAutoFlow, GridPlacement, LengthPercentage,
3 LengthPercentageAuto, Style,
4};
5
6pub use taffy::{AlignItems, AvailableSpace, JustifyContent};
7
8use crate::direction::Direction;
9use crate::track::TemplateTrack;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum SizeDimension {
13 Px(f32),
14 Percent(f32),
15 Auto,
16}
17
18impl From<f32> for SizeDimension {
19 fn from(px: f32) -> Self {
20 SizeDimension::Px(px)
21 }
22}
23
24impl From<SizeDimension> for Dimension {
25 fn from(d: SizeDimension) -> Self {
26 match d {
27 SizeDimension::Px(v) => Dimension::length(v),
28 SizeDimension::Percent(v) => Dimension::percent(v),
29 SizeDimension::Auto => Dimension::auto(),
30 }
31 }
32}
33
34#[derive(Clone, Copy, Debug, Default, PartialEq)]
38pub(crate) struct LogicalStyle {
39 pub(crate) padding_start: Option<f32>,
40 pub(crate) padding_end: Option<f32>,
41 pub(crate) margin_start: Option<f32>,
42 pub(crate) margin_end: Option<f32>,
43 pub(crate) inset_start: Option<f32>,
44 pub(crate) inset_end: Option<f32>,
45 pub(crate) row_follows_direction: bool,
48}
49
50impl LogicalStyle {
51 pub(crate) fn has_edges(&self) -> bool {
54 self.padding_start.is_some()
55 || self.padding_end.is_some()
56 || self.margin_start.is_some()
57 || self.margin_end.is_some()
58 || self.inset_start.is_some()
59 || self.inset_end.is_some()
60 }
61}
62
63#[derive(Clone)]
64pub struct LayoutStyle {
65 pub(crate) inner: Style,
66 pub(crate) logical: LogicalStyle,
67}
68
69impl LayoutStyle {
70 pub fn new() -> Self {
71 Self {
72 inner: Style {
73 display: Display::Block,
74 ..Style::default()
75 },
76 logical: LogicalStyle::default(),
77 }
78 }
79
80 pub fn flex_row(mut self) -> Self {
84 self.inner.display = Display::Flex;
85 self.inner.flex_direction = FlexDirection::Row;
86 self.logical.row_follows_direction = true;
87 self
88 }
89
90 pub fn flex_row_reverse(mut self) -> Self {
93 self.inner.display = Display::Flex;
94 self.inner.flex_direction = FlexDirection::RowReverse;
95 self.logical.row_follows_direction = false;
96 self
97 }
98
99 pub fn flex_column(mut self) -> Self {
100 self.inner.display = Display::Flex;
101 self.inner.flex_direction = FlexDirection::Column;
102 self.logical.row_follows_direction = false;
103 self
104 }
105
106 pub fn flex_wrap(mut self) -> Self {
107 self.inner.flex_wrap = FlexWrap::Wrap;
108 self
109 }
110
111 pub fn absolute_fill(mut self) -> Self {
115 self.inner.position = taffy::Position::Absolute;
116 let zero = LengthPercentageAuto::length(0.0);
117 self.inner.inset = taffy::Rect {
118 left: zero,
119 right: zero,
120 top: zero,
121 bottom: zero,
122 };
123 self
124 }
125
126 pub fn width_px(&self) -> Option<f32> {
129 self.inner.size.width.into_option()
130 }
131
132 pub fn is_width_auto(&self) -> bool {
134 self.inner.size.width.is_auto()
135 }
136
137 pub fn width(mut self, dim: impl Into<SizeDimension>) -> Self {
138 self.inner.size.width = dim.into().into();
139 self
140 }
141
142 pub fn height_px(&self) -> Option<f32> {
144 self.inner.size.height.into_option()
145 }
146
147 pub fn is_height_auto(&self) -> bool {
149 self.inner.size.height.is_auto()
150 }
151
152 pub fn height(mut self, dim: impl Into<SizeDimension>) -> Self {
153 self.inner.size.height = dim.into().into();
154 self
155 }
156
157 pub fn min_width(mut self, dim: impl Into<SizeDimension>) -> Self {
158 self.inner.min_size.width = dim.into().into();
159 self
160 }
161
162 pub fn min_height(mut self, dim: impl Into<SizeDimension>) -> Self {
163 self.inner.min_size.height = dim.into().into();
164 self
165 }
166
167 pub fn max_width_px(&self) -> Option<f32> {
170 self.inner.max_size.width.into_option()
171 }
172
173 pub fn max_width(mut self, dim: impl Into<SizeDimension>) -> Self {
174 self.inner.max_size.width = dim.into().into();
175 self
176 }
177
178 pub fn max_height(mut self, dim: impl Into<SizeDimension>) -> Self {
179 self.inner.max_size.height = dim.into().into();
180 self
181 }
182
183 pub fn flex_grow(mut self, grow: f32) -> Self {
184 self.inner.flex_grow = grow;
185 self
186 }
187
188 pub fn flex_shrink(mut self, shrink: f32) -> Self {
189 self.inner.flex_shrink = shrink;
190 self
191 }
192
193 pub fn flex_basis(mut self, dim: impl Into<SizeDimension>) -> Self {
194 self.inner.flex_basis = dim.into().into();
195 self
196 }
197
198 pub fn padding_all(mut self, px: f32) -> Self {
199 let value = LengthPercentage::length(px);
200 self.inner.padding = taffy::geometry::Rect {
201 left: value,
202 right: value,
203 top: value,
204 bottom: value,
205 };
206 self
207 }
208
209 pub fn padding_horizontal(mut self, px: f32) -> Self {
210 self.inner.padding.left = LengthPercentage::length(px);
211 self.inner.padding.right = LengthPercentage::length(px);
212 self
213 }
214
215 pub fn padding_vertical(mut self, px: f32) -> Self {
216 self.inner.padding.top = LengthPercentage::length(px);
217 self.inner.padding.bottom = LengthPercentage::length(px);
218 self
219 }
220
221 pub fn padding_top(mut self, px: f32) -> Self {
222 self.inner.padding.top = LengthPercentage::length(px);
223 self
224 }
225
226 pub fn padding_bottom(mut self, px: f32) -> Self {
227 self.inner.padding.bottom = LengthPercentage::length(px);
228 self
229 }
230
231 pub fn padding_left(mut self, px: f32) -> Self {
232 self.inner.padding.left = LengthPercentage::length(px);
233 self
234 }
235
236 pub fn padding_right(mut self, px: f32) -> Self {
237 self.inner.padding.right = LengthPercentage::length(px);
238 self
239 }
240
241 pub fn padding_start(mut self, px: f32) -> Self {
244 self.logical.padding_start = Some(px);
245 self
246 }
247
248 pub fn padding_end(mut self, px: f32) -> Self {
251 self.logical.padding_end = Some(px);
252 self
253 }
254
255 pub fn margin_all(mut self, px: f32) -> Self {
256 let value = LengthPercentageAuto::length(px);
257 self.inner.margin = taffy::geometry::Rect {
258 left: value,
259 right: value,
260 top: value,
261 bottom: value,
262 };
263 self
264 }
265
266 pub fn margin_horizontal(mut self, px: f32) -> Self {
267 self.inner.margin.left = LengthPercentageAuto::length(px);
268 self.inner.margin.right = LengthPercentageAuto::length(px);
269 self
270 }
271
272 pub fn margin_vertical(mut self, px: f32) -> Self {
273 self.inner.margin.top = LengthPercentageAuto::length(px);
274 self.inner.margin.bottom = LengthPercentageAuto::length(px);
275 self
276 }
277
278 pub fn margin_top(mut self, px: f32) -> Self {
279 self.inner.margin.top = LengthPercentageAuto::length(px);
280 self
281 }
282
283 pub fn margin_bottom(mut self, px: f32) -> Self {
284 self.inner.margin.bottom = LengthPercentageAuto::length(px);
285 self
286 }
287
288 pub fn margin_left(mut self, px: f32) -> Self {
289 self.inner.margin.left = LengthPercentageAuto::length(px);
290 self
291 }
292
293 pub fn margin_right(mut self, px: f32) -> Self {
294 self.inner.margin.right = LengthPercentageAuto::length(px);
295 self
296 }
297
298 pub fn margin_start(mut self, px: f32) -> Self {
301 self.logical.margin_start = Some(px);
302 self
303 }
304
305 pub fn margin_end(mut self, px: f32) -> Self {
308 self.logical.margin_end = Some(px);
309 self
310 }
311
312 pub fn inset_start(mut self, px: f32) -> Self {
315 self.logical.inset_start = Some(px);
316 self
317 }
318
319 pub fn inset_end(mut self, px: f32) -> Self {
321 self.logical.inset_end = Some(px);
322 self
323 }
324
325 pub fn gap(mut self, px: f32) -> Self {
326 self.inner.gap = taffy::geometry::Size {
327 width: LengthPercentage::length(px),
328 height: LengthPercentage::length(px),
329 };
330 self
331 }
332
333 pub fn gap_x(mut self, px: f32) -> Self {
334 self.inner.gap.width = LengthPercentage::length(px);
335 self
336 }
337
338 pub fn gap_y(mut self, px: f32) -> Self {
339 self.inner.gap.height = LengthPercentage::length(px);
340 self
341 }
342
343 pub fn align_items(mut self, value: AlignItems) -> Self {
344 self.inner.align_items = Some(value);
345 self
346 }
347
348 pub fn align_self_stretch(mut self) -> Self {
349 self.inner.align_self = Some(taffy::AlignSelf::STRETCH);
350 self
351 }
352
353 pub fn align_self_center(mut self) -> Self {
356 self.inner.align_self = Some(taffy::AlignSelf::CENTER);
357 self
358 }
359
360 pub fn align_self_start(mut self) -> Self {
362 self.inner.align_self = Some(taffy::AlignSelf::FLEX_START);
363 self
364 }
365
366 pub fn align_self_end(mut self) -> Self {
368 self.inner.align_self = Some(taffy::AlignSelf::FLEX_END);
369 self
370 }
371
372 pub fn justify_content(mut self, value: JustifyContent) -> Self {
373 self.inner.justify_content = Some(value);
374 self
375 }
376
377 pub fn display_grid(mut self) -> Self {
378 self.inner.display = Display::Grid;
379 self
380 }
381
382 pub fn grid_template_columns(mut self, tracks: Vec<TemplateTrack>) -> Self {
383 self.inner.grid_template_columns = tracks
384 .into_iter()
385 .map(|t| t.into_template_component())
386 .collect();
387 self
388 }
389
390 pub fn grid_template_rows(mut self, tracks: Vec<TemplateTrack>) -> Self {
391 self.inner.grid_template_rows = tracks
392 .into_iter()
393 .map(|t| t.into_template_component())
394 .collect();
395 self
396 }
397
398 pub fn grid_auto_flow_row(mut self) -> Self {
399 self.inner.grid_auto_flow = GridAutoFlow::Row;
400 self
401 }
402
403 pub fn grid_auto_flow_column(mut self) -> Self {
404 self.inner.grid_auto_flow = GridAutoFlow::Column;
405 self
406 }
407
408 pub fn grid_column(mut self, start: i16, end: i16) -> Self {
409 self.inner.grid_column = taffy::geometry::Line {
410 start: taffy::style_helpers::line(start),
411 end: taffy::style_helpers::line(end),
412 };
413 self
414 }
415
416 pub fn grid_row(mut self, start: i16, end: i16) -> Self {
417 self.inner.grid_row = taffy::geometry::Line {
418 start: taffy::style_helpers::line(start),
419 end: taffy::style_helpers::line(end),
420 };
421 self
422 }
423
424 pub fn grid_column_span(mut self, count: u16) -> Self {
425 self.inner.grid_column = taffy::geometry::Line {
426 start: GridPlacement::Span(count),
427 end: GridPlacement::Auto,
428 };
429 self
430 }
431
432 pub fn grid_row_span(mut self, count: u16) -> Self {
433 self.inner.grid_row = taffy::geometry::Line {
434 start: GridPlacement::Span(count),
435 end: GridPlacement::Auto,
436 };
437 self
438 }
439
440 pub fn aspect_ratio(mut self, ratio: f32) -> Self {
441 self.inner.aspect_ratio = Some(ratio);
442 self
443 }
444
445 pub(crate) fn resolve(&self, direction: Direction) -> Style {
448 let mut style = self.inner.clone();
449 let logical = &self.logical;
450 if logical.row_follows_direction && direction.is_rtl() {
451 style.flex_direction = FlexDirection::RowReverse;
452 }
453 let (start, end) = if direction.is_rtl() {
454 (Edge::Right, Edge::Left)
455 } else {
456 (Edge::Left, Edge::Right)
457 };
458 for (edge, px) in [(start, logical.padding_start), (end, logical.padding_end)] {
459 if let Some(px) = px {
460 *edge.of_mut(&mut style.padding) = LengthPercentage::length(px);
461 }
462 }
463 for (edge, px) in [(start, logical.margin_start), (end, logical.margin_end)] {
464 if let Some(px) = px {
465 *edge.of_mut(&mut style.margin) = LengthPercentageAuto::length(px);
466 }
467 }
468 for (edge, px) in [(start, logical.inset_start), (end, logical.inset_end)] {
469 if let Some(px) = px {
470 *edge.of_mut(&mut style.inset) = LengthPercentageAuto::length(px);
471 }
472 }
473 style
474 }
475}
476
477#[derive(Clone, Copy)]
478enum Edge {
479 Left,
480 Right,
481}
482
483impl Edge {
484 fn of_mut<T>(self, rect: &mut taffy::geometry::Rect<T>) -> &mut T {
485 match self {
486 Edge::Left => &mut rect.left,
487 Edge::Right => &mut rect.right,
488 }
489 }
490}
491
492impl Default for LayoutStyle {
493 fn default() -> Self {
494 Self::new()
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn logical_padding_resolves_to_the_edge_the_direction_starts_from() {
504 let style = LayoutStyle::new().padding_start(8.0).padding_end(2.0);
505 let ltr = style.resolve(Direction::Ltr);
506 assert_eq!(ltr.padding.left, LengthPercentage::length(8.0));
507 assert_eq!(ltr.padding.right, LengthPercentage::length(2.0));
508 let rtl = style.resolve(Direction::Rtl);
509 assert_eq!(rtl.padding.right, LengthPercentage::length(8.0));
510 assert_eq!(rtl.padding.left, LengthPercentage::length(2.0));
511 }
512
513 #[test]
514 fn a_physical_edge_is_left_alone_by_the_direction() {
515 let style = LayoutStyle::new().padding_left(12.0);
516 for direction in [Direction::Ltr, Direction::Rtl] {
517 let resolved = style.resolve(direction);
518 assert_eq!(resolved.padding.left, LengthPercentage::length(12.0));
519 assert_eq!(resolved.padding.right, LengthPercentage::length(0.0));
520 }
521 }
522
523 #[test]
524 fn resolving_twice_does_not_accumulate() {
525 let style = LayoutStyle::new().padding_start(8.0);
527 let _ = style.resolve(Direction::Rtl);
528 let back = style.resolve(Direction::Ltr);
529 assert_eq!(back.padding.left, LengthPercentage::length(8.0));
530 assert_eq!(back.padding.right, LengthPercentage::length(0.0));
531 }
532
533 #[test]
534 fn a_row_reverses_under_rtl_but_an_explicit_reverse_does_not_flip_back() {
535 let row = LayoutStyle::new().flex_row();
536 assert_eq!(
537 row.resolve(Direction::Ltr).flex_direction,
538 FlexDirection::Row
539 );
540 assert_eq!(
541 row.resolve(Direction::Rtl).flex_direction,
542 FlexDirection::RowReverse
543 );
544 let reversed = LayoutStyle::new().flex_row_reverse();
545 for direction in [Direction::Ltr, Direction::Rtl] {
546 assert_eq!(
547 reversed.resolve(direction).flex_direction,
548 FlexDirection::RowReverse,
549 "an explicit reverse is physical"
550 );
551 }
552 }
553
554 #[test]
555 fn a_column_is_unaffected_by_direction() {
556 let col = LayoutStyle::new().flex_column();
557 for direction in [Direction::Ltr, Direction::Rtl] {
558 assert_eq!(col.resolve(direction).flex_direction, FlexDirection::Column);
559 }
560 }
561
562 #[test]
563 fn only_logical_edges_need_the_style_kept_for_a_flip() {
564 assert!(!LayoutStyle::new().flex_row().logical.has_edges());
565 assert!(LayoutStyle::new().margin_start(4.0).logical.has_edges());
566 assert!(LayoutStyle::new().inset_end(4.0).logical.has_edges());
567 }
568
569 #[test]
570 fn style_default_is_block() {
571 let style = LayoutStyle::new();
572 assert_eq!(style.inner.display, Display::Block);
573 }
574
575 #[test]
576 fn style_width_sets_dimension() {
577 let style = LayoutStyle::new().width(120.0);
578 assert_eq!(style.inner.size.width, Dimension::length(120.0));
579 }
580
581 #[test]
582 fn style_width_px_reads_back_length() {
583 let style = LayoutStyle::new().width(120.0);
584 assert_eq!(style.width_px(), Some(120.0));
585 assert!(!style.is_width_auto());
586 }
587
588 #[test]
589 fn style_width_px_none_for_percent_or_default() {
590 assert_eq!(LayoutStyle::new().width_px(), None);
591 assert!(LayoutStyle::new().is_width_auto());
592 let percent = LayoutStyle::new().width(SizeDimension::Percent(0.5));
593 assert_eq!(percent.width_px(), None);
594 assert!(!percent.is_width_auto());
595 }
596
597 #[test]
598 fn style_width_percent_sets_dimension() {
599 let style = LayoutStyle::new().width(SizeDimension::Percent(0.5));
600 assert_eq!(style.inner.size.width, Dimension::percent(0.5));
601 }
602
603 #[test]
604 fn style_height_sets_dimension() {
605 let style = LayoutStyle::new().height(80.0);
606 assert_eq!(style.inner.size.height, Dimension::length(80.0));
607 }
608
609 #[test]
610 fn style_max_width_sets_dimension() {
611 let style = LayoutStyle::new().max_width(200.0);
612 assert_eq!(style.inner.max_size.width, Dimension::length(200.0));
613 }
614
615 #[test]
616 fn style_max_height_sets_dimension() {
617 let style = LayoutStyle::new().max_height(150.0);
618 assert_eq!(style.inner.max_size.height, Dimension::length(150.0));
619 }
620
621 #[test]
622 fn style_flex_basis_percent_sets_dimension() {
623 let style = LayoutStyle::new().flex_basis(SizeDimension::Percent(0.5));
624 assert_eq!(style.inner.flex_basis, Dimension::percent(0.5));
625 }
626
627 #[test]
628 fn style_flex_row_sets_direction() {
629 let style = LayoutStyle::new().flex_row();
630 assert_eq!(style.inner.flex_direction, FlexDirection::Row);
631 }
632
633 #[test]
634 fn style_flex_column_sets_direction() {
635 let style = LayoutStyle::new().flex_column();
636 assert_eq!(style.inner.flex_direction, FlexDirection::Column);
637 }
638
639 #[test]
640 fn style_align_items_center_sets_field() {
641 let style = LayoutStyle::new().align_items(AlignItems::CENTER);
642 assert_eq!(style.inner.align_items, Some(taffy::AlignItems::CENTER));
643 }
644
645 #[test]
646 fn style_justify_center_sets_field() {
647 let style = LayoutStyle::new().justify_content(JustifyContent::CENTER);
648 assert_eq!(
649 style.inner.justify_content,
650 Some(taffy::JustifyContent::CENTER)
651 );
652 }
653
654 #[test]
655 fn style_default_impl_matches_new() {
656 let style = LayoutStyle::default();
657 assert_eq!(style.inner.display, Display::Block);
658 }
659
660 #[test]
661 fn style_padding_horizontal_sets_left_right() {
662 let style = LayoutStyle::new().padding_horizontal(10.0);
663 assert_eq!(style.inner.padding.left, LengthPercentage::length(10.0));
664 assert_eq!(style.inner.padding.right, LengthPercentage::length(10.0));
665 }
666
667 #[test]
668 fn style_padding_vertical_sets_top_bottom() {
669 let style = LayoutStyle::new().padding_vertical(8.0);
670 assert_eq!(style.inner.padding.top, LengthPercentage::length(8.0));
671 assert_eq!(style.inner.padding.bottom, LengthPercentage::length(8.0));
672 }
673
674 #[test]
675 fn style_padding_top_sets_field() {
676 let style = LayoutStyle::new().padding_top(4.0);
677 assert_eq!(style.inner.padding.top, LengthPercentage::length(4.0));
678 }
679
680 #[test]
681 fn style_margin_horizontal_sets_left_right() {
682 let style = LayoutStyle::new().margin_horizontal(12.0);
683 assert_eq!(style.inner.margin.left, LengthPercentageAuto::length(12.0));
684 assert_eq!(style.inner.margin.right, LengthPercentageAuto::length(12.0));
685 }
686
687 #[test]
688 fn style_margin_top_sets_field() {
689 let style = LayoutStyle::new().margin_top(6.0);
690 assert_eq!(style.inner.margin.top, LengthPercentageAuto::length(6.0));
691 }
692}