zest_theme/style.rs
1//! Catalog/Status/Appearance pattern, modeled on libcosmic but trimmed
2//! for embedded touch:
3//!
4//! - **No hover state.** Touch screens have no pointer-over phase.
5//! - **No animations / transitions.** Allocate-and-draw budget on an
6//! ESP32 doesn't have room for them.
7//!
8//! A catalog is a function on the [`Theme`](crate::Theme): given a
9//! widget *class* (variant — `Standard`, `Suggested`, `Destructive`,
10//! …) and a *status* (`Active`, `Focused`, `Pressed`, `Disabled`), return a
11//! resolved [`ButtonAppearance`] the widget can paint directly.
12//! Widgets never reach into the theme's `Component`/`Container` fields;
13//! they call the catalog method.
14
15use embedded_graphics::pixelcolor::PixelColor;
16
17/// Interaction state of a stateful widget. Single shared enum across
18/// all interactive widgets — keeps the catalog dispatch table small
19/// and the embedded code path predictable.
20#[derive(Copy, Clone, Debug, PartialEq, Eq)]
21pub enum Status {
22 /// Resting state — not pressed, not disabled.
23 Active,
24 /// Focused through non-touch traversal.
25 Focused,
26 /// Currently being touched.
27 Pressed,
28 /// No-op state (e.g. button has no `on_press`).
29 Disabled,
30}
31
32/// Semantic variant of a button. The catalog maps a class to one of
33/// the theme's interactive components (button / accent / destructive
34/// / …) without leaking those field names to widget code.
35#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
36pub enum ButtonClass {
37 /// Default filled button. Maps to `theme.button`.
38 #[default]
39 Standard,
40 /// Primary call-to-action. Maps to `theme.accent`.
41 Suggested,
42 /// Destructive action (delete, cancel-with-loss). Maps to `theme.destructive`.
43 Destructive,
44 /// Positive confirmation. Maps to `theme.success`.
45 Success,
46 /// Cautionary action. Maps to `theme.warning`.
47 Warning,
48 /// No fill, no border — text-only. Maps to `theme.text_button`.
49 Text,
50 /// Icon-only target. Maps to `theme.icon_button`.
51 Icon,
52}
53
54/// Resolved button style — what the widget actually paints.
55///
56/// `Option`s encode "no fill" / "no border" so a `ButtonClass::Text`
57/// can return `background = None, border = None` without us inventing
58/// a sentinel transparent color.
59#[derive(Copy, Clone, Debug)]
60pub struct ButtonAppearance<C: PixelColor> {
61 /// Fill color. `None` = transparent.
62 pub background: Option<C>,
63 /// Border color. `None` = no border drawn.
64 pub border: Option<C>,
65 /// Foreground color (label text).
66 pub text: C,
67}
68
69/// Catalog for buttons. Implemented by [`crate::Theme`].
70pub trait ButtonCatalog<C: PixelColor> {
71 /// Resolve `(class, status)` to the colors the widget should paint.
72 fn button(&self, class: ButtonClass, status: Status) -> ButtonAppearance<C>;
73}