Skip to main content

frus_widgets/
cupertinocolors.rs

1//! The colours of Apple's platforms, and the colours that **adapt**: one name, up to eight
2//! values, picked by the brightness, the contrast and the level of the interface it is used
3//! in (milestone 627).
4
5use frus_core::Color;
6
7use crate::media::{Brightness, MediaQuery};
8use crate::Theme;
9
10/// **How high a piece of interface sits**: on the window's own surface, or raised above it —
11/// a sheet, a dialog, a popover.
12///
13/// An adaptive colour has a variant for each, and in a dark interface they differ: the
14/// background of a raised sheet is a little lighter than the page under it, which is how a
15/// dark interface shows depth without shadows. In a light one they are nearly all the same.
16///
17/// A subtree says which it is with [`around`](Self::around); the theme carries the answer
18/// ([`Theme::user_interface_level`]), and [`CupertinoDynamicColor::resolve`] reads it there.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
20pub enum CupertinoUserInterfaceLevelData {
21    /// The window's own surface. What everything is, until something says otherwise.
22    #[default]
23    Base,
24    /// Raised above it: a sheet, a dialog, a popover.
25    Elevated,
26}
27
28impl CupertinoUserInterfaceLevelData {
29    /// Wraps `child` in this level: the adaptive colours inside it resolve to this level's
30    /// variants.
31    pub fn around<Msg: 'static>(
32        self,
33        child: impl crate::Widget<Msg> + 'static,
34    ) -> crate::Themed<Msg> {
35        crate::Themed::tweak(move |t| t.user_interface_level = self, child)
36    }
37
38    /// The level `theme` says its subtree is at.
39    pub fn of(theme: &Theme) -> Self {
40        theme.user_interface_level
41    }
42}
43
44/// A colour that **adapts**: up to eight values for one name, one for each combination of a
45/// light or dark interface, normal or high contrast, and the base or the
46/// [elevated](CupertinoUserInterfaceLevelData::Elevated) level.
47///
48/// [`resolve`](Self::resolve) picks the one that applies where it is used;
49/// [`resolve_with`](Self::resolve_with) picks it for conditions given outright. Used as a
50/// plain [`Color`] without being resolved (`Color::from`), it is its light, normal-contrast,
51/// base value, as the reference's is.
52///
53/// Most colours vary along fewer than three of the axes, and two constructors say so:
54/// [`with_brightness`](Self::with_brightness) and
55/// [`with_brightness_and_contrast`](Self::with_brightness_and_contrast). The palette is
56/// [`CupertinoColors`].
57///
58/// ```
59/// use frus_widgets::{Brightness, Color, CupertinoDynamicColor, CupertinoUserInterfaceLevelData};
60///
61/// let ink = CupertinoDynamicColor::with_brightness(Color::rgb8(20, 20, 20), Color::rgb8(240, 240, 240));
62/// let dark = ink.resolve_with(Brightness::Dark, CupertinoUserInterfaceLevelData::Base, false);
63/// assert_eq!(dark, Color::rgb8(240, 240, 240));
64/// ```
65#[derive(Clone, Copy, Debug)]
66pub struct CupertinoDynamicColor {
67    /// Light, normal contrast, base level.
68    pub color: Color,
69    /// Dark, normal contrast, base level.
70    pub dark_color: Color,
71    /// Light, high contrast, base level.
72    pub high_contrast_color: Color,
73    /// Dark, high contrast, base level.
74    pub dark_high_contrast_color: Color,
75    /// Light, normal contrast, elevated.
76    pub elevated_color: Color,
77    /// Dark, normal contrast, elevated.
78    pub dark_elevated_color: Color,
79    /// Light, high contrast, elevated.
80    pub high_contrast_elevated_color: Color,
81    /// Dark, high contrast, elevated.
82    pub dark_high_contrast_elevated_color: Color,
83    /// A name to tell it by when it is printed; it takes no part in equality.
84    pub debug_label: Option<&'static str>,
85}
86
87impl CupertinoDynamicColor {
88    /// A colour that varies along all three axes: all eight values, given outright.
89    #[allow(clippy::too_many_arguments)]
90    pub const fn new(
91        color: Color,
92        dark_color: Color,
93        high_contrast_color: Color,
94        dark_high_contrast_color: Color,
95        elevated_color: Color,
96        dark_elevated_color: Color,
97        high_contrast_elevated_color: Color,
98        dark_high_contrast_elevated_color: Color,
99    ) -> Self {
100        Self {
101            color,
102            dark_color,
103            high_contrast_color,
104            dark_high_contrast_color,
105            elevated_color,
106            dark_elevated_color,
107            high_contrast_elevated_color,
108            dark_high_contrast_elevated_color,
109            debug_label: None,
110        }
111    }
112
113    /// A colour that varies with the brightness and the contrast, and is the same at either
114    /// level.
115    pub const fn with_brightness_and_contrast(
116        color: Color,
117        dark_color: Color,
118        high_contrast_color: Color,
119        dark_high_contrast_color: Color,
120    ) -> Self {
121        Self::new(
122            color,
123            dark_color,
124            high_contrast_color,
125            dark_high_contrast_color,
126            color,
127            dark_color,
128            high_contrast_color,
129            dark_high_contrast_color,
130        )
131    }
132
133    /// A colour that varies with the brightness alone.
134    pub const fn with_brightness(color: Color, dark_color: Color) -> Self {
135        Self::new(
136            color, dark_color, color, dark_color, color, dark_color, color, dark_color,
137        )
138    }
139
140    /// The same colour, printed as `label`.
141    #[must_use]
142    pub const fn labelled(mut self, label: &'static str) -> Self {
143        self.debug_label = Some(label);
144        self
145    }
146
147    /// **The value for these conditions**: a `brightness`, a `level`, and whether the
148    /// reader has asked for `high_contrast`.
149    pub fn resolve_with(
150        &self,
151        brightness: Brightness,
152        level: CupertinoUserInterfaceLevelData,
153        high_contrast: bool,
154    ) -> Color {
155        use CupertinoUserInterfaceLevelData::{Base, Elevated};
156        match (brightness, level, high_contrast) {
157            (Brightness::Light, Base, false) => self.color,
158            (Brightness::Light, Base, true) => self.high_contrast_color,
159            (Brightness::Light, Elevated, false) => self.elevated_color,
160            (Brightness::Light, Elevated, true) => self.high_contrast_elevated_color,
161            (Brightness::Dark, Base, false) => self.dark_color,
162            (Brightness::Dark, Base, true) => self.dark_high_contrast_color,
163            (Brightness::Dark, Elevated, false) => self.dark_elevated_color,
164            (Brightness::Dark, Elevated, true) => self.dark_high_contrast_elevated_color,
165        }
166    }
167
168    /// **The value where it is used**: in the brightness of the Apple-style theme under
169    /// `theme` ([`CupertinoTheme::brightness_of`](crate::CupertinoTheme::brightness_of)), at
170    /// the level `theme` says its subtree is at, and in high contrast if the reader has asked
171    /// for it ([`Accessibility::high_contrast`](crate::Accessibility::high_contrast), read
172    /// from the ambient [`MediaQuery`]).
173    ///
174    /// That brightness is the theme's, not the system's, unless the theme says otherwise: an
175    /// application that keeps a light theme on a dark system gets the light values, as the
176    /// reference's adaptive colours under its main theme do.
177    pub fn resolve(&self, theme: &Theme) -> Color {
178        self.resolve_with(
179            crate::CupertinoTheme::brightness_of(theme),
180            theme.user_interface_level,
181            MediaQuery::of().accessibility.high_contrast,
182        )
183    }
184}
185
186/// Equal when all eight values are: the label is only a name.
187impl PartialEq for CupertinoDynamicColor {
188    fn eq(&self, other: &Self) -> bool {
189        self.color == other.color
190            && self.dark_color == other.dark_color
191            && self.high_contrast_color == other.high_contrast_color
192            && self.dark_high_contrast_color == other.dark_high_contrast_color
193            && self.elevated_color == other.elevated_color
194            && self.dark_elevated_color == other.dark_elevated_color
195            && self.high_contrast_elevated_color == other.high_contrast_elevated_color
196            && self.dark_high_contrast_elevated_color == other.dark_high_contrast_elevated_color
197    }
198}
199
200/// A plain colour as an adaptive one: the same in every condition, so that a theme takes
201/// either where it takes a colour.
202impl From<Color> for CupertinoDynamicColor {
203    fn from(c: Color) -> Self {
204        Self::with_brightness(c, c)
205    }
206}
207
208/// An adaptive colour used without being resolved: its light, normal-contrast, base value.
209impl From<CupertinoDynamicColor> for Color {
210    fn from(c: CupertinoDynamicColor) -> Color {
211        c.color
212    }
213}
214
215/// **The palette of Apple's platforms**: their system colours, the colours of text, fills,
216/// backgrounds and separators, and a few plain ones — the reference's table, value for value.
217///
218/// Most are [adaptive](CupertinoDynamicColor), and are resolved where they are used:
219///
220/// ```
221/// use frus_widgets::{CupertinoColors, Theme};
222///
223/// let theme = Theme::dark();
224/// let background = CupertinoColors::SYSTEM_BACKGROUND.resolve(&theme);
225/// assert_eq!(background, CupertinoColors::BLACK);
226/// ```
227pub struct CupertinoColors;
228
229impl CupertinoColors {
230    /// The blue of active elements — a button's label, a selected tab. The same as [`SYSTEM_BLUE`](Self::SYSTEM_BLUE).
231    pub const ACTIVE_BLUE: CupertinoDynamicColor = Self::SYSTEM_BLUE;
232
233    /// The green of active accents — a switch that is on. The same as [`SYSTEM_GREEN`](Self::SYSTEM_GREEN).
234    pub const ACTIVE_GREEN: CupertinoDynamicColor = Self::SYSTEM_GREEN;
235
236    /// The orange of active accents. The same as [`SYSTEM_ORANGE`](Self::SYSTEM_ORANGE).
237    pub const ACTIVE_ORANGE: CupertinoDynamicColor = Self::SYSTEM_ORANGE;
238
239    /// Opaque white, for backgrounds, and for text against dark ones.
240    pub const WHITE: Color = Color::from_argb_u32(0xFFFFFFFF);
241
242    /// Opaque black, for text against light backgrounds.
243    pub const BLACK: Color = Color::from_argb_u32(0xFF000000);
244
245    /// Fully transparent: a colour that paints nothing.
246    pub const TRANSPARENT: Color = Color::from_argb_u32(0x00000000);
247
248    /// A light background fill, such as a chat bubble's.
249    pub const LIGHT_BACKGROUND_GRAY: Color = Color::from_argb_u32(0xFFE5E5EA);
250
251    /// A very light background fill, such as the gap between a table's groups of cells.
252    pub const EXTRA_LIGHT_BACKGROUND_GRAY: Color = Color::from_argb_u32(0xFFEFEFF4);
253
254    /// A very dark background fill, such as the gap between a dark table's groups of cells.
255    pub const DARK_BACKGROUND_GRAY: Color = Color::from_argb_u32(0xFF171717);
256
257    /// Controls that are not selected, such as a tab bar's other items.
258    pub const INACTIVE_GRAY: CupertinoDynamicColor = CupertinoDynamicColor::with_brightness(
259        Color::from_argb_u32(0xFF999999),
260        Color::from_argb_u32(0xFF757575),
261    )
262    .labelled("inactiveGray");
263
264    /// Destructive actions, such as a list's delete. The same as [`SYSTEM_RED`](Self::SYSTEM_RED).
265    pub const DESTRUCTIVE_RED: CupertinoDynamicColor = Self::SYSTEM_RED;
266
267    /// The system's blue.
268    pub const SYSTEM_BLUE: CupertinoDynamicColor =
269        CupertinoDynamicColor::with_brightness_and_contrast(
270            Color::rgba8(0, 122, 255, 255),
271            Color::rgba8(10, 132, 255, 255),
272            Color::rgba8(0, 64, 221, 255),
273            Color::rgba8(64, 156, 255, 255),
274        )
275        .labelled("systemBlue");
276
277    /// The system's green.
278    pub const SYSTEM_GREEN: CupertinoDynamicColor =
279        CupertinoDynamicColor::with_brightness_and_contrast(
280            Color::rgba8(52, 199, 89, 255),
281            Color::rgba8(48, 209, 88, 255),
282            Color::rgba8(36, 138, 61, 255),
283            Color::rgba8(48, 219, 91, 255),
284        )
285        .labelled("systemGreen");
286
287    /// The system's mint.
288    pub const SYSTEM_MINT: CupertinoDynamicColor =
289        CupertinoDynamicColor::with_brightness_and_contrast(
290            Color::rgba8(0, 199, 190, 255),
291            Color::rgba8(99, 230, 226, 255),
292            Color::rgba8(12, 129, 123, 255),
293            Color::rgba8(102, 212, 207, 255),
294        )
295        .labelled("systemMint");
296
297    /// The system's indigo.
298    pub const SYSTEM_INDIGO: CupertinoDynamicColor =
299        CupertinoDynamicColor::with_brightness_and_contrast(
300            Color::rgba8(88, 86, 214, 255),
301            Color::rgba8(94, 92, 230, 255),
302            Color::rgba8(54, 52, 163, 255),
303            Color::rgba8(125, 122, 255, 255),
304        )
305        .labelled("systemIndigo");
306
307    /// The system's orange.
308    pub const SYSTEM_ORANGE: CupertinoDynamicColor =
309        CupertinoDynamicColor::with_brightness_and_contrast(
310            Color::rgba8(255, 149, 0, 255),
311            Color::rgba8(255, 159, 10, 255),
312            Color::rgba8(201, 52, 0, 255),
313            Color::rgba8(255, 179, 64, 255),
314        )
315        .labelled("systemOrange");
316
317    /// The system's pink.
318    pub const SYSTEM_PINK: CupertinoDynamicColor =
319        CupertinoDynamicColor::with_brightness_and_contrast(
320            Color::rgba8(255, 45, 85, 255),
321            Color::rgba8(255, 55, 95, 255),
322            Color::rgba8(211, 15, 69, 255),
323            Color::rgba8(255, 100, 130, 255),
324        )
325        .labelled("systemPink");
326
327    /// The system's brown.
328    pub const SYSTEM_BROWN: CupertinoDynamicColor =
329        CupertinoDynamicColor::with_brightness_and_contrast(
330            Color::rgba8(162, 132, 94, 255),
331            Color::rgba8(172, 142, 104, 255),
332            Color::rgba8(127, 101, 69, 255),
333            Color::rgba8(181, 148, 105, 255),
334        )
335        .labelled("systemBrown");
336
337    /// The system's purple.
338    pub const SYSTEM_PURPLE: CupertinoDynamicColor =
339        CupertinoDynamicColor::with_brightness_and_contrast(
340            Color::rgba8(175, 82, 222, 255),
341            Color::rgba8(191, 90, 242, 255),
342            Color::rgba8(137, 68, 171, 255),
343            Color::rgba8(218, 143, 255, 255),
344        )
345        .labelled("systemPurple");
346
347    /// The system's red.
348    pub const SYSTEM_RED: CupertinoDynamicColor =
349        CupertinoDynamicColor::with_brightness_and_contrast(
350            Color::rgba8(255, 59, 48, 255),
351            Color::rgba8(255, 69, 58, 255),
352            Color::rgba8(215, 0, 21, 255),
353            Color::rgba8(255, 105, 97, 255),
354        )
355        .labelled("systemRed");
356
357    /// The system's teal.
358    pub const SYSTEM_TEAL: CupertinoDynamicColor =
359        CupertinoDynamicColor::with_brightness_and_contrast(
360            Color::rgba8(90, 200, 250, 255),
361            Color::rgba8(100, 210, 255, 255),
362            Color::rgba8(0, 113, 164, 255),
363            Color::rgba8(112, 215, 255, 255),
364        )
365        .labelled("systemTeal");
366
367    /// The system's cyan.
368    pub const SYSTEM_CYAN: CupertinoDynamicColor =
369        CupertinoDynamicColor::with_brightness_and_contrast(
370            Color::rgba8(50, 173, 230, 255),
371            Color::rgba8(100, 210, 255, 255),
372            Color::rgba8(0, 113, 164, 255),
373            Color::rgba8(112, 215, 255, 255),
374        )
375        .labelled("systemCyan");
376
377    /// The system's yellow.
378    pub const SYSTEM_YELLOW: CupertinoDynamicColor =
379        CupertinoDynamicColor::with_brightness_and_contrast(
380            Color::rgba8(255, 204, 0, 255),
381            Color::rgba8(255, 214, 10, 255),
382            Color::rgba8(160, 90, 0, 255),
383            Color::rgba8(255, 212, 38, 255),
384        )
385        .labelled("systemYellow");
386
387    /// The system's base grey.
388    pub const SYSTEM_GREY: CupertinoDynamicColor =
389        CupertinoDynamicColor::with_brightness_and_contrast(
390            Color::rgba8(142, 142, 147, 255),
391            Color::rgba8(142, 142, 147, 255),
392            Color::rgba8(108, 108, 112, 255),
393            Color::rgba8(174, 174, 178, 255),
394        )
395        .labelled("systemGrey");
396
397    /// The system's second grey, a step lighter in the light and darker in the dark.
398    pub const SYSTEM_GREY_2: CupertinoDynamicColor =
399        CupertinoDynamicColor::with_brightness_and_contrast(
400            Color::rgba8(174, 174, 178, 255),
401            Color::rgba8(99, 99, 102, 255),
402            Color::rgba8(142, 142, 147, 255),
403            Color::rgba8(124, 124, 128, 255),
404        )
405        .labelled("systemGrey2");
406
407    /// The system's third grey.
408    pub const SYSTEM_GREY_3: CupertinoDynamicColor =
409        CupertinoDynamicColor::with_brightness_and_contrast(
410            Color::rgba8(199, 199, 204, 255),
411            Color::rgba8(72, 72, 74, 255),
412            Color::rgba8(174, 174, 178, 255),
413            Color::rgba8(84, 84, 86, 255),
414        )
415        .labelled("systemGrey3");
416
417    /// The system's fourth grey.
418    pub const SYSTEM_GREY_4: CupertinoDynamicColor =
419        CupertinoDynamicColor::with_brightness_and_contrast(
420            Color::rgba8(209, 209, 214, 255),
421            Color::rgba8(58, 58, 60, 255),
422            Color::rgba8(188, 188, 192, 255),
423            Color::rgba8(68, 68, 70, 255),
424        )
425        .labelled("systemGrey4");
426
427    /// The system's fifth grey.
428    pub const SYSTEM_GREY_5: CupertinoDynamicColor =
429        CupertinoDynamicColor::with_brightness_and_contrast(
430            Color::rgba8(229, 229, 234, 255),
431            Color::rgba8(44, 44, 46, 255),
432            Color::rgba8(216, 216, 220, 255),
433            Color::rgba8(54, 54, 56, 255),
434        )
435        .labelled("systemGrey5");
436
437    /// The system's sixth grey, the nearest to the background.
438    pub const SYSTEM_GREY_6: CupertinoDynamicColor =
439        CupertinoDynamicColor::with_brightness_and_contrast(
440            Color::rgba8(242, 242, 247, 255),
441            Color::rgba8(28, 28, 30, 255),
442            Color::rgba8(235, 235, 240, 255),
443            Color::rgba8(36, 36, 38, 255),
444        )
445        .labelled("systemGrey6");
446
447    /// Text that holds the primary content.
448    pub const LABEL: CupertinoDynamicColor = CupertinoDynamicColor::new(
449        Color::rgba8(0, 0, 0, 255),
450        Color::rgba8(255, 255, 255, 255),
451        Color::rgba8(0, 0, 0, 255),
452        Color::rgba8(255, 255, 255, 255),
453        Color::rgba8(0, 0, 0, 255),
454        Color::rgba8(255, 255, 255, 255),
455        Color::rgba8(0, 0, 0, 255),
456        Color::rgba8(255, 255, 255, 255),
457    )
458    .labelled("label");
459
460    /// Text that holds secondary content.
461    pub const SECONDARY_LABEL: CupertinoDynamicColor = CupertinoDynamicColor::new(
462        Color::rgba8(60, 60, 67, 153),
463        Color::rgba8(235, 235, 245, 153),
464        Color::rgba8(60, 60, 67, 173),
465        Color::rgba8(235, 235, 245, 173),
466        Color::rgba8(60, 60, 67, 153),
467        Color::rgba8(235, 235, 245, 153),
468        Color::rgba8(60, 60, 67, 173),
469        Color::rgba8(235, 235, 245, 173),
470    )
471    .labelled("secondaryLabel");
472
473    /// Text that holds tertiary content.
474    pub const TERTIARY_LABEL: CupertinoDynamicColor = CupertinoDynamicColor::new(
475        Color::rgba8(60, 60, 67, 76),
476        Color::rgba8(235, 235, 245, 76),
477        Color::rgba8(60, 60, 67, 96),
478        Color::rgba8(235, 235, 245, 96),
479        Color::rgba8(60, 60, 67, 76),
480        Color::rgba8(235, 235, 245, 76),
481        Color::rgba8(60, 60, 67, 96),
482        Color::rgba8(235, 235, 245, 96),
483    )
484    .labelled("tertiaryLabel");
485
486    /// Text that holds quaternary content.
487    pub const QUATERNARY_LABEL: CupertinoDynamicColor = CupertinoDynamicColor::new(
488        Color::rgba8(60, 60, 67, 45),
489        Color::rgba8(235, 235, 245, 40),
490        Color::rgba8(60, 60, 67, 66),
491        Color::rgba8(235, 235, 245, 61),
492        Color::rgba8(60, 60, 67, 45),
493        Color::rgba8(235, 235, 245, 40),
494        Color::rgba8(60, 60, 67, 66),
495        Color::rgba8(235, 235, 245, 61),
496    )
497    .labelled("quaternaryLabel");
498
499    /// An overlay fill for thin and small shapes.
500    pub const SYSTEM_FILL: CupertinoDynamicColor = CupertinoDynamicColor::new(
501        Color::rgba8(120, 120, 128, 51),
502        Color::rgba8(120, 120, 128, 91),
503        Color::rgba8(120, 120, 128, 71),
504        Color::rgba8(120, 120, 128, 112),
505        Color::rgba8(120, 120, 128, 51),
506        Color::rgba8(120, 120, 128, 91),
507        Color::rgba8(120, 120, 128, 71),
508        Color::rgba8(120, 120, 128, 112),
509    )
510    .labelled("systemFill");
511
512    /// An overlay fill for medium-sized shapes.
513    pub const SECONDARY_SYSTEM_FILL: CupertinoDynamicColor = CupertinoDynamicColor::new(
514        Color::rgba8(120, 120, 128, 40),
515        Color::rgba8(120, 120, 128, 81),
516        Color::rgba8(120, 120, 128, 61),
517        Color::rgba8(120, 120, 128, 102),
518        Color::rgba8(120, 120, 128, 40),
519        Color::rgba8(120, 120, 128, 81),
520        Color::rgba8(120, 120, 128, 61),
521        Color::rgba8(120, 120, 128, 102),
522    )
523    .labelled("secondarySystemFill");
524
525    /// An overlay fill for large shapes.
526    pub const TERTIARY_SYSTEM_FILL: CupertinoDynamicColor = CupertinoDynamicColor::new(
527        Color::rgba8(118, 118, 128, 30),
528        Color::rgba8(118, 118, 128, 61),
529        Color::rgba8(118, 118, 128, 51),
530        Color::rgba8(118, 118, 128, 81),
531        Color::rgba8(118, 118, 128, 30),
532        Color::rgba8(118, 118, 128, 61),
533        Color::rgba8(118, 118, 128, 51),
534        Color::rgba8(118, 118, 128, 81),
535    )
536    .labelled("tertiarySystemFill");
537
538    /// An overlay fill for large areas holding complex content.
539    pub const QUATERNARY_SYSTEM_FILL: CupertinoDynamicColor = CupertinoDynamicColor::new(
540        Color::rgba8(116, 116, 128, 20),
541        Color::rgba8(118, 118, 128, 45),
542        Color::rgba8(116, 116, 128, 40),
543        Color::rgba8(118, 118, 128, 66),
544        Color::rgba8(116, 116, 128, 20),
545        Color::rgba8(118, 118, 128, 45),
546        Color::rgba8(116, 116, 128, 40),
547        Color::rgba8(118, 118, 128, 66),
548    )
549    .labelled("quaternarySystemFill");
550
551    /// A field's placeholder text.
552    pub const PLACEHOLDER_TEXT: CupertinoDynamicColor = CupertinoDynamicColor::new(
553        Color::rgba8(60, 60, 67, 76),
554        Color::rgba8(235, 235, 245, 76),
555        Color::rgba8(60, 60, 67, 96),
556        Color::rgba8(235, 235, 245, 96),
557        Color::rgba8(60, 60, 67, 76),
558        Color::rgba8(235, 235, 245, 76),
559        Color::rgba8(60, 60, 67, 96),
560        Color::rgba8(235, 235, 245, 96),
561    )
562    .labelled("placeholderText");
563
564    /// The interface's main background.
565    pub const SYSTEM_BACKGROUND: CupertinoDynamicColor = CupertinoDynamicColor::new(
566        Color::rgba8(255, 255, 255, 255),
567        Color::rgba8(0, 0, 0, 255),
568        Color::rgba8(255, 255, 255, 255),
569        Color::rgba8(0, 0, 0, 255),
570        Color::rgba8(255, 255, 255, 255),
571        Color::rgba8(28, 28, 30, 255),
572        Color::rgba8(255, 255, 255, 255),
573        Color::rgba8(36, 36, 38, 255),
574    )
575    .labelled("systemBackground");
576
577    /// Content layered on the main background.
578    pub const SECONDARY_SYSTEM_BACKGROUND: CupertinoDynamicColor = CupertinoDynamicColor::new(
579        Color::rgba8(242, 242, 247, 255),
580        Color::rgba8(28, 28, 30, 255),
581        Color::rgba8(235, 235, 240, 255),
582        Color::rgba8(36, 36, 38, 255),
583        Color::rgba8(242, 242, 247, 255),
584        Color::rgba8(44, 44, 46, 255),
585        Color::rgba8(235, 235, 240, 255),
586        Color::rgba8(54, 54, 56, 255),
587    )
588    .labelled("secondarySystemBackground");
589
590    /// Content layered on a secondary background.
591    pub const TERTIARY_SYSTEM_BACKGROUND: CupertinoDynamicColor = CupertinoDynamicColor::new(
592        Color::rgba8(255, 255, 255, 255),
593        Color::rgba8(44, 44, 46, 255),
594        Color::rgba8(255, 255, 255, 255),
595        Color::rgba8(54, 54, 56, 255),
596        Color::rgba8(255, 255, 255, 255),
597        Color::rgba8(58, 58, 60, 255),
598        Color::rgba8(255, 255, 255, 255),
599        Color::rgba8(68, 68, 70, 255),
600    )
601    .labelled("tertiarySystemBackground");
602
603    /// The main background of a grouped interface.
604    pub const SYSTEM_GROUPED_BACKGROUND: CupertinoDynamicColor = CupertinoDynamicColor::new(
605        Color::rgba8(242, 242, 247, 255),
606        Color::rgba8(0, 0, 0, 255),
607        Color::rgba8(235, 235, 240, 255),
608        Color::rgba8(0, 0, 0, 255),
609        Color::rgba8(242, 242, 247, 255),
610        Color::rgba8(28, 28, 30, 255),
611        Color::rgba8(235, 235, 240, 255),
612        Color::rgba8(36, 36, 38, 255),
613    )
614    .labelled("systemGroupedBackground");
615
616    /// Content layered on a grouped interface's main background.
617    pub const SECONDARY_SYSTEM_GROUPED_BACKGROUND: CupertinoDynamicColor =
618        CupertinoDynamicColor::new(
619            Color::rgba8(255, 255, 255, 255),
620            Color::rgba8(28, 28, 30, 255),
621            Color::rgba8(255, 255, 255, 255),
622            Color::rgba8(36, 36, 38, 255),
623            Color::rgba8(255, 255, 255, 255),
624            Color::rgba8(44, 44, 46, 255),
625            Color::rgba8(255, 255, 255, 255),
626            Color::rgba8(54, 54, 56, 255),
627        )
628        .labelled("secondarySystemGroupedBackground");
629
630    /// Content layered on a grouped interface's secondary background.
631    pub const TERTIARY_SYSTEM_GROUPED_BACKGROUND: CupertinoDynamicColor =
632        CupertinoDynamicColor::new(
633            Color::rgba8(242, 242, 247, 255),
634            Color::rgba8(44, 44, 46, 255),
635            Color::rgba8(235, 235, 240, 255),
636            Color::rgba8(54, 54, 56, 255),
637            Color::rgba8(242, 242, 247, 255),
638            Color::rgba8(58, 58, 60, 255),
639            Color::rgba8(235, 235, 240, 255),
640            Color::rgba8(68, 68, 70, 255),
641        )
642        .labelled("tertiarySystemGroupedBackground");
643
644    /// Thin borders and dividers that let some of what is under them show.
645    pub const SEPARATOR: CupertinoDynamicColor = CupertinoDynamicColor::new(
646        Color::rgba8(60, 60, 67, 73),
647        Color::rgba8(84, 84, 88, 153),
648        Color::rgba8(60, 60, 67, 94),
649        Color::rgba8(84, 84, 88, 173),
650        Color::rgba8(60, 60, 67, 73),
651        Color::rgba8(210, 210, 210, 153),
652        Color::rgba8(60, 60, 67, 94),
653        Color::rgba8(84, 84, 88, 173),
654    )
655    .labelled("separator");
656
657    /// Borders and dividers that hide what is under them.
658    pub const OPAQUE_SEPARATOR: CupertinoDynamicColor = CupertinoDynamicColor::new(
659        Color::rgba8(198, 198, 200, 255),
660        Color::rgba8(56, 56, 58, 255),
661        Color::rgba8(198, 198, 200, 255),
662        Color::rgba8(56, 56, 58, 255),
663        Color::rgba8(198, 198, 200, 255),
664        Color::rgba8(56, 56, 58, 255),
665        Color::rgba8(198, 198, 200, 255),
666        Color::rgba8(56, 56, 58, 255),
667    )
668    .labelled("opaqueSeparator");
669
670    /// Links.
671    pub const LINK: CupertinoDynamicColor = CupertinoDynamicColor::new(
672        Color::rgba8(0, 122, 255, 255),
673        Color::rgba8(9, 132, 255, 255),
674        Color::rgba8(0, 122, 255, 255),
675        Color::rgba8(9, 132, 255, 255),
676        Color::rgba8(0, 122, 255, 255),
677        Color::rgba8(9, 132, 255, 255),
678        Color::rgba8(0, 122, 255, 255),
679        Color::rgba8(9, 132, 255, 255),
680    )
681    .labelled("link");
682}
683
684#[cfg(test)]
685mod tests {
686    use super::{CupertinoColors as C, CupertinoDynamicColor, CupertinoUserInterfaceLevelData};
687    use crate::media::{Accessibility, Brightness, MediaQuery};
688    use crate::{build_ui, Container, Flex, Runtime, Theme, ThemeBuilder, Widget};
689    use frus_core::{Color, Primitive, Size};
690    use CupertinoUserInterfaceLevelData::{Base, Elevated};
691
692    #[derive(Clone, Debug, PartialEq)]
693    enum Msg {}
694
695    /// **Each of the eight conditions picks its own value** — the reference's table of
696    /// brightness × level × contrast, cell by cell.
697    #[test]
698    fn each_condition_picks_its_value() {
699        let v: Vec<Color> = (0..8).map(|i| Color::rgb8(i * 10, 0, 0)).collect();
700        let c = CupertinoDynamicColor::new(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
701        let light = Brightness::Light;
702        let dark = Brightness::Dark;
703        assert_eq!(c.resolve_with(light, Base, false), v[0]);
704        assert_eq!(c.resolve_with(dark, Base, false), v[1]);
705        assert_eq!(c.resolve_with(light, Base, true), v[2]);
706        assert_eq!(c.resolve_with(dark, Base, true), v[3]);
707        assert_eq!(c.resolve_with(light, Elevated, false), v[4]);
708        assert_eq!(c.resolve_with(dark, Elevated, false), v[5]);
709        assert_eq!(c.resolve_with(light, Elevated, true), v[6]);
710        assert_eq!(c.resolve_with(dark, Elevated, true), v[7]);
711        // Unresolved, it is its first value.
712        assert_eq!(Color::from(c), v[0]);
713    }
714
715    /// **The shorter constructors fill the axes they do not vary along**, as the reference's
716    /// do.
717    #[test]
718    fn the_shorter_constructors_fill_the_other_axes() {
719        let [a, b, c, d] = [1, 2, 3, 4].map(|i| Color::rgb8(i, 0, 0));
720        assert_eq!(
721            CupertinoDynamicColor::with_brightness_and_contrast(a, b, c, d),
722            CupertinoDynamicColor::new(a, b, c, d, a, b, c, d)
723        );
724        assert_eq!(
725            CupertinoDynamicColor::with_brightness(a, b),
726            CupertinoDynamicColor::new(a, b, a, b, a, b, a, b)
727        );
728        // The label is a name, not a value.
729        assert_eq!(
730            CupertinoDynamicColor::with_brightness(a, b).labelled("ink"),
731            CupertinoDynamicColor::with_brightness(a, b)
732        );
733    }
734
735    /// **The palette is the reference's**, checked at the values that are easiest to get
736    /// wrong: a system colour's four, a label's translucency, a background that lightens
737    /// when raised in the dark, and the aliases.
738    #[test]
739    fn the_palette_is_the_reference_s() {
740        let rgba = Color::rgba8;
741        assert_eq!(
742            C::SYSTEM_BLUE,
743            CupertinoDynamicColor::with_brightness_and_contrast(
744                rgba(0, 122, 255, 255),
745                rgba(10, 132, 255, 255),
746                rgba(0, 64, 221, 255),
747                rgba(64, 156, 255, 255),
748            )
749        );
750        assert_eq!(C::SYSTEM_BLUE.debug_label, Some("systemBlue"));
751        assert_eq!(C::ACTIVE_BLUE, C::SYSTEM_BLUE);
752        assert_eq!(C::ACTIVE_GREEN, C::SYSTEM_GREEN);
753        assert_eq!(C::ACTIVE_ORANGE, C::SYSTEM_ORANGE);
754        assert_eq!(C::DESTRUCTIVE_RED, C::SYSTEM_RED);
755        assert_eq!(C::SECONDARY_LABEL.color, rgba(60, 60, 67, 153));
756        assert_eq!(
757            C::SECONDARY_LABEL.dark_high_contrast_color,
758            rgba(235, 235, 245, 173)
759        );
760        assert_eq!(C::SYSTEM_BACKGROUND.dark_color, rgba(0, 0, 0, 255));
761        assert_eq!(
762            C::SYSTEM_BACKGROUND.dark_elevated_color,
763            rgba(28, 28, 30, 255)
764        );
765        assert_eq!(
766            C::SYSTEM_BACKGROUND.dark_high_contrast_elevated_color,
767            rgba(36, 36, 38, 255)
768        );
769        assert_eq!(C::SEPARATOR.dark_elevated_color, rgba(210, 210, 210, 153));
770        assert_eq!(C::SYSTEM_GREY_6.color, rgba(242, 242, 247, 255));
771        assert_eq!(
772            C::INACTIVE_GRAY.dark_color,
773            Color::from_argb_u32(0xFF757575)
774        );
775        assert_eq!(
776            C::EXTRA_LIGHT_BACKGROUND_GRAY,
777            Color::from_argb_u32(0xFFEFEFF4)
778        );
779    }
780
781    /// **Where it is used decides**: the theme's brightness, the level the theme carries, and
782    /// the reader's high-contrast setting from the ambient surface.
783    #[test]
784    fn resolve_reads_the_theme_and_the_surface() {
785        let bg = C::SYSTEM_BACKGROUND;
786        assert_eq!(bg.resolve(&Theme::light()), bg.color);
787        assert_eq!(bg.resolve(&Theme::dark()), bg.dark_color);
788        let mut raised = Theme::dark();
789        raised.user_interface_level = Elevated;
790        assert_eq!(bg.resolve(&raised), bg.dark_elevated_color);
791        let mut surface = MediaQuery::new(Size::new(100.0, 100.0));
792        surface.accessibility = Accessibility {
793            high_contrast: true,
794            ..Accessibility::NONE
795        };
796        surface.scope(|| {
797            assert_eq!(bg.resolve(&raised), bg.dark_high_contrast_elevated_color);
798            assert_eq!(
799                C::LABEL.resolve(&Theme::light()),
800                C::LABEL.high_contrast_color
801            );
802        });
803    }
804
805    /// **A theme starts at the base level, keeps it through a fade, and a subtree raised with
806    /// `around` resolves its colours raised** — while its sibling, outside, does not.
807    #[test]
808    fn a_raised_subtree_resolves_raised() {
809        assert_eq!(Theme::default().user_interface_level, Base);
810        let mut raised = Theme::dark();
811        raised.user_interface_level = Elevated;
812        assert_eq!(raised.lerp(&raised, 0.3).user_interface_level, Elevated);
813        assert_eq!(
814            Theme::dark().lerp(&raised, 0.7).user_interface_level,
815            Elevated
816        );
817        assert_eq!(Theme::dark().lerp(&raised, 0.3).user_interface_level, Base);
818
819        let swatch = || {
820            ThemeBuilder::new(|t: &Theme| {
821                Container::new()
822                    .width(50.0)
823                    .height(50.0)
824                    .color(C::SYSTEM_BACKGROUND.resolve(t))
825            })
826        };
827        let tree = Flex::<Msg>::column()
828            .width(200.0)
829            .height(200.0)
830            .child(swatch())
831            .child(Elevated.around(swatch()));
832        let painted: Vec<Color> = build_ui(
833            &tree as &dyn Widget<Msg>,
834            Size::new(200.0, 200.0),
835            &Runtime::default(),
836            &Theme::dark(),
837        )
838        .scene()
839        .primitives()
840        .iter()
841        .filter_map(|p| match p {
842            Primitive::Rect { color, blur, .. } if *blur == 0.0 => Some(*color),
843            _ => None,
844        })
845        .collect();
846        assert_eq!(
847            painted,
848            vec![
849                C::SYSTEM_BACKGROUND.dark_color,
850                C::SYSTEM_BACKGROUND.dark_elevated_color
851            ]
852        );
853        assert_eq!(CupertinoUserInterfaceLevelData::of(&raised), Elevated);
854    }
855}