Skip to main content

crabshell_ui/
style.rs

1//! Colors, fonts and [`Style`]. Everything here is `Copy` and built with
2//! `const fn`s, so a theme is a set of constants.
3
4use crate::animation::{Animate, Transition};
5use crate::geometry::{Affine, Point, Rect};
6
7/// An sRGB color with straight (not premultiplied) alpha.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub struct Color {
10  /// Red, 0 to 255.
11  pub r: u8,
12  /// Green, 0 to 255.
13  pub g: u8,
14  /// Blue, 0 to 255.
15  pub b: u8,
16  /// Alpha: 0 is transparent, 255 is opaque.
17  pub a: u8,
18}
19
20impl Color {
21  /// An opaque color.
22  pub const fn rgb(r: u8, g: u8, b: u8) -> Color {
23    Color { r, g, b, a: 255 }
24  }
25
26  /// A color with alpha: 0 is transparent, 255 is opaque.
27  pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
28    Color { r, g, b, a }
29  }
30
31  /// Parses `"#rrggbb"` or `"#rrggbbaa"`, in upper or lower case.
32  ///
33  /// # Panics
34  ///
35  /// If `hex` has any other form. In a `const`, that is a compile error:
36  ///
37  /// ```compile_fail
38  /// const BAD: crabshell_ui::Color = crabshell_ui::Color::hex("#12345");
39  /// ```
40  pub const fn hex(hex: &str) -> Color {
41    let bytes = hex.as_bytes();
42    if bytes.is_empty() || bytes[0] != b'#' || (bytes.len() != 7 && bytes.len() != 9) {
43      panic!("Color::hex takes \"#rrggbb\" or \"#rrggbbaa\"");
44    }
45    let a = if bytes.len() == 9 {
46      hex_byte(bytes[7], bytes[8])
47    } else {
48      255
49    };
50    Color {
51      r: hex_byte(bytes[1], bytes[2]),
52      g: hex_byte(bytes[3], bytes[4]),
53      b: hex_byte(bytes[5], bytes[6]),
54      a,
55    }
56  }
57}
58
59const fn hex_byte(high: u8, low: u8) -> u8 {
60  hex_digit(high) * 16 + hex_digit(low)
61}
62
63const fn hex_digit(digit: u8) -> u8 {
64  match digit {
65    b'0'..=b'9' => digit - b'0',
66    b'a'..=b'f' => digit - b'a' + 10,
67    b'A'..=b'F' => digit - b'A' + 10,
68    _ => panic!("Color::hex found a character that isn't a hex digit"),
69  }
70}
71
72/// A font: family, size and weight.
73///
74/// The family is resolved through fontconfig, so generic names such as
75/// `"sans-serif"` and `"monospace"` work too.
76#[derive(Clone, Copy, Debug, PartialEq)]
77pub struct Font {
78  /// The family name, such as `"Inter"` or `"sans-serif"`.
79  pub family: &'static str,
80  /// The size in logical pixels.
81  pub size: f32,
82  /// The weight, from 100 (thin) through 400 (regular) and 700 (bold) to
83  /// 900 (black).
84  pub weight: u16,
85}
86
87impl Font {
88  /// The font text uses when no ancestor sets one.
89  pub const DEFAULT: Font = Font::new("sans-serif", 13.0);
90
91  /// A regular-weight font of `family` at `size` logical pixels.
92  pub const fn new(family: &'static str, size: f32) -> Font {
93    Font {
94      family,
95      size,
96      weight: 400,
97    }
98  }
99
100  /// The same font with another weight, such as 700 for bold.
101  pub const fn weight(self, weight: u16) -> Font {
102    Font { weight, ..self }
103  }
104}
105
106/// A border drawn inside a widget's edge. It takes no layout space.
107#[derive(Clone, Copy, Debug, PartialEq)]
108pub struct Border {
109  /// The width in logical pixels.
110  pub width: f32,
111  /// The color.
112  pub color: Color,
113}
114
115/// Colors that blend into each other across a widget's box, drawn over
116/// its background with the same corners.
117///
118/// ```
119/// use crabshell_ui::{Color, Gradient, Style};
120///
121/// const DUSK: Style = Style::new().gradient(
122///     Gradient::linear(90.0, Color::hex("#1e1e2e"), Color::hex("#45475a"))
123///         .stop(0.5, Color::hex("#313244")),
124/// );
125/// ```
126#[derive(Clone, Copy, Debug, PartialEq)]
127pub struct Gradient {
128  /// Which way the colors run.
129  pub shape: GradientShape,
130  /// The first `count` hold each color and where it sits, from 0 at the
131  /// start to 1 at the end.
132  stops: [(f32, Color); 4],
133  count: u8,
134}
135
136/// Which way a [`Gradient`]'s colors run.
137#[derive(Clone, Copy, Debug, PartialEq)]
138pub enum GradientShape {
139  /// Along a line through the middle at this many degrees, as in CSS: 0
140  /// runs from the bottom up, 90 from left to right, 180 from the top
141  /// down. Its ends meet the corners.
142  Linear(f32),
143  /// In circles out from the middle to the farthest corner.
144  Radial,
145}
146
147impl Gradient {
148  /// A gradient from `from` to `to` along a line at `angle` degrees: 0
149  /// runs from the bottom up, 90 from left to right.
150  pub const fn linear(angle: f32, from: Color, to: Color) -> Gradient {
151    Gradient::new(GradientShape::Linear(angle), from, to)
152  }
153
154  /// A gradient from `from` in the middle to `to` at the farthest corner.
155  pub const fn radial(from: Color, to: Color) -> Gradient {
156    Gradient::new(GradientShape::Radial, from, to)
157  }
158
159  const fn new(shape: GradientShape, from: Color, to: Color) -> Gradient {
160    Gradient {
161      shape,
162      stops: [(0.0, from), (1.0, to), (1.0, to), (1.0, to)],
163      count: 2,
164    }
165  }
166
167  /// Adds `color` at `at`, from 0 to 1, before the end color. Add them
168  /// in order. A gradient has at most four colors, so a third stop
169  /// panics.
170  pub const fn stop(self, at: f32, color: Color) -> Gradient {
171    assert!(self.count < 4, "a gradient has at most four colors");
172    let mut stops = self.stops;
173    let last = self.count as usize - 1;
174    stops[last + 1] = stops[last];
175    stops[last] = (at, color);
176    Gradient {
177      stops,
178      count: self.count + 1,
179      ..self
180    }
181  }
182
183  /// Each color and where it sits, from the start to the end.
184  pub fn stops(&self) -> &[(f32, Color)] {
185    &self.stops[..usize::from(self.count)]
186  }
187
188  /// `t` of the way from this gradient to `to`, or `None` when their
189  /// shapes or numbers of colors differ.
190  fn lerp(&self, to: &Gradient, t: f32) -> Option<Gradient> {
191    let shape = match (self.shape, to.shape) {
192      (GradientShape::Linear(from), GradientShape::Linear(to)) => {
193        GradientShape::Linear(from.lerp(&to, t))
194      }
195      (GradientShape::Radial, GradientShape::Radial) => GradientShape::Radial,
196      _ => return None,
197    };
198    if self.count != to.count {
199      return None;
200    }
201    let mut stops = to.stops;
202    for (stop, (from, to)) in stops.iter_mut().zip(self.stops.iter().zip(to.stops)) {
203      *stop = (from.0.lerp(&to.0, t), from.1.lerp(&to.1, t));
204    }
205    Some(Gradient {
206      shape,
207      stops,
208      count: to.count,
209    })
210  }
211
212  /// This gradient with every color transparent.
213  fn clear(mut self) -> Gradient {
214    for (_, color) in &mut self.stops {
215      color.a = 0;
216    }
217    self
218  }
219}
220
221/// A shadow cast by a widget's box, as CSS's `box-shadow`: the box's
222/// shape moved by `x` and `y`, grown by `spread` and blurred over `blur`
223/// logical pixels. Unlike CSS's, it is drawn under the box too, so a
224/// translucent background shows it through, unless it is
225/// [`cut_out`](Shadow::cut_out).
226///
227/// ```
228/// use crabshell_ui::{Color, Shadow, Style};
229///
230/// const CARD: Style = Style::new()
231///     .background(Color::hex("#1e1e2e"))
232///     .radius(8.0)
233///     .shadow(Shadow::new(0.0, 4.0, 12.0, Color::rgba(0, 0, 0, 96)));
234/// ```
235#[derive(Clone, Copy, Debug, PartialEq)]
236pub struct Shadow {
237  /// How far right it is moved.
238  pub x: f32,
239  /// How far down it is moved.
240  pub y: f32,
241  /// How far its edge is blurred. 0 is a sharp edge.
242  pub blur: f32,
243  /// How much larger than the box it is on each side, or smaller when
244  /// negative.
245  pub spread: f32,
246  /// Its color.
247  pub color: Color,
248  /// Whether it is left out under the box, as under translucent glass.
249  pub cut_out: bool,
250}
251
252impl Shadow {
253  /// A shadow moved by `x` and `y` and blurred over `blur`, the box's
254  /// size.
255  pub const fn new(x: f32, y: f32, blur: f32, color: Color) -> Shadow {
256    Shadow {
257      x,
258      y,
259      blur,
260      spread: 0.0,
261      color,
262      cut_out: false,
263    }
264  }
265
266  /// The same shadow `spread` larger on each side.
267  pub const fn spread(self, spread: f32) -> Shadow {
268    Shadow { spread, ..self }
269  }
270
271  /// The same shadow left out under the box's own shape, so a translucent
272  /// background, such as glass the compositor blurs, shows what is
273  /// behind it instead of the shadow. CSS's `box-shadow` is always drawn
274  /// this way.
275  pub const fn cut_out(self) -> Shadow {
276    Shadow {
277      cut_out: true,
278      ..self
279    }
280  }
281
282  /// The area this shadow of `rect` covers: its shape, grown by one and a
283  /// half blurs, past which the blur fades below a 255th. A spread that
284  /// swallows the box still reaches between the edges it would have, so
285  /// a fade from it stays within its start and end. A shadow with a
286  /// number that isn't finite is never drawn, so it reaches only `rect`.
287  pub(crate) fn reach(&self, rect: Rect) -> Rect {
288    let grow = self.spread + 1.5 * self.blur.max(0.0);
289    if ![self.x, self.y, grow].iter().all(|n| n.is_finite()) {
290      return rect;
291    }
292    let (left, right) = (rect.x + self.x - grow, rect.right() + self.x + grow);
293    let (top, bottom) = (rect.y + self.y - grow, rect.bottom() + self.y + grow);
294    Rect::new(
295      left.min(right),
296      top.min(bottom),
297      (right - left).abs(),
298      (bottom - top).abs(),
299    )
300  }
301
302  /// `t` of the way to `to`.
303  fn lerp(&self, to: &Shadow, t: f32) -> Shadow {
304    Shadow {
305      x: self.x.lerp(&to.x, t),
306      y: self.y.lerp(&to.y, t),
307      blur: self.blur.lerp(&to.blur, t),
308      spread: self.spread.lerp(&to.spread, t),
309      color: self.color.lerp(&to.color, t),
310      // Not a number: it changes at once.
311      cut_out: to.cut_out,
312    }
313  }
314
315  /// This shadow, transparent.
316  fn clear(self) -> Shadow {
317    Shadow {
318      color: Color::rgba(self.color.r, self.color.g, self.color.b, 0),
319      ..self
320    }
321  }
322}
323
324/// Space between a widget's edge and its content, in logical pixels, on
325/// each side.
326#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
327pub struct Padding {
328  /// Above the content.
329  pub top: u32,
330  /// Right of the content.
331  pub right: u32,
332  /// Below the content.
333  pub bottom: u32,
334  /// Left of the content.
335  pub left: u32,
336}
337
338/// How a widget looks. Every field is optional, and an unset field falls back
339/// to the widget's default: no background, black text in
340/// [`Font::DEFAULT`], no padding, square corners, no border, fully opaque.
341///
342/// [`Text`](crate::Text) takes `color` and `font` from the nearest widget
343/// that sets them, starting with itself and going up. Nothing else inherits.
344///
345/// `font`, `padding`, `clip`, `width` and `height` change the layout; the
346/// other fields only change how the widget is painted.
347///
348/// `translate`, `scale`, `rotate` and `origin` move the widget and
349/// everything inside it where it is drawn and where the pointer finds it,
350/// without moving anything else: the layout, and popups anchored to the
351/// widget, keep its box where it was laid out.
352///
353/// ```
354/// use crabshell_ui::{Color, Font, Style};
355///
356/// const BAR: Style = Style::new()
357///     .background(Color::rgb(0x1e, 0x1e, 0x2e))
358///     .color(Color::hex("#cdd6f4"))
359///     .font(Font::new("Inter", 13.0));
360/// const WS: Style = Style::new().padding(2, 8).radius(4.0);
361/// const WS_FOCUSED: Style = WS.background(Color::rgb(0x45, 0x47, 0x5a));
362/// ```
363#[derive(Clone, Copy, Debug, Default, PartialEq)]
364pub struct Style {
365  /// Fills the widget's rectangle.
366  pub background: Option<Color>,
367  /// Colors blending across the widget's rectangle, over the background.
368  pub gradient: Option<Gradient>,
369  /// A shadow under the widget's box.
370  pub shadow: Option<Shadow>,
371  /// The text color, inherited by [`Text`](crate::Text).
372  pub color: Option<Color>,
373  /// The text font, inherited by [`Text`](crate::Text).
374  pub font: Option<Font>,
375  /// Space between the edge and the content.
376  pub padding: Option<Padding>,
377  /// The corner radius in logical pixels.
378  pub radius: Option<f32>,
379  /// A border inside the edge.
380  pub border: Option<Border>,
381  /// From 0.0 (invisible) to 1.0 (opaque), applied to the widget and
382  /// everything inside it.
383  pub opacity: Option<f32>,
384  /// Whether the children are cut to the inside of the border, following
385  /// the rounded corners, like CSS's `overflow: hidden`.
386  pub clip: Option<bool>,
387  /// The width in logical pixels, instead of what the content needs.
388  pub width: Option<f32>,
389  /// The height in logical pixels, instead of what the content needs.
390  pub height: Option<f32>,
391  /// How far the widget is moved, right and down, in logical pixels,
392  /// after it is scaled and turned.
393  pub translate: Option<Point>,
394  /// How many times its size it is drawn, around `origin`. 0 draws
395  /// nothing, and a negative scale also turns it upside down.
396  pub scale: Option<f32>,
397  /// How many degrees it is turned clockwise, around `origin`.
398  pub rotate: Option<f32>,
399  /// The point it is scaled and turned around, as fractions of its box:
400  /// `(0, 0)` is the top-left corner, and the default `(0.5, 0.5)` the
401  /// middle.
402  pub origin: Option<Point>,
403  /// How a change to the paint fields fades in: background, gradient,
404  /// shadow, color, border, radius, opacity and the transform. Other
405  /// fields change at once.
406  pub transition: Option<Transition>,
407}
408
409impl Style {
410  /// A style that sets nothing.
411  pub const fn new() -> Style {
412    Style {
413      background: None,
414      gradient: None,
415      shadow: None,
416      color: None,
417      font: None,
418      padding: None,
419      radius: None,
420      border: None,
421      opacity: None,
422      clip: None,
423      width: None,
424      height: None,
425      translate: None,
426      scale: None,
427      rotate: None,
428      origin: None,
429      transition: None,
430    }
431  }
432
433  /// Sets the background color.
434  pub const fn background(self, color: Color) -> Style {
435    Style {
436      background: Some(color),
437      ..self
438    }
439  }
440
441  /// Sets a gradient, drawn over the background.
442  pub const fn gradient(self, gradient: Gradient) -> Style {
443    Style {
444      gradient: Some(gradient),
445      ..self
446    }
447  }
448
449  /// Sets a shadow under the box.
450  pub const fn shadow(self, shadow: Shadow) -> Style {
451    Style {
452      shadow: Some(shadow),
453      ..self
454    }
455  }
456
457  /// Sets the text color.
458  pub const fn color(self, color: Color) -> Style {
459    Style {
460      color: Some(color),
461      ..self
462    }
463  }
464
465  /// Sets the text font.
466  pub const fn font(self, font: Font) -> Style {
467    Style {
468      font: Some(font),
469      ..self
470    }
471  }
472
473  /// Sets the padding: `vertical` above and below, `horizontal` left and
474  /// right, in logical pixels.
475  pub const fn padding(self, vertical: u32, horizontal: u32) -> Style {
476    Style {
477      padding: Some(Padding {
478        top: vertical,
479        right: horizontal,
480        bottom: vertical,
481        left: horizontal,
482      }),
483      ..self
484    }
485  }
486
487  /// Sets the padding above the content, in logical pixels, keeping the
488  /// other sides this style sets, or 0 where it sets none. As with every
489  /// field, [`merge`](Self::merge) takes the whole padding of the style on
490  /// top, so a hover style built from `Style::new()` sets the rest to 0.
491  ///
492  /// ```
493  /// use crabshell_ui::Style;
494  ///
495  /// const PILL: Style = Style::new().padding(2, 8).padding_left(12);
496  /// ```
497  pub const fn padding_top(self, top: u32) -> Style {
498    let mut padding = self.sides();
499    padding.top = top;
500    Style {
501      padding: Some(padding),
502      ..self
503    }
504  }
505
506  /// Sets the padding right of the content, keeping the other sides, as
507  /// [`padding_top`](Self::padding_top) does.
508  pub const fn padding_right(self, right: u32) -> Style {
509    let mut padding = self.sides();
510    padding.right = right;
511    Style {
512      padding: Some(padding),
513      ..self
514    }
515  }
516
517  /// Sets the padding below the content, keeping the other sides, as
518  /// [`padding_top`](Self::padding_top) does.
519  pub const fn padding_bottom(self, bottom: u32) -> Style {
520    let mut padding = self.sides();
521    padding.bottom = bottom;
522    Style {
523      padding: Some(padding),
524      ..self
525    }
526  }
527
528  /// Sets the padding left of the content, keeping the other sides, as
529  /// [`padding_top`](Self::padding_top) does.
530  pub const fn padding_left(self, left: u32) -> Style {
531    let mut padding = self.sides();
532    padding.left = left;
533    Style {
534      padding: Some(padding),
535      ..self
536    }
537  }
538
539  /// The padding this style sets, or 0 on every side.
540  const fn sides(self) -> Padding {
541    match self.padding {
542      Some(padding) => padding,
543      None => Padding {
544        top: 0,
545        right: 0,
546        bottom: 0,
547        left: 0,
548      },
549    }
550  }
551
552  /// Sets the corner radius in logical pixels.
553  pub const fn radius(self, radius: f32) -> Style {
554    Style {
555      radius: Some(radius),
556      ..self
557    }
558  }
559
560  /// Sets a border of `width` logical pixels.
561  pub const fn border(self, width: f32, color: Color) -> Style {
562    Style {
563      border: Some(Border { width, color }),
564      ..self
565    }
566  }
567
568  /// Sets the opacity, from 0.0 to 1.0.
569  pub const fn opacity(self, opacity: f32) -> Style {
570    Style {
571      opacity: Some(opacity),
572      ..self
573    }
574  }
575
576  /// Sets whether the children are cut to the inside of the border,
577  /// following the rounded corners. Nothing is cut by default.
578  pub const fn clip(self, clip: bool) -> Style {
579    Style {
580      clip: Some(clip),
581      ..self
582    }
583  }
584
585  /// Sets the width in logical pixels, instead of what the content needs.
586  pub const fn width(self, width: f32) -> Style {
587    Style {
588      width: Some(width),
589      ..self
590    }
591  }
592
593  /// Sets the height in logical pixels, instead of what the content
594  /// needs.
595  pub const fn height(self, height: f32) -> Style {
596    Style {
597      height: Some(height),
598      ..self
599    }
600  }
601
602  /// Moves the widget and everything inside it `x` logical pixels right
603  /// and `y` down where it is drawn, after it is scaled and turned. The
604  /// layout doesn't move.
605  ///
606  /// ```
607  /// use crabshell_ui::Style;
608  ///
609  /// const HIDDEN: Style = Style::new().translate(0.0, -40.0);
610  /// ```
611  pub const fn translate(self, x: f32, y: f32) -> Style {
612    Style {
613      translate: Some(Point::new(x, y)),
614      ..self
615    }
616  }
617
618  /// Draws the widget and everything inside it `scale` times its size,
619  /// around its [`origin`](Self::origin), without changing the layout.
620  /// 0 draws nothing and is never under the pointer; a negative scale
621  /// also turns it upside down. A number that isn't finite is ignored.
622  pub const fn scale(self, scale: f32) -> Style {
623    Style {
624      scale: Some(scale),
625      ..self
626    }
627  }
628
629  /// Turns the widget and everything inside it `degrees` clockwise,
630  /// around its [`origin`](Self::origin), without changing the layout. A
631  /// number that isn't finite is ignored.
632  pub const fn rotate(self, degrees: f32) -> Style {
633    Style {
634      rotate: Some(degrees),
635      ..self
636    }
637  }
638
639  /// Sets the point the widget is scaled and turned around, as fractions
640  /// of its box: `(0.0, 0.0)` is the top-left corner, `(1.0, 1.0)` the
641  /// bottom-right. The default is the middle, `(0.5, 0.5)`.
642  pub const fn origin(self, x: f32, y: f32) -> Style {
643    Style {
644      origin: Some(Point::new(x, y)),
645      ..self
646    }
647  }
648
649  /// Sets how a change to the paint fields fades in, as when a hover
650  /// style takes over: background, gradient, shadow, color, border,
651  /// radius, opacity, translate, scale, rotate and origin move over
652  /// `transition`. The style being changed to says how.
653  pub const fn transition(self, transition: Transition) -> Style {
654    Style {
655      transition: Some(transition),
656      ..self
657    }
658  }
659
660  /// This style with every field that `over` sets replaced by its value.
661  /// Buttons use it to lay their hover and pressed styles over the normal
662  /// one.
663  pub const fn merge(self, over: Style) -> Style {
664    Style {
665      background: or(over.background, self.background),
666      gradient: or(over.gradient, self.gradient),
667      shadow: or(over.shadow, self.shadow),
668      color: or(over.color, self.color),
669      font: or(over.font, self.font),
670      padding: or(over.padding, self.padding),
671      radius: or(over.radius, self.radius),
672      border: or(over.border, self.border),
673      opacity: or(over.opacity, self.opacity),
674      clip: or(over.clip, self.clip),
675      width: or(over.width, self.width),
676      height: or(over.height, self.height),
677      translate: or(over.translate, self.translate),
678      scale: or(over.scale, self.scale),
679      rotate: or(over.rotate, self.rotate),
680      origin: or(over.origin, self.origin),
681      transition: or(over.transition, self.transition),
682    }
683  }
684
685  /// Whether the children are cut to the widget's box.
686  pub fn clips(&self) -> bool {
687    self.clip == Some(true)
688  }
689
690  /// Whether the two styles lay out the same: same font, padding, size
691  /// and clipping, which changes how far the children's painting reaches.
692  pub(crate) fn same_layout(&self, other: &Style) -> bool {
693    self.font == other.font
694      && self.padding == other.padding
695      && self.width == other.width
696      && self.height == other.height
697      && self.clips() == other.clips()
698  }
699
700  /// Whether the two styles move the widget the same way where it is
701  /// drawn.
702  pub(crate) fn same_transform(&self, other: &Style) -> bool {
703    self.translate == other.translate
704      && self.scale == other.scale
705      && self.rotate == other.rotate
706      && self.origin == other.origin
707  }
708
709  /// Where this style moves a widget laid out at `rect`, from its own
710  /// coordinates into its parent's, or `None` when it doesn't. It is
711  /// scaled and turned around its origin, then translated. A number that
712  /// isn't finite counts as unset.
713  pub(crate) fn transform(&self, rect: Rect) -> Option<Affine> {
714    let finite = |value: f32, default: f32| if value.is_finite() { value } else { default };
715    let translate = self.translate.unwrap_or_default();
716    let (x, y) = (finite(translate.x, 0.0), finite(translate.y, 0.0));
717    let scale = finite(self.scale.unwrap_or(1.0), 1.0);
718    let degrees = finite(self.rotate.unwrap_or(0.0), 0.0).rem_euclid(360.0);
719    if x == 0.0 && y == 0.0 && scale == 1.0 && degrees == 0.0 {
720      return None;
721    }
722    // Quarter turns are exact, so a chevron turned over stays crisp.
723    let (sin, cos) = match degrees {
724      90.0 => (1.0, 0.0),
725      180.0 => (0.0, -1.0),
726      270.0 => (-1.0, 0.0),
727      _ => degrees.to_radians().sin_cos(),
728    };
729    let origin = self.origin.unwrap_or(Point::new(0.5, 0.5));
730    let ox = rect.x + finite(origin.x, 0.5) * rect.width;
731    let oy = rect.y + finite(origin.y, 0.5) * rect.height;
732    // A point p goes to origin + turn(scale(p - origin)) + translate.
733    let (sx, ky, kx, sy) = (cos * scale, sin * scale, -sin * scale, cos * scale);
734    // One too large for f32 maps every area to an empty one, so it is
735    // drawn nowhere and never hit.
736    Some(Affine {
737      sx,
738      ky,
739      kx,
740      sy,
741      tx: ox + x - (sx * ox + kx * oy),
742      ty: oy + y - (ky * ox + sy * oy),
743    })
744  }
745}
746
747/// `Option::or`, which isn't a `const fn` on stable Rust.
748const fn or<T: Copy>(first: Option<T>, second: Option<T>) -> Option<T> {
749  match first {
750    Some(value) => Some(value),
751    None => second,
752  }
753}
754
755impl Style {
756  /// The paint fields `t` of the way from `self` to `to`; every other
757  /// field is `to`'s. A field only one side sets fades from or to nothing:
758  /// a color from or to its transparent self, a border from or to 0 wide.
759  /// A text color only one sets changes at once, as the other comes
760  /// from an ancestor. Gradients of different shapes or numbers of colors
761  /// change at once. An unset translate is 0, scale 1, rotate 0 and
762  /// origin the middle.
763  pub(crate) fn lerp(&self, to: &Style, t: f32) -> Style {
764    let color = |from: Option<Color>, to: Option<Color>| match (from, to) {
765      (Some(from), Some(to)) => Some(from.lerp(&to, t)),
766      (None, Some(to)) => Some(Color::rgba(to.r, to.g, to.b, 0).lerp(&to, t)),
767      (Some(from), None) => Some(from.lerp(&Color::rgba(from.r, from.g, from.b, 0), t)),
768      (None, None) => None,
769    };
770    let border = match (self.border, to.border) {
771      (None, None) => None,
772      (from, to) => {
773        let from = from.or(to.map(|b| Border {
774          width: 0.0,
775          color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
776        }));
777        let to = to.or(from.map(|b| Border {
778          width: 0.0,
779          color: Color::rgba(b.color.r, b.color.g, b.color.b, 0),
780        }));
781        match (from, to) {
782          (Some(from), Some(to)) => Some(Border {
783            width: from.width.lerp(&to.width, t),
784            color: from.color.lerp(&to.color, t),
785          }),
786          _ => None,
787        }
788      }
789    };
790    let number = |from: Option<f32>, to: Option<f32>, none: f32| match (from, to) {
791      (None, None) => None,
792      (from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
793    };
794    let point = |from: Option<Point>, to: Option<Point>, none: Point| match (from, to) {
795      (None, None) => None,
796      (from, to) => Some(from.unwrap_or(none).lerp(&to.unwrap_or(none), t)),
797    };
798    let gradient = match (self.gradient, to.gradient) {
799      (Some(from), Some(end)) => from.lerp(&end, t).or(to.gradient),
800      (None, Some(end)) => end.clear().lerp(&end, t),
801      (Some(from), None) => from.lerp(&from.clear(), t),
802      (None, None) => None,
803    };
804    let shadow = match (self.shadow, to.shadow) {
805      (Some(from), Some(end)) => Some(from.lerp(&end, t)),
806      (None, Some(end)) => Some(end.clear().lerp(&end, t)),
807      (Some(from), None) => Some(from.lerp(&from.clear(), t)),
808      (None, None) => None,
809    };
810    Style {
811      background: color(self.background, to.background),
812      gradient,
813      shadow,
814      color: match (self.color, to.color) {
815        (Some(from), Some(to)) => Some(from.lerp(&to, t)),
816        _ => to.color,
817      },
818      border,
819      radius: number(self.radius, to.radius, 0.0),
820      opacity: number(self.opacity, to.opacity, 1.0),
821      translate: point(self.translate, to.translate, Point::default()),
822      scale: number(self.scale, to.scale, 1.0),
823      rotate: number(self.rotate, to.rotate, 0.0),
824      origin: point(self.origin, to.origin, Point::new(0.5, 0.5)),
825      ..*to
826    }
827  }
828}