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;