Skip to main content

rosin_core/css/
style.rs

1use std::{f64::consts::TAU, fmt::Display, sync::Arc};
2
3use kurbo::{Affine, Point, Rect};
4use parley::Alignment;
5use vello::peniko::{
6    self,
7    color::{ColorSpaceTag, HueDirection},
8};
9
10use crate::css::properties::ColorProperty;
11
12/// A `text-align` CSS value.
13#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
14pub enum TextAlign {
15    #[default]
16    Start,
17    End,
18    Left,
19    Right,
20    Center,
21    Justify,
22}
23
24impl std::fmt::Display for TextAlign {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            TextAlign::Start => f.write_str("start"),
28            TextAlign::End => f.write_str("end"),
29            TextAlign::Left => f.write_str("left"),
30            TextAlign::Right => f.write_str("right"),
31            TextAlign::Center => f.write_str("center"),
32            TextAlign::Justify => f.write_str("justify"),
33        }
34    }
35}
36
37impl From<TextAlign> for Alignment {
38    fn from(val: TextAlign) -> Self {
39        match val {
40            TextAlign::Start => Alignment::Start,
41            TextAlign::End => Alignment::End,
42            TextAlign::Left => Alignment::Left,
43            TextAlign::Right => Alignment::Right,
44            TextAlign::Center => Alignment::Center,
45            TextAlign::Justify => Alignment::Justify,
46        }
47    }
48}
49
50// ---------- Length ----------
51
52/// A definite length value.
53#[derive(Debug, Copy, Clone, PartialEq)]
54pub enum Length {
55    Px(f32),
56    Em(f32),
57}
58
59impl Eq for Length {}
60
61impl Default for Length {
62    fn default() -> Self {
63        Length::Px(0.0)
64    }
65}
66
67impl Length {
68    pub const ZERO: Length = Length::Px(0.0);
69
70    /// Resolves the length to a px value using the provided font size.
71    #[inline]
72    pub fn resolve(&self, font_size: f32) -> f32 {
73        match *self {
74            Length::Px(px) => px,
75            Length::Em(em) => em * font_size,
76        }
77    }
78}
79
80impl Display for Length {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        match *self {
83            Length::Px(v) => write!(f, "{v}px"),
84            Length::Em(v) => write!(f, "{v}em"),
85        }
86    }
87}
88
89impl From<f32> for Length {
90    fn from(value: f32) -> Self {
91        Self::Px(value)
92    }
93}
94
95impl From<f64> for Length {
96    fn from(value: f64) -> Self {
97        Self::Px(value as f32)
98    }
99}
100
101// ---------- Box Shadow ----------
102
103/// A `box-shadow` CSS value.
104#[derive(Debug, Default, Clone, Copy, PartialEq)]
105pub struct BoxShadow {
106    /// Horizontal offset of the shadow.
107    pub offset_x: Length,
108    /// Vertical offset of the shadow.
109    pub offset_y: Length,
110    /// Blur radius of the shadow.
111    pub blur: Length,
112    /// Spread radius of the shadow (positive grows, negative shrinks).
113    pub spread: Length,
114    /// Shadow color. `None` is equivalent to `currentcolor`.
115    pub color: Option<peniko::Color>,
116    /// Inset shadows are currently unsupported.
117    pub inset: bool, // TODO - update doc comment when they are.
118}
119
120impl Eq for BoxShadow {}
121
122impl Display for BoxShadow {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        if self.inset {
125            f.write_str("inset ")?;
126        }
127
128        write!(f, "{} {} {} {}", self.offset_x, self.offset_y, self.blur, self.spread)?;
129
130        if let Some(color) = self.color {
131            let color = color.to_rgba8();
132            write!(f, " #{:02X}{:02X}{:02X}{:02X}", color.r, color.g, color.b, color.a)?;
133        } else {
134            write!(f, " currentcolor")?;
135        }
136
137        Ok(())
138    }
139}
140
141// ---------- Text Shadow ----------
142
143/// A `text-shadow` CSS value.
144#[derive(Debug, Default, Clone, Copy, PartialEq)]
145pub struct TextShadow {
146    /// Horizontal offset of the shadow.
147    pub offset_x: Length,
148    /// Vertical offset of the shadow.
149    pub offset_y: Length,
150    /// Blur radius of the shadow.
151    pub blur: Length,
152    /// Shadow color. `None` is equivalent to `currentcolor`.
153    pub color: Option<peniko::Color>,
154}
155
156impl Eq for TextShadow {}
157
158impl Display for TextShadow {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        write!(f, "{} {} {}", self.offset_x, self.offset_y, self.blur)?;
161
162        if let Some(color) = self.color {
163            let color = color.to_rgba8();
164            write!(f, " #{:02X}{:02X}{:02X}{:02X}", color.r, color.g, color.b, color.a)?;
165        } else {
166            write!(f, " currentcolor")?;
167        }
168
169        Ok(())
170    }
171}
172
173// ---------- Direction ----------
174
175/// A CSS value that determines the direction that a node's children should be laid out.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum Direction {
178    Row,
179    RowReverse,
180    Column,
181    ColumnReverse,
182}
183
184impl Display for Direction {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        match self {
187            Direction::Row => f.write_str("row"),
188            Direction::RowReverse => f.write_str("row-reverse"),
189            Direction::Column => f.write_str("column"),
190            Direction::ColumnReverse => f.write_str("column-reverse"),
191        }
192    }
193}
194
195impl Direction {
196    pub fn is_row(&self) -> bool {
197        match self {
198            Direction::Row | Direction::RowReverse => true,
199            Direction::Column | Direction::ColumnReverse => false,
200        }
201    }
202
203    pub fn is_reverse(&self) -> bool {
204        match self {
205            Direction::RowReverse | Direction::ColumnReverse => true,
206            Direction::Column | Direction::Row => false,
207        }
208    }
209
210    pub fn other_axis(&self) -> Self {
211        match self {
212            Direction::Row => Direction::Column,
213            Direction::RowReverse => Direction::ColumnReverse,
214            Direction::Column => Direction::Row,
215            Direction::ColumnReverse => Direction::RowReverse,
216        }
217    }
218}
219
220// ---------- Gradient ----------
221
222/// An angle/direction for `linear-gradient(...)`.
223#[derive(Debug, Copy, Clone)]
224pub enum GradientAngle {
225    ToTop,
226    ToRight,
227    ToBottom,
228    ToLeft,
229    ToTopRight,
230    ToTopLeft,
231    ToBottomRight,
232    ToBottomLeft,
233    Radians(f32),
234    Degrees(f32),
235}
236
237impl PartialEq for GradientAngle {
238    fn eq(&self, other: &Self) -> bool {
239        use GradientAngle::*;
240
241        const EPS: f32 = 1.0e-6;
242
243        fn radians(a: &GradientAngle) -> Option<f32> {
244            match a {
245                Radians(r) => Some(*r),
246                Degrees(d) => Some(d.to_radians()),
247                _ => None,
248            }
249        }
250
251        fn approx_eq(a: f32, b: f32) -> bool {
252            // avoid treating NaN as equal to anything
253            if a.is_nan() || b.is_nan() {
254                return false;
255            }
256            (a - b).abs() <= EPS
257        }
258
259        match (self, other) {
260            // keyword directions only equal to themselves
261            (ToTop, ToTop)
262            | (ToRight, ToRight)
263            | (ToBottom, ToBottom)
264            | (ToLeft, ToLeft)
265            | (ToTopRight, ToTopRight)
266            | (ToTopLeft, ToTopLeft)
267            | (ToBottomRight, ToBottomRight)
268            | (ToBottomLeft, ToBottomLeft) => true,
269
270            // compare angles in radians
271            _ => match (radians(self), radians(other)) {
272                (Some(a), Some(b)) => approx_eq(a, b),
273                _ => false,
274            },
275        }
276    }
277}
278
279impl Display for GradientAngle {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        match self {
282            GradientAngle::ToTop => f.write_str("to top"),
283            GradientAngle::ToRight => f.write_str("to right"),
284            GradientAngle::ToBottom => f.write_str("to bottom"),
285            GradientAngle::ToLeft => f.write_str("to left"),
286            GradientAngle::ToTopRight => f.write_str("to top right"),
287            GradientAngle::ToTopLeft => f.write_str("to top left"),
288            GradientAngle::ToBottomRight => f.write_str("to bottom right"),
289            GradientAngle::ToBottomLeft => f.write_str("to bottom left"),
290            GradientAngle::Radians(value) => write!(f, "{value}rad"),
291            GradientAngle::Degrees(value) => write!(f, "{value}deg"),
292        }
293    }
294}
295
296/// A `linear-gradient(...)` CSS value.
297#[derive(Debug, Clone)]
298pub struct LinearGradient {
299    pub(crate) angle: GradientAngle,
300    pub(crate) gradient_stops: Vec<(f32, ColorProperty)>,
301    pub(crate) interpolation_cs: ColorSpaceTag,
302    pub(crate) hue_direction: HueDirection,
303}
304
305impl PartialEq for LinearGradient {
306    fn eq(&self, other: &Self) -> bool {
307        const EPS: f32 = 1.0e-6;
308
309        fn approx_eq(a: f32, b: f32) -> bool {
310            if a.is_nan() || b.is_nan() {
311                return false;
312            }
313            (a - b).abs() <= EPS
314        }
315
316        if self.angle != other.angle {
317            return false;
318        }
319        if self.interpolation_cs != other.interpolation_cs {
320            return false;
321        }
322        if self.hue_direction != other.hue_direction {
323            return false;
324        }
325
326        if self.gradient_stops.len() != other.gradient_stops.len() {
327            return false;
328        }
329
330        self.gradient_stops
331            .iter()
332            .zip(other.gradient_stops.iter())
333            .all(|((t1, c1), (t2, c2))| approx_eq(*t1, *t2) && c1 == c2)
334    }
335}
336
337impl Display for LinearGradient {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        use cssparser::ToCss;
340
341        f.write_str("linear-gradient(")?;
342        self.angle.fmt(f)?;
343        f.write_str(", ")?;
344        for (i, (offset, property_color)) in self.gradient_stops.iter().enumerate() {
345            match property_color {
346                ColorProperty::CurrentColor => cssparser_color::Color::CurrentColor,
347                ColorProperty::Color(color) => {
348                    let color = color.to_rgba8();
349                    cssparser_color::Color::Rgba(cssparser_color::RgbaLegacy {
350                        red: color.r,
351                        green: color.g,
352                        blue: color.b,
353                        alpha: color.a as f32 / 255.0,
354                    })
355                }
356            }
357            .to_css(f)?;
358            let percent = (offset * 100.0) as u32;
359            if percent != 0 && percent != 100 {
360                write!(f, " {percent}%")?;
361            }
362            if i != self.gradient_stops.len() - 1 {
363                f.write_str(", ")?;
364            }
365        }
366        f.write_str(")")
367    }
368}
369
370impl LinearGradient {
371    /// Creates a new `LinearGradient` with the given direction/angle and no stops.
372    ///
373    /// You must add stops with [`LinearGradient::add_stop`] before the gradient is usable.
374    /// A valid gradient needs at least two stops.
375    pub fn new(angle: impl Into<Option<GradientAngle>>) -> Self {
376        Self {
377            angle: angle.into().unwrap_or(GradientAngle::ToBottom),
378            gradient_stops: Vec::new(),
379            interpolation_cs: ColorSpaceTag::Srgb,
380            hue_direction: HueDirection::default(),
381        }
382    }
383
384    /// Add a stop to the gradient. It must have at least two stops to be valid. `color: None` is treated as `currentColor`
385    pub fn add_stop(mut self, offset: f32, color: impl Into<Option<peniko::Color>>) -> Self {
386        if let Some(color) = color.into() {
387            self.gradient_stops.push((offset.clamp(0.0, 1.0), ColorProperty::Color(color)));
388        } else {
389            self.gradient_stops.push((offset.clamp(0.0, 1.0), ColorProperty::CurrentColor));
390        }
391        self
392    }
393
394    /// Sets the interpolation color space used when blending between stops.
395    pub fn with_interpolation_space(mut self, cs: ColorSpaceTag) -> Self {
396        self.interpolation_cs = cs;
397        self
398    }
399
400    /// Sets the hue interpolation direction used by hue-based color spaces.
401    pub fn with_hue_direction(mut self, dir: HueDirection) -> Self {
402        self.hue_direction = dir;
403        self
404    }
405
406    /// Calculate the start and end points for a gradient, and resolve currentColor.
407    pub fn resolve(&self, rect: Rect, current_color: peniko::Color) -> peniko::Gradient {
408        let width = rect.width();
409        let height = rect.height();
410
411        let mut start;
412        let mut end;
413
414        let calc = |mut rad: f64| {
415            while rad < 0.0 {
416                rad += TAU;
417            }
418            while rad >= TAU {
419                rad -= TAU;
420            }
421
422            let u;
423            let v;
424            let hypot = width.hypot(height) / 2.0;
425            if rad < TAU * 0.25 {
426                let theta = (width / height).atan() - rad;
427                let len = theta.cos() * hypot;
428                let x = rad.sin() * len;
429                let y = rad.cos() * len;
430                u = (x / width) + 0.5;
431                v = ((height / 2.0) - y) / height;
432            } else if rad < TAU * 0.5 {
433                let theta = rad - (TAU / 4.0) - (height / width).atan();
434                let len = theta.cos() * hypot;
435                let x = rad.sin() * len;
436                let y = rad.cos() * len;
437                u = (x / width) + 0.5;
438                v = ((height / 2.0) - y) / height;
439            } else if rad < TAU * 0.75 {
440                let theta = (width / height).atan() - rad;
441                let len = theta.cos() * hypot;
442                let x = rad.sin() * len;
443                let y = rad.cos() * len;
444                u = 0.5 - (x / width);
445                v = 1.0 - ((height / 2.0) - y) / height;
446            } else {
447                let theta = rad - (3.0 * TAU / 4.0) - (height / width).atan();
448                let len = theta.cos() * hypot;
449                let x = rad.sin() * len;
450                let y = rad.cos() * len;
451                u = 1.0 - ((width / 2.0) - x) / width;
452                v = ((height / 2.0) - y) / height;
453            }
454
455            (Point::new(1.0 - u, 1.0 - v), Point::new(u, v))
456        };
457
458        match &self.angle {
459            GradientAngle::ToTop => {
460                start = Point::new(0.5, 1.0);
461                end = Point::new(0.5, 0.0);
462            }
463            GradientAngle::ToRight => {
464                start = Point::new(0.0, 0.5);
465                end = Point::new(1.0, 0.5);
466            }
467            GradientAngle::ToBottom => {
468                start = Point::new(0.5, 0.0);
469                end = Point::new(0.5, 1.0);
470            }
471            GradientAngle::ToLeft => {
472                start = Point::new(1.0, 0.5);
473                end = Point::new(0.0, 0.5);
474            }
475            GradientAngle::ToTopRight => {
476                (start, end) = calc((height / width).atan());
477            }
478            GradientAngle::ToTopLeft => {
479                (start, end) = calc((width / height).atan() - TAU / 4.0);
480            }
481            GradientAngle::ToBottomRight => {
482                (start, end) = calc((width / height).atan() + TAU / 4.0);
483            }
484            GradientAngle::ToBottomLeft => {
485                (start, end) = calc((height / width).atan() + TAU / 2.0);
486            }
487            GradientAngle::Degrees(deg) => {
488                (start, end) = calc(deg.to_radians() as f64);
489            }
490            GradientAngle::Radians(rad) => {
491                (start, end) = calc(*rad as f64);
492            }
493        }
494
495        start.x = start.x * width + rect.origin().x;
496        start.y = start.y * height + rect.origin().y;
497
498        end.x = end.x * width + rect.origin().x;
499        end.y = end.y * height + rect.origin().y;
500
501        let mut stops = peniko::ColorStops::new();
502        for &(offset, color) in &self.gradient_stops {
503            stops.push(peniko::ColorStop {
504                offset,
505                color: color.resolve(current_color).into(),
506            });
507        }
508
509        peniko::Gradient {
510            kind: peniko::GradientKind::Linear(peniko::LinearGradientPosition { start, end }),
511            extend: peniko::Extend::default(),
512            stops,
513            interpolation_cs: self.interpolation_cs,
514            ..Default::default()
515        }
516    }
517}
518
519/// A stack of gradients used in the `background-image` property.
520///
521/// Can be created with a [`GradientStackBuilder`]
522#[derive(Clone, Debug, PartialEq)]
523pub struct GradientStack {
524    pub(crate) stack: Arc<Vec<LinearGradient>>,
525}
526
527impl std::fmt::Display for GradientStack {
528    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529        for (i, gradient) in self.stack.iter().enumerate() {
530            if i != 0 {
531                write!(f, ", ")?;
532            }
533            write!(f, "{gradient}")?;
534        }
535        Ok(())
536    }
537}
538
539/// Used to build a [`GradientStack`].
540#[derive(Default)]
541pub struct GradientStackBuilder {
542    stack: Vec<LinearGradient>,
543}
544
545impl GradientStackBuilder {
546    /// Creates an empty gradient stack.
547    pub fn new() -> Self {
548        Self { stack: Vec::new() }
549    }
550
551    /// Pushes a `LinearGradient` onto the stack.
552    pub fn add_linear(mut self, gradient: LinearGradient) -> Self {
553        self.stack.push(gradient);
554        self
555    }
556
557    /// Finalizes the gradient stack.
558    pub fn build(self) -> GradientStack {
559        GradientStack { stack: Arc::new(self.stack) }
560    }
561}
562
563// ---------- Unit ----------
564
565/// A potentially flexible length value.
566#[derive(Debug, Copy, Clone, PartialEq)]
567pub enum Unit {
568    Auto,
569    Em(f32),
570    Percent(f32),
571    Px(f32),
572    Stretch(f32),
573}
574
575impl Eq for Unit {}
576
577impl Default for Unit {
578    fn default() -> Self {
579        Self::Px(0.0)
580    }
581}
582
583impl Unit {
584    /// Returns `true` if the value is a definite length.
585    pub fn is_definite(&self) -> bool {
586        matches!(self, Unit::Em(_) | Unit::Percent(_) | Unit::Px(_))
587    }
588
589    /// Computes the length if it's definite, otherwise returns `0.0`.
590    pub fn definite_size(&self, font_size: f32, pct_base: f32) -> f32 {
591        match self {
592            Unit::Em(em) => em * font_size,
593            Unit::Percent(pct) => pct * pct_base,
594            Unit::Px(px) => *px,
595            _ => 0.0,
596        }
597    }
598}
599
600impl Display for Unit {
601    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602        match self {
603            Unit::Auto => write!(f, "auto"),
604            Unit::Em(em) => write!(f, "{em}em"),
605            Unit::Percent(value) => write!(f, "{}%", value * 100.0),
606            Unit::Px(value) => write!(f, "{value}px"),
607            Unit::Stretch(value) => write!(f, "{value}s"),
608        }
609    }
610}
611
612// ---------- Position ----------
613
614/// A CSS value that determines how a node should be laid out.
615#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
616pub enum Position {
617    #[default]
618    ParentDirected,
619    SelfDirected,
620    Fixed,
621}
622
623impl Display for Position {
624    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625        match self {
626            Position::ParentDirected => f.write_str("parent-directed"),
627            Position::SelfDirected => f.write_str("self-directed"),
628            Position::Fixed => f.write_str("fixed"),
629        }
630    }
631}
632
633// ---------- Font Style ----------
634
635/// All of the properties needed for text layout.
636///
637/// Returned by [`Style::get_font_layout_style`].
638///
639/// Intended to be used for text layout cache invalidation.
640/// If these changed, the cache is invalid.
641#[derive(Clone, Debug, PartialEq)]
642pub struct FontLayoutStyle {
643    pub font_family: Option<Arc<str>>,
644    pub font_size: f32,
645    pub font_style: parley::style::FontStyle,
646    pub font_weight: f32,
647    pub font_width: f32,
648    pub line_height: Unit,
649    pub letter_spacing: Option<Unit>,
650    pub word_spacing: Option<Unit>,
651    pub text_align: TextAlign,
652}
653
654// ---------- Layout Style ----------
655
656/// All of the properties that can affect layout.
657/// Returned by [`Style::get_layout_style`]
658///
659/// Intended to be used for layout cache invalidation.
660/// If these changed, layout should be considered invalid.
661#[derive(Clone, Debug, PartialEq)]
662pub(crate) struct LayoutStyle {
663    // This need to match the values in Property::affects_layout
664    pub border_bottom_left_radius: Length,
665    pub border_bottom_right_radius: Length,
666    pub border_bottom_width: Length,
667    pub border_left_width: Length,
668    pub border_right_width: Length,
669    pub border_top_left_radius: Length,
670    pub border_top_right_radius: Length,
671    pub border_top_width: Length,
672    pub bottom: Unit,
673    pub child_between: Unit,
674    pub child_bottom: Unit,
675    pub child_left: Unit,
676    pub child_right: Unit,
677    pub child_top: Unit,
678    pub display: Option<Direction>,
679    pub flex_basis: Length,
680    pub font_family: Option<Arc<str>>,
681    pub font_size: f32,
682    pub font_style: parley::style::FontStyle,
683    pub font_weight: f32,
684    pub font_width: f32,
685    pub height: Unit,
686    pub left: Unit,
687    pub letter_spacing: Option<Unit>,
688    pub line_height: Unit,
689    pub max_bottom: Option<Length>,
690    pub max_child_between: Option<Length>,
691    pub max_child_bottom: Option<Length>,
692    pub max_child_left: Option<Length>,
693    pub max_child_right: Option<Length>,
694    pub max_child_top: Option<Length>,
695    pub max_height: Option<Length>,
696    pub max_left: Option<Length>,
697    pub max_right: Option<Length>,
698    pub max_top: Option<Length>,
699    pub max_width: Option<Length>,
700    pub min_bottom: Option<Length>,
701    pub min_child_between: Option<Length>,
702    pub min_child_bottom: Option<Length>,
703    pub min_child_left: Option<Length>,
704    pub min_child_right: Option<Length>,
705    pub min_child_top: Option<Length>,
706    pub min_height: Option<Length>,
707    pub min_left: Option<Length>,
708    pub min_right: Option<Length>,
709    pub min_top: Option<Length>,
710    pub min_width: Option<Length>,
711    pub position: Position,
712    pub right: Unit,
713    pub text_align: TextAlign,
714    pub top: Unit,
715    pub width: Unit,
716    pub word_spacing: Option<Unit>,
717}
718
719// ---------- Style ----------
720
721/// Computed style properties of a Node.
722///
723/// The [`Ui::on_style`](crate::tree::Ui::on_style) callback gives an application the opportunity to modify a node's style after CSS rules have been applied and before rendering.
724#[derive(Clone, PartialEq)]
725pub struct Style {
726    pub background_color: peniko::Color,
727    pub background_image: Option<GradientStack>,
728    pub border_bottom_color: peniko::Color,
729    pub border_bottom_left_radius: Length,
730    pub border_bottom_right_radius: Length,
731    pub border_bottom_width: Length,
732    pub border_left_color: peniko::Color,
733    pub border_left_width: Length,
734    pub border_right_color: peniko::Color,
735    pub border_right_width: Length,
736    pub border_top_color: peniko::Color,
737    pub border_top_left_radius: Length,
738    pub border_top_right_radius: Length,
739    pub border_top_width: Length,
740    pub bottom: Unit,
741    pub box_shadow: Option<Arc<[BoxShadow]>>,
742    pub child_between: Unit,
743    pub child_bottom: Unit,
744    pub child_left: Unit,
745    pub child_right: Unit,
746    pub child_top: Unit,
747    pub color: peniko::Color,
748    pub display: Option<Direction>,
749    pub flex_basis: Length,
750    pub font_family: Option<Arc<str>>,
751    pub font_size: f32,
752    pub font_style: parley::style::FontStyle,
753    pub font_weight: f32,
754    pub font_width: f32,
755    pub height: Unit,
756    pub left: Unit,
757    pub letter_spacing: Option<Unit>,
758    pub line_height: Unit,
759    pub max_bottom: Option<Length>,
760    pub max_child_between: Option<Length>,
761    pub max_child_bottom: Option<Length>,
762    pub max_child_left: Option<Length>,
763    pub max_child_right: Option<Length>,
764    pub max_child_top: Option<Length>,
765    pub max_height: Option<Length>,
766    pub max_left: Option<Length>,
767    pub max_right: Option<Length>,
768    pub max_top: Option<Length>,
769    pub max_width: Option<Length>,
770    pub min_bottom: Option<Length>,
771    pub min_child_between: Option<Length>,
772    pub min_child_bottom: Option<Length>,
773    pub min_child_left: Option<Length>,
774    pub min_child_right: Option<Length>,
775    pub min_child_top: Option<Length>,
776    pub min_height: Option<Length>,
777    pub min_left: Option<Length>,
778    pub min_right: Option<Length>,
779    pub min_top: Option<Length>,
780    pub min_width: Option<Length>,
781    pub opacity: f32,
782    pub outline_color: peniko::Color,
783    pub outline_offset: Length,
784    pub outline_width: Length,
785    pub position: Position,
786    pub right: Unit,
787    pub selection_background: peniko::Color,
788
789    /// If `selection_color` is `None`, selected text will not have a different color.
790    pub selection_color: Option<peniko::Color>,
791    pub text_align: TextAlign,
792    pub text_shadow: Option<Arc<[TextShadow]>>,
793    pub top: Unit,
794    pub transform: Affine,
795    pub visibility: bool,
796    pub width: Unit,
797    pub word_spacing: Option<Unit>,
798    pub z_index: i32,
799}
800
801impl std::fmt::Debug for Style {
802    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
803        let d = Style::default();
804        let mut s = f.debug_struct("Style");
805
806        macro_rules! diff {
807            ($field:ident) => {
808                if self.$field != d.$field {
809                    s.field(stringify!($field), &self.$field);
810                }
811            };
812        }
813
814        // peniko::Color -> "#RRGGBBAA" in sRGB (via to_rgba8)
815        macro_rules! diff_color {
816            ($field:ident) => {
817                if self.$field != d.$field {
818                    let c = self.$field.to_rgba8();
819                    s.field(stringify!($field), &format!("#{:02X}{:02X}{:02X}{:02X}", c.r, c.g, c.b, c.a));
820                }
821            };
822        }
823
824        macro_rules! diff_opt_color {
825            ($field:ident) => {
826                if self.$field != d.$field {
827                    match self.$field {
828                        None => s.field(stringify!($field), &"None"),
829                        Some(c) => {
830                            let c = c.to_rgba8();
831                            s.field(stringify!($field), &format!("Some(#{:02X}{:02X}{:02X}{:02X})", c.r, c.g, c.b, c.a))
832                        }
833                    };
834                }
835            };
836        }
837
838        diff_color!(background_color);
839        diff_color!(border_bottom_color);
840        diff_color!(border_left_color);
841        diff_color!(border_right_color);
842        diff_color!(border_top_color);
843        diff_color!(color);
844        diff_color!(outline_color);
845        diff_color!(selection_background);
846        diff_opt_color!(selection_color);
847        diff!(background_image);
848        diff!(border_bottom_left_radius);
849        diff!(border_bottom_right_radius);
850        diff!(border_bottom_width);
851        diff!(border_left_width);
852        diff!(border_right_width);
853        diff!(border_top_left_radius);
854        diff!(border_top_right_radius);
855        diff!(border_top_width);
856        diff!(bottom);
857        diff!(box_shadow);
858        diff!(child_between);
859        diff!(child_bottom);
860        diff!(child_left);
861        diff!(child_right);
862        diff!(child_top);
863        diff!(display);
864        diff!(flex_basis);
865        diff!(font_family);
866        diff!(font_size);
867        diff!(font_style);
868        diff!(font_weight);
869        diff!(font_width);
870        diff!(height);
871        diff!(left);
872        diff!(letter_spacing);
873        diff!(line_height);
874        diff!(max_bottom);
875        diff!(max_child_between);
876        diff!(max_child_bottom);
877        diff!(max_child_left);
878        diff!(max_child_right);
879        diff!(max_child_top);
880        diff!(max_height);
881        diff!(max_left);
882        diff!(max_right);
883        diff!(max_top);
884        diff!(max_width);
885        diff!(min_bottom);
886        diff!(min_child_between);
887        diff!(min_child_bottom);
888        diff!(min_child_left);
889        diff!(min_child_right);
890        diff!(min_child_top);
891        diff!(min_height);
892        diff!(min_left);
893        diff!(min_right);
894        diff!(min_top);
895        diff!(min_width);
896        diff!(opacity);
897        diff!(outline_offset);
898        diff!(outline_width);
899        diff!(position);
900        diff!(right);
901        diff!(text_align);
902        diff!(text_shadow);
903        diff!(top);
904        diff!(transform);
905        diff!(visibility);
906        diff!(width);
907        diff!(word_spacing);
908        diff!(z_index);
909
910        s.finish()
911    }
912}
913
914impl Default for Style {
915    fn default() -> Self {
916        Self {
917            background_color: peniko::Color::from_rgba8(0, 0, 0, 0),
918            background_image: None,
919            border_bottom_color: peniko::Color::from_rgba8(0, 0, 0, 255),
920            border_bottom_left_radius: Length::Px(0.0),
921            border_bottom_right_radius: Length::Px(0.0),
922            border_bottom_width: Length::Px(0.0),
923            border_left_color: peniko::Color::from_rgba8(0, 0, 0, 255),
924            border_left_width: Length::Px(0.0),
925            border_right_color: peniko::Color::from_rgba8(0, 0, 0, 255),
926            border_right_width: Length::Px(0.0),
927            border_top_color: peniko::Color::from_rgba8(0, 0, 0, 255),
928            border_top_left_radius: Length::Px(0.0),
929            border_top_right_radius: Length::Px(0.0),
930            border_top_width: Length::Px(0.0),
931            bottom: Unit::Auto,
932            box_shadow: None,
933            child_between: Unit::Auto,
934            child_bottom: Unit::Auto,
935            child_left: Unit::Auto,
936            child_right: Unit::Auto,
937            child_top: Unit::Auto,
938            color: peniko::Color::from_rgba8(0, 0, 0, 255),
939            display: Some(Direction::Column),
940            flex_basis: Length::Px(0.0),
941            font_family: None,
942            font_size: 16.0,
943            font_style: parley::style::FontStyle::Normal,
944            font_weight: 400.0,
945            font_width: 1.0,
946            height: Unit::Stretch(1.0),
947            left: Unit::Auto,
948            letter_spacing: None,
949            line_height: Unit::Stretch(1.2),
950            max_bottom: None,
951            max_child_between: None,
952            max_child_bottom: None,
953            max_child_left: None,
954            max_child_right: None,
955            max_child_top: None,
956            max_height: None,
957            max_left: None,
958            max_right: None,
959            max_top: None,
960            max_width: None,
961            min_bottom: None,
962            min_child_between: None,
963            min_child_bottom: None,
964            min_child_left: None,
965            min_child_right: None,
966            min_child_top: None,
967            min_height: None,
968            min_left: None,
969            min_right: None,
970            min_top: None,
971            min_width: None,
972            opacity: 1.0,
973            outline_color: peniko::Color::from_rgba8(0, 0, 0, 255),
974            outline_offset: Length::Px(0.0),
975            outline_width: Length::Px(0.0),
976            position: Position::ParentDirected,
977            right: Unit::Auto,
978            selection_background: peniko::Color::from_rgba8(4, 101, 175, 128),
979            selection_color: None,
980            text_align: TextAlign::Start,
981            text_shadow: None,
982            top: Unit::Auto,
983            transform: Affine::IDENTITY,
984            visibility: true,
985            width: Unit::Stretch(1.0),
986            word_spacing: None,
987            z_index: 0,
988        }
989    }
990}
991
992impl Style {
993    pub fn get_font_layout_style(&self) -> FontLayoutStyle {
994        FontLayoutStyle {
995            font_family: self.font_family.clone(),
996            font_size: self.font_size,
997            font_style: self.font_style,
998            font_weight: self.font_weight,
999            font_width: self.font_width,
1000            letter_spacing: self.letter_spacing,
1001            line_height: self.line_height,
1002            text_align: self.text_align,
1003            word_spacing: self.word_spacing,
1004        }
1005    }
1006
1007    pub(crate) fn get_layout_style(&self) -> LayoutStyle {
1008        LayoutStyle {
1009            border_bottom_left_radius: self.border_bottom_left_radius,
1010            border_bottom_right_radius: self.border_bottom_right_radius,
1011            border_bottom_width: self.border_bottom_width,
1012            border_left_width: self.border_left_width,
1013            border_right_width: self.border_right_width,
1014            border_top_left_radius: self.border_top_left_radius,
1015            border_top_right_radius: self.border_top_right_radius,
1016            border_top_width: self.border_top_width,
1017            bottom: self.bottom,
1018            child_between: self.child_between,
1019            child_bottom: self.child_bottom,
1020            child_left: self.child_left,
1021            child_right: self.child_right,
1022            child_top: self.child_top,
1023            display: self.display,
1024            flex_basis: self.flex_basis,
1025            font_family: self.font_family.clone(),
1026            font_size: self.font_size,
1027            font_style: self.font_style,
1028            font_weight: self.font_weight,
1029            font_width: self.font_width,
1030            height: self.height,
1031            left: self.left,
1032            letter_spacing: self.letter_spacing,
1033            line_height: self.line_height,
1034            max_bottom: self.max_bottom,
1035            max_child_between: self.max_child_between,
1036            max_child_bottom: self.max_child_bottom,
1037            max_child_left: self.max_child_left,
1038            max_child_right: self.max_child_right,
1039            max_child_top: self.max_child_top,
1040            max_height: self.max_height,
1041            max_left: self.max_left,
1042            max_right: self.max_right,
1043            max_top: self.max_top,
1044            max_width: self.max_width,
1045            min_bottom: self.min_bottom,
1046            min_child_between: self.min_child_between,
1047            min_child_bottom: self.min_child_bottom,
1048            min_child_left: self.min_child_left,
1049            min_child_right: self.min_child_right,
1050            min_child_top: self.min_child_top,
1051            min_height: self.min_height,
1052            min_left: self.min_left,
1053            min_right: self.min_right,
1054            min_top: self.min_top,
1055            min_width: self.min_width,
1056            position: self.position,
1057            right: self.right,
1058            text_align: self.text_align,
1059            top: self.top,
1060            width: self.width,
1061            word_spacing: self.word_spacing,
1062        }
1063    }
1064}