Skip to main content

telar_layout_core/
style.rs

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/// The parts of a style that cannot be turned into physical edges until a [`Direction`] is known. Kept
35/// alongside the resolved `taffy::Style` rather than folded into it, so a direction flip can re-resolve the
36/// original intent instead of trying to un-swap edges it can no longer tell apart from physical ones.
37#[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    /// Set by [`LayoutStyle::flex_row`]: the main axis is the inline axis, so it reverses under RTL. An
46    /// explicit [`LayoutStyle::flex_row_reverse`] leaves this clear — it means "reversed" in either direction.
47    pub(crate) row_follows_direction: bool,
48}
49
50impl LogicalStyle {
51    /// Whether any edge needs re-resolving on a direction flip. A direction-following row alone does not: it
52    /// is a single flag the engine can toggle in place, without the original style to resolve against.
53    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    /// A **block** box, as in CSS: children stack vertically and the flex properties do nothing.
71    ///
72    /// Worth saying out loud because the ones that do nothing do it silently. [`gap`](Self::gap),
73    /// [`justify_content`](Self::justify_content) and [`align_items`](Self::align_items) all belong to
74    /// flex layout, so on a box that never called [`flex_row`](Self::flex_row) or
75    /// [`flex_column`](Self::flex_column) they are accepted and ignored — a row written without
76    /// `flex_row` comes out as a column, and the reading on screen is not "that row is a column" but
77    /// "why is this panel twice as tall as it should be".
78    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    /// A flex row along the inline axis: items run left-to-right under [`Direction::Ltr`] and right-to-left
89    /// under [`Direction::Rtl`], the way `flex-direction: row` follows `dir` on the web. Use
90    /// [`flex_row_reverse`](Self::flex_row_reverse) for a row that is reversed in both directions.
91    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    /// A flex row laid out against the writing direction, unconditionally. Unlike
99    /// [`flex_row`](Self::flex_row) this is a physical choice and does not flip with [`Direction`].
100    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    /// Takes the node out of normal flow (`position: absolute`) with all four insets pinned to 0, so it
120    /// fills its containing block without affecting sibling layout — used by `overlay` to cover the
121    /// viewport. Combine with `flex_column`/alignment to position the overlay's content within the layer.
122    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    /// The node's `width` in pixels if it is a definite length, else `None` (e.g. percent or auto).
135    /// Lets widgets with an intrinsic size (e.g. `<svg>`/`<img>`) inspect a caller-supplied width before registering their layout leaf.
136    pub fn width_px(&self) -> Option<f32> {
137        self.inner.size.width.into_option()
138    }
139
140    /// True when `width` was left at its default, which taffy also treats as `auto`.
141    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    /// The node's `height` in pixels if it is a definite length, else `None` (e.g. percent or auto).
151    pub fn height_px(&self) -> Option<f32> {
152        self.inner.size.height.into_option()
153    }
154
155    /// True when `height` was left at its default, which taffy also treats as `auto`.
156    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    /// The node's `max-width` in pixels if it is a definite length, else `None`
176    /// (e.g. percent or unset). Used by the layout pass to pin a resolved width.
177    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    /// Padding on the edge the text starts from — `left` under [`Direction::Ltr`], `right` under
250    /// [`Direction::Rtl`].
251    pub fn padding_start(mut self, px: f32) -> Self {
252        self.logical.padding_start = Some(px);
253        self
254    }
255
256    /// Padding on the edge the text runs towards — `right` under [`Direction::Ltr`], `left` under
257    /// [`Direction::Rtl`].
258    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    /// Margin on the edge the text starts from — `left` under [`Direction::Ltr`], `right` under
307    /// [`Direction::Rtl`].
308    pub fn margin_start(mut self, px: f32) -> Self {
309        self.logical.margin_start = Some(px);
310        self
311    }
312
313    /// Margin on the edge the text runs towards — `right` under [`Direction::Ltr`], `left` under
314    /// [`Direction::Rtl`].
315    pub fn margin_end(mut self, px: f32) -> Self {
316        self.logical.margin_end = Some(px);
317        self
318    }
319
320    /// Inset from the edge the text starts from, for a node already taken out of flow (see
321    /// [`absolute_fill`](Self::absolute_fill)); ignored on an in-flow node, as `inset` is in CSS.
322    pub fn inset_start(mut self, px: f32) -> Self {
323        self.logical.inset_start = Some(px);
324        self
325    }
326
327    /// Inset from the edge the text runs towards, for a node already taken out of flow.
328    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    /// Overrides the parent's `align_items` for this child, centering it on the cross axis instead of
362    /// stretching — so a fixed-size child (e.g. a square icon chip) keeps its size and stays centered.
363    pub fn align_self_center(mut self) -> Self {
364        self.inner.align_self = Some(taffy::AlignSelf::CENTER);
365        self
366    }
367
368    /// Aligns this child to the start of the cross axis, overriding the parent's `align_items`.
369    pub fn align_self_start(mut self) -> Self {
370        self.inner.align_self = Some(taffy::AlignSelf::FLEX_START);
371        self
372    }
373
374    /// Aligns this child to the end of the cross axis, overriding the parent's `align_items`.
375    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    /// The physical `taffy::Style` this describes under `direction`. Called by the engine at every point a
454    /// style reaches a node, and again for each affected node when the direction flips.
455    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        // The engine re-resolves from the same LayoutStyle on every flip, so resolution must be a pure function of the intent.
534        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}