Skip to main content

smudge_core/
style.rs

1//! Style data for one node, split into layout properties and paint properties.
2//!
3//! The split is the mechanism that lets a paint-only change skip layout.
4//! [`LayoutProps`] converts to the layout engine's style and is compared before
5//! and after a change to decide whether the node needs layout again, while
6//! [`PaintProps`] never reaches the layout engine.
7//!
8//! There is no cascade and no inheritance: every node carries a complete
9//! [`Style`] of its own.
10
11use crate::geometry::Size;
12use crate::theme::{ColorValue, RadiusValue};
13
14/// A length along one axis.
15#[derive(Copy, Clone, Debug, PartialEq)]
16pub enum Length {
17    /// A fixed length in logical pixels.
18    Px(f32),
19    /// A percentage of the containing block's size, where `50.0` is half.
20    Percent(f32),
21    /// A length the layout algorithm computes from the content and the space
22    /// available.
23    Auto,
24}
25
26impl Length {
27    /// A fixed length in logical pixels.
28    pub const fn px(value: f32) -> Length {
29        Length::Px(value)
30    }
31
32    /// A percentage of the containing block's size, where `50.0` is half.
33    pub const fn pct(percent: f32) -> Length {
34        Length::Percent(percent)
35    }
36
37    /// A length the layout algorithm computes.
38    pub const fn auto() -> Length {
39        Length::Auto
40    }
41}
42
43/// A fixed length in logical pixels, for builders that take a [`Length`].
44///
45/// ```
46/// use smudge_core::style::px;
47///
48/// assert_eq!(px(8.0), smudge_core::style::Length::px(8.0));
49/// ```
50pub const fn px(value: f32) -> Length {
51    Length::px(value)
52}
53
54/// A length in percent of the containing block's size, for builders that take
55/// a [`Length`].
56///
57/// ```
58/// use smudge_core::style::{Length, pct};
59///
60/// assert_eq!(pct(50.0), Length::pct(50.0));
61/// ```
62pub const fn pct(percent: f32) -> Length {
63    Length::pct(percent)
64}
65
66/// Four edge values, one per side of a box.
67#[derive(Copy, Clone, Debug, PartialEq)]
68pub struct Edges<T> {
69    /// Value for the top edge.
70    pub top: T,
71    /// Value for the right edge.
72    pub right: T,
73    /// Value for the bottom edge.
74    pub bottom: T,
75    /// Value for the left edge.
76    pub left: T,
77}
78
79impl<T: Copy> Edges<T> {
80    /// The same value on every edge.
81    pub const fn all(value: T) -> Edges<T> {
82        Edges {
83            top: value,
84            right: value,
85            bottom: value,
86            left: value,
87        }
88    }
89
90    /// `x` on the left and right edges, `y` on the top and bottom edges.
91    pub const fn xy(x: T, y: T) -> Edges<T> {
92        Edges {
93            top: y,
94            right: x,
95            bottom: y,
96            left: x,
97        }
98    }
99}
100
101/// The main axis of a flex layout.
102#[derive(Copy, Clone, Debug, PartialEq)]
103pub enum Direction {
104    /// Children are placed left to right and wrap onto new lines downwards.
105    Row,
106    /// Children are placed top to bottom and wrap onto new columns.
107    Column,
108}
109
110/// Cross axis alignment of a node's children.
111#[derive(Copy, Clone, Debug, PartialEq)]
112pub enum Align {
113    /// Pack children against the start of the cross axis.
114    Start,
115    /// Pack children against the end of the cross axis.
116    End,
117    /// Center children on the cross axis.
118    Center,
119    /// Stretch children to fill the cross axis.
120    Stretch,
121}
122
123/// Main axis distribution of a node's children.
124#[derive(Copy, Clone, Debug, PartialEq)]
125pub enum Justify {
126    /// Pack children against the start of the main axis.
127    Start,
128    /// Pack children against the end of the main axis.
129    End,
130    /// Center children on the main axis.
131    Center,
132    /// Place the first and last child at the ends of the main axis and spread
133    /// the remaining space between them.
134    SpaceBetween,
135}
136
137/// Whether a node takes part in normal flow.
138#[derive(Copy, Clone, Debug, PartialEq)]
139pub enum Position {
140    /// The node sits in normal flow. Insets move it from where the flow put it
141    /// without moving its siblings, and the node becomes the containing block
142    /// for its absolutely positioned children. This is the default, so an
143    /// absolutely positioned child is placed against its own parent.
144    Relative,
145    /// The node leaves normal flow and is placed by its insets against the
146    /// nearest positioned ancestor. Siblings are laid out as if it were not
147    /// there.
148    Absolute,
149}
150
151/// Which layout algorithm arranges a node's children.
152#[derive(Copy, Clone, Debug, PartialEq)]
153pub enum Display {
154    /// Children are laid out as a flex row or column.
155    Flex,
156    /// Children are laid out one after another down the block axis.
157    Block,
158}
159
160/// What happens to children that do not fit inside their parent.
161#[derive(Copy, Clone, Debug, PartialEq)]
162#[non_exhaustive]
163pub enum Overflow {
164    /// Children paint outside the parent's bounds.
165    Visible,
166    /// Children are clipped to the parent's bounds.
167    Clip,
168    /// Children are clipped to the parent's bounds, and the parent is a
169    /// scroll container in layout: as a flex item its automatic minimum
170    /// size is zero rather than its content's size, so it shrinks to the
171    /// space it is given and its content scrolls inside it. The scroll view
172    /// and the virtual lists use this.
173    Scroll,
174}
175
176impl Overflow {
177    /// Whether children are clipped to the node's bounds.
178    pub fn clips(self) -> bool {
179        self != Overflow::Visible
180    }
181}
182
183/// The properties that take part in layout.
184///
185/// Every field here reaches the layout engine, so a change to any of them
186/// requires a new layout pass. A change to [`PaintProps`] alone does not.
187#[derive(Clone, Debug, PartialEq)]
188pub struct LayoutProps {
189    /// Which layout algorithm arranges this node's children.
190    pub display: Display,
191    /// Whether the node and its subtree are left out of layout and paint
192    /// nothing. Hiding is kept apart from [`LayoutProps::display`], so showing
193    /// the node again restores the layout algorithm it had.
194    pub hidden: bool,
195    /// The main axis of the flex layout.
196    pub direction: Direction,
197    /// Whether children that do not fit move to a new line instead of
198    /// shrinking.
199    pub wrap: bool,
200    /// Share of the leftover main axis space this node takes when it is a flex
201    /// child. Zero keeps the node at its basis.
202    pub grow: f32,
203    /// How readily this node gives up main axis space when flex children
204    /// overflow. Zero never shrinks.
205    pub shrink: f32,
206    /// The main axis size this node starts from before growing or shrinking.
207    pub basis: Length,
208    /// Size on the horizontal axis.
209    pub width: Length,
210    /// Size on the vertical axis.
211    pub height: Length,
212    /// Lower bound for [`LayoutProps::width`].
213    pub min_width: Length,
214    /// Lower bound for [`LayoutProps::height`].
215    pub min_height: Length,
216    /// Upper bound for [`LayoutProps::width`].
217    pub max_width: Length,
218    /// Upper bound for [`LayoutProps::height`].
219    pub max_height: Length,
220    /// Space between the node's edge and its children.
221    pub padding: Edges<Length>,
222    /// Space around the node, outside its border.
223    pub margin: Edges<Length>,
224    /// Space between adjacent children.
225    pub gap: Size,
226    /// Cross axis alignment for this node's children. `None` leaves the layout
227    /// engine's default, which stretches children across the cross axis.
228    pub align_items: Option<Align>,
229    /// Main axis distribution of this node's children. `None` leaves the
230    /// layout engine's default, which packs them against the start.
231    pub justify_content: Option<Justify>,
232    /// Whether the node stays in normal flow.
233    pub position: Position,
234    /// Offsets from the containing block's edges. They position an absolutely
235    /// positioned node and shift a relatively positioned one from its flow
236    /// position.
237    pub inset: Edges<Length>,
238    /// Border thickness inside the node's edge. The border takes space away
239    /// from the content and is painted in [`PaintProps::border_color`].
240    pub border_width: Edges<f32>,
241    /// Whether content that does not fit is clipped. This also affects the
242    /// automatic minimum size of a flex item, so it is a layout property.
243    pub overflow: Overflow,
244}
245
246impl Default for LayoutProps {
247    /// A flex row that is sized by its content, does not grow, shrinks freely
248    /// and takes no space beyond its children. These are the layout engine's
249    /// defaults, with the exception of [`Position::Relative`], which this crate
250    /// uses so that every node is a containing block for its own children.
251    fn default() -> LayoutProps {
252        LayoutProps {
253            display: Display::Flex,
254            hidden: false,
255            direction: Direction::Row,
256            wrap: false,
257            grow: 0.0,
258            shrink: 1.0,
259            basis: Length::Auto,
260            width: Length::Auto,
261            height: Length::Auto,
262            min_width: Length::Auto,
263            min_height: Length::Auto,
264            max_width: Length::Auto,
265            max_height: Length::Auto,
266            padding: Edges::all(Length::px(0.0)),
267            margin: Edges::all(Length::px(0.0)),
268            gap: Size::default(),
269            align_items: None,
270            justify_content: None,
271            position: Position::Relative,
272            inset: Edges::all(Length::auto()),
273            border_width: Edges::all(0.0),
274            overflow: Overflow::Visible,
275        }
276    }
277}
278
279impl LayoutProps {
280    /// Converts these properties to the layout engine's style.
281    ///
282    /// Percentages are divided by 100, so [`Length::pct`] with `50.0` becomes
283    /// the engine's fraction `0.5`. [`Length::Auto`] in a property that has no
284    /// automatic value, which is padding and gap, becomes zero.
285    pub(crate) fn to_taffy(&self) -> taffy::Style {
286        let overflow = match self.overflow {
287            Overflow::Visible => taffy::Overflow::Visible,
288            Overflow::Clip => taffy::Overflow::Clip,
289            Overflow::Scroll => taffy::Overflow::Scroll,
290        };
291        let mut style: taffy::Style = taffy::Style::DEFAULT;
292        style.display = match (self.hidden, self.display) {
293            (true, _) => taffy::Display::None,
294            (false, Display::Flex) => taffy::Display::Flex,
295            (false, Display::Block) => taffy::Display::Block,
296        };
297        style.flex_direction = match self.direction {
298            Direction::Row => taffy::FlexDirection::Row,
299            Direction::Column => taffy::FlexDirection::Column,
300        };
301        style.flex_wrap = if self.wrap {
302            taffy::FlexWrap::Wrap
303        } else {
304            taffy::FlexWrap::NoWrap
305        };
306        style.flex_grow = self.grow;
307        style.flex_shrink = self.shrink;
308        style.flex_basis = to_dimension(self.basis);
309        style.size = taffy::Size {
310            width: to_dimension(self.width),
311            height: to_dimension(self.height),
312        };
313        style.min_size = taffy::Size {
314            width: to_length_percentage_auto(self.min_width),
315            height: to_length_percentage_auto(self.min_height),
316        };
317        style.max_size = taffy::Size {
318            width: to_length_percentage_auto(self.max_width),
319            height: to_length_percentage_auto(self.max_height),
320        };
321        style.padding = to_taffy_edges(self.padding, to_length_percentage);
322        style.margin = to_taffy_edges(self.margin, to_length_percentage_auto);
323        style.gap = taffy::Size {
324            width: taffy::LengthPercentage::length(self.gap.width),
325            height: taffy::LengthPercentage::length(self.gap.height),
326        };
327        style.align_items = match self.align_items {
328            None => taffy::AlignItems::NORMAL,
329            Some(Align::Start) => taffy::AlignItems::START,
330            Some(Align::End) => taffy::AlignItems::END,
331            Some(Align::Center) => taffy::AlignItems::CENTER,
332            Some(Align::Stretch) => taffy::AlignItems::STRETCH,
333        };
334        style.justify_content = match self.justify_content {
335            None => taffy::JustifyContent::NORMAL,
336            Some(Justify::Start) => taffy::JustifyContent::START,
337            Some(Justify::End) => taffy::JustifyContent::END,
338            Some(Justify::Center) => taffy::JustifyContent::CENTER,
339            Some(Justify::SpaceBetween) => taffy::JustifyContent::SPACE_BETWEEN,
340        };
341        style.position = match self.position {
342            Position::Relative => taffy::Position::Relative,
343            Position::Absolute => taffy::Position::Absolute,
344        };
345        style.inset = to_taffy_edges(self.inset, to_length_percentage_auto);
346        style.border = to_taffy_edges(self.border_width, taffy::LengthPercentage::length);
347        style.overflow = taffy::Point {
348            x: overflow,
349            y: overflow,
350        };
351        style
352    }
353}
354
355/// The properties that are painted but never laid out.
356///
357/// Colors and radii are [`ColorValue`]s and [`RadiusValue`]s, so a style can
358/// name a theme token. They resolve to concrete values when the node paints,
359/// against the theme that is current then.
360#[derive(Clone, Debug, Default, PartialEq)]
361pub struct PaintProps {
362    /// Fill painted behind the node's content, or `None` for no fill.
363    pub background: Option<ColorValue>,
364    /// Color of the border drawn inside the node's edge, or `None` for no
365    /// border. Its thickness is [`LayoutProps::border_width`].
366    pub border_color: Option<ColorValue>,
367    /// Color a widget's text paints with, or `None` to let the widget use its
368    /// own. Widgets without text ignore it.
369    pub text_color: Option<ColorValue>,
370    /// Corner radius of the background and the border, the same on all four
371    /// corners.
372    pub corner_radius: RadiusValue,
373}
374
375/// The complete style of one node.
376#[derive(Clone, Debug, Default, PartialEq)]
377pub struct Style {
378    /// Properties that take part in layout.
379    pub layout: LayoutProps,
380    /// Properties that only affect painting.
381    pub paint: PaintProps,
382}
383
384/// Converts a length to a size, which accepts a percentage, a length or `auto`.
385fn to_dimension(length: Length) -> taffy::Dimension {
386    match length {
387        Length::Px(value) => taffy::Dimension::length(value),
388        Length::Percent(percent) => taffy::Dimension::percent(percent / 100.0),
389        Length::Auto => taffy::Dimension::auto(),
390    }
391}
392
393/// Converts a length to a spacing value, which accepts a percentage or a
394/// length but not `auto`. An automatic length becomes zero.
395fn to_length_percentage(length: Length) -> taffy::LengthPercentage {
396    match length {
397        Length::Px(value) => taffy::LengthPercentage::length(value),
398        Length::Percent(percent) => taffy::LengthPercentage::percent(percent / 100.0),
399        Length::Auto => taffy::LengthPercentage::length(0.0),
400    }
401}
402
403/// Converts a length to a spacing value that also accepts `auto`.
404fn to_length_percentage_auto(length: Length) -> taffy::LengthPercentageAuto {
405    match length {
406        Length::Px(value) => taffy::LengthPercentageAuto::length(value),
407        Length::Percent(percent) => taffy::LengthPercentageAuto::percent(percent / 100.0),
408        Length::Auto => taffy::LengthPercentageAuto::auto(),
409    }
410}
411
412/// Converts four edge values of one length type to the layout engine's edge
413/// struct.
414fn to_taffy_edges<T: Copy, U>(edges: Edges<T>, convert: impl Fn(T) -> U) -> taffy::Rect<U> {
415    taffy::Rect {
416        left: convert(edges.left),
417        right: convert(edges.right),
418        top: convert(edges.top),
419        bottom: convert(edges.bottom),
420    }
421}
422
423#[cfg(test)]
424#[path = "style_tests.rs"]
425mod tests;