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