🔒 Repository is read-only – file editing is disabled.

luna/crates/libluna-core/src/style.rs main

863 lines Raw ← Back
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
//! Tokeny wyglądu — odpowiednik „design tokens” w libadwaita.
//!
//! Zamiast arkuszy CSS libLuna zaczyna od typowanych tokenów: odstępów, promieni,
//! skali typograficznej i palety (jasna/ciemna). Warstwa `libluna-widgets` buduje
//! z nich konkretne widgety, dzięki czemu wygląd jest spójny i tani w zmianie.
//!
//! Wartości palety są zbliżone do libadwaita (GNOME 45+) i mogą wymagać kalibracji
//! podczas wdrażania.

use crate::color::Color;

/// Odstępy w pikselach logicznych (siateczka 3/6/12/18/24 — jak w GNOME).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Spacing {
    pub xs: f64,
    pub sm: f64,
    pub md: f64,
    pub lg: f64,
    pub xl: f64,
}

impl Spacing {
    pub const fn standard() -> Self {
        Self {
            xs: 3.0,
            sm: 6.0,
            md: 12.0,
            lg: 18.0,
            xl: 24.0,
        }
    }
}

impl Default for Spacing {
    fn default() -> Self {
        Self::standard()
    }
}

/// Promienie zaokrągleń (piksele logiczne).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Radius {
    pub small: f64,
    pub medium: f64,
    pub large: f64,
    /// Wartość „pigułki” — na tyle duża, by przyciski były w pełni zaokrąglone.
    pub pill: f64,
}

impl Radius {
    pub const fn standard() -> Self {
        Self {
            small: 6.0,
            medium: 9.0,
            large: 12.0,
            pill: 999.0,
        }
    }
}

impl Default for Radius {
    fn default() -> Self {
        Self::standard()
    }
}

/// Grubości czcionek.
pub mod weight {
    pub const NORMAL: u16 = 400;
    pub const MEDIUM: u16 = 500;
    pub const BOLD: u16 = 700;
}

/// Pojedynczy styl czcionki.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FontStyle {
    /// Rozmiar (piksele logiczne).
    pub size: f64,
    /// Grubość (patrz [`weight`]).
    pub weight: u16,
    /// Wysokość linii (piksele logiczne).
    pub line_height: f64,
    /// Czy używać kroju o stałej szerokości.
    pub monospace: bool,
}

impl FontStyle {
    const fn new(size: f64, weight: u16, line_height: f64, monospace: bool) -> Self {
        Self {
            size,
            weight,
            line_height,
            monospace,
        }
    }
}

/// Skala typograficzna.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Typography {
    /// Duży tytuł okna / strony.
    pub title: FontStyle,
    /// Nagłówek sekcji.
    pub heading: FontStyle,
    /// Zwykły tekst treści.
    pub body: FontStyle,
    /// Drobny tekst pomocniczy.
    pub caption: FontStyle,
}

impl Typography {
    pub const fn standard() -> Self {
        Self {
            title: FontStyle::new(20.0, weight::BOLD, 26.0, false),
            heading: FontStyle::new(15.0, weight::BOLD, 20.0, false),
            body: FontStyle::new(14.0, weight::NORMAL, 20.0, false),
            caption: FontStyle::new(12.0, weight::NORMAL, 16.0, false),
        }
    }
}

impl Default for Typography {
    fn default() -> Self {
        Self::standard()
    }
}

/// Paleta kolorów dla jednego wariantu (jasny lub ciemny).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ColorScheme {
    /// Tło okna aplikacji.
    pub window_bg: Color,
    /// Tło głównego widoku / listy.
    pub view_bg: Color,
    /// Tło karty / powierzchni podniesionej.
    pub card_bg: Color,
    /// Tło Headera (HeaderBar).
    pub headerbar_bg: Color,
    /// Tło panelu bocznego.
    pub sidebar_bg: Color,
    /// Kolor tekstu podstawowego.
    pub text: Color,
    /// Kolor tekstu drugorzędnego / wyciszonego.
    pub dim_text: Color,
    /// Kolor akcentu (np. sugerowany przycisk, przełącznik wł.).
    pub accent: Color,
    /// Tekst na akcencie.
    pub accent_fg: Color,
    /// Kolor obramowań i separatorów.
    pub border: Color,
    /// Delikatna nakładka (hover/pressed/„shade”).
    pub shade: Color,
    pub error: Color,
    pub warning: Color,
    pub success: Color,
}

impl ColorScheme {
    /// Wariant jasny (przybliżenie libadwaita light).
    pub const fn light() -> Self {
        Self {
            window_bg: Color::from_rgba8(0xfa, 0xfa, 0xfa, 0xff),
            view_bg: Color::from_rgba8(0xff, 0xff, 0xff, 0xff),
            card_bg: Color::from_rgba8(0xff, 0xff, 0xff, 0xff),
            headerbar_bg: Color::from_rgba8(0xf2, 0xf2, 0xf2, 0xff),
            sidebar_bg: Color::from_rgba8(0xeb, 0xeb, 0xeb, 0xff),
            text: Color::from_rgba8(0x2e, 0x2e, 0x2e, 0xff),
            dim_text: Color::rgba(0.0, 0.0, 0.0, 0.55),
            accent: Color::from_rgba8(0x35, 0x84, 0xe4, 0xff),
            accent_fg: Color::from_rgba8(0xff, 0xff, 0xff, 0xff),
            border: Color::rgba(0.0, 0.0, 0.0, 0.10),
            shade: Color::rgba(0.0, 0.0, 0.0, 0.06),
            error: Color::from_rgba8(0xc0, 0x1c, 0x28, 0xff),
            warning: Color::from_rgba8(0xe5, 0xa5, 0x0a, 0xff),
            success: Color::from_rgba8(0x2e, 0xc2, 0x7e, 0xff),
        }
    }

    /// Wariant ciemny (przybliżenie libadwaita dark).
    pub const fn dark() -> Self {
        Self {
            window_bg: Color::from_rgba8(0x24, 0x24, 0x24, 0xff),
            view_bg: Color::from_rgba8(0x1e, 0x1e, 0x1e, 0xff),
            card_bg: Color::from_rgba8(0x30, 0x30, 0x30, 0xff),
            headerbar_bg: Color::from_rgba8(0x30, 0x30, 0x30, 0xff),
            sidebar_bg: Color::from_rgba8(0x2b, 0x2b, 0x2b, 0xff),
            text: Color::from_rgba8(0xff, 0xff, 0xff, 0xff),
            dim_text: Color::rgba(1.0, 1.0, 1.0, 0.55),
            accent: Color::from_rgba8(0x35, 0x84, 0xe4, 0xff),
            accent_fg: Color::from_rgba8(0xff, 0xff, 0xff, 0xff),
            border: Color::rgba(1.0, 1.0, 1.0, 0.12),
            shade: Color::rgba(1.0, 1.0, 1.0, 0.08),
            error: Color::from_rgba8(0xff, 0x7b, 0x63, 0xff),
            warning: Color::from_rgba8(0xf8, 0xe4, 0x5c, 0xff),
            success: Color::from_rgba8(0x8f, 0xf0, 0xa4, 0xff),
        }
    }
}

/// Czy motyw jest jasny czy ciemny.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Appearance {
    Light,
    Dark,
}

/// Geometria paska tytułu ([`HeaderBar`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HeaderBarMetrics {
    /// Wysokość paska (piksele logiczne).
    pub height: f64,
    /// Bok kwadratowego obszaru kontrolki okna (`— □ ✕`).
    pub control: f64,
    /// Odstęp między kontrolkami okna (i od krawędzi paska).
    pub control_gap: f64,
}

/// Geometria przycisku.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ButtonMetrics {
    /// Domyślna wysokość.
    pub height: f64,
    /// Wewnętrzny padding poziomy (z obu stron etykiety).
    pub pad_x: f64,
    /// Rozpiętość wskaźnika rozwijania (chevron).
    pub chevron: f64,
    /// Odstęp etykieta–chevron.
    pub chevron_gap: f64,
}

/// Geometria przełącznika.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SwitchMetrics {
    pub width: f64,
    pub height: f64,
}

/// Geometria suwaka.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SliderMetrics {
    /// Promień uchwytu (także tolerancja trafienia w poziomie).
    pub knob_r: f64,
    /// Pionowa tolerancja trafienia ponad/pod torem.
    pub hit_pad: f64,
    /// Domyślna wysokość toru.
    pub track_height: f64,
}

/// Geometria listy rozwijanej ([`DropDown`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DropDownMetrics {
    pub height: f64,
    /// Wewnętrzny padding poziomy.
    pub pad_x: f64,
    /// Rozpiętość wskaźnika rozwijania.
    pub chevron: f64,
    /// Odstęp etykieta–chevron.
    pub chevron_gap: f64,
    /// Minimalna szerokość kontrolki.
    pub min_width: f64,
}

/// Geometria popoveru ([`Menu`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MenuMetrics {
    /// Wysokość pozycji akcji.
    pub item_h: f64,
    /// Wysokość nagłówka sekcji.
    pub section_h: f64,
    /// Wysokość separatora.
    pub separator_h: f64,
    /// Padding pionowy panelu (góra/dół).
    pub pad_v: f64,
    /// Padding poziomy zawartości.
    pub pad_h: f64,
    /// Minimalny odstęp między etykietą a skrótem.
    pub label_gap: f64,
    /// Minimalna szerokość panelu.
    pub min_width: f64,
    /// Margines od krawędzi okna przy umieszczaniu.
    pub margin: f64,
    /// Odstęp kotwica–panel.
    pub anchor_gap: f64,
}

/// Geometria menu nawigacji ([`NavList`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NavMetrics {
    /// Wysokość pozycji.
    pub item_h: f64,
    /// Wysokość nagłówka sekcji.
    pub section_h: f64,
    /// Padding górny/dolny listy.
    pub pad_v: f64,
    /// Padding poziomy pozycji.
    pub pad_x: f64,
}

/// Geometria przełącznika segmentowego ([`ViewSwitcher`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SegmentedMetrics {
    /// O ile tło-„szyna” wystaje poza segmenty.
    pub track_pad: f64,
}

/// Geometria paska zakładek ([`TabBar`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TabsMetrics {
    /// Wysokość paska zakładek.
    pub height: f64,
    /// Minimalna szerokość zakładki.
    pub min_width: f64,
    /// Maksymalna szerokość zakładki.
    pub max_width: f64,
    /// Wewnętrzny padding poziomy etykiety.
    pub pad_x: f64,
    /// Odstęp między zakładkami.
    pub gap: f64,
    /// Bok obszaru przycisku zamknięcia zakładki.
    pub close: f64,
}

/// Geometria dolnego panelu ([`BottomSheet`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BottomSheetMetrics {
    /// Domyślna wysokość panelu.
    pub height: f64,
    /// Szerokość „grabbera”.
    pub handle_width: f64,
    /// Wysokość „grabbera”.
    pub handle_height: f64,
    /// Wysokość nagłówka panelu.
    pub header: f64,
    /// Promień górnych narożników panelu.
    pub radius: f64,
    /// Krycie przygaszającego tła (0..1).
    pub scrim: f64,
    /// Przesunięcie w dół (px) po którym puszczenie zamyka panel.
    pub dismiss: f64,
}

/// Geometria karuzeli stron ([`Pager`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PagerMetrics {
    /// Średnica kropki-wskaźnika.
    pub dot: f64,
    /// Odstęp między kropkami.
    pub gap: f64,
    /// Odstęp kropek od dolnej krawędzi.
    pub pad: f64,
}

/// Geometria paska ścieżki / okruszków ([`PathBar`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PathBarMetrics {
    /// Wysokość paska.
    pub height: f64,
    /// Wewnętrzny padding poziomy pozycji.
    pub pad_x: f64,
    /// Odstęp wokół separatora.
    pub gap: f64,
}

/// Geometria pola tekstowego ([`SearchEntry`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EntryMetrics {
    /// Wysokość pola.
    pub height: f64,
    /// Wewnętrzny padding poziomy.
    pub pad_x: f64,
    /// Bok ikony (lupka, „×”).
    pub icon: f64,
}

/// Geometria widoku elementów ([`ItemView`](crate::style)) — siatka i lista.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ItemViewMetrics {
    /// Szerokość kafelka (tryb siatki).
    pub tile_w: f64,
    /// Wysokość kafelka (tryb siatki).
    pub tile_h: f64,
    /// Odstęp między kafelkami.
    pub gap: f64,
    /// Wysokość wiersza (tryb listy).
    pub row_h: f64,
    /// Rozmiar ikony elementu.
    pub icon: f64,
}

/// Geometria paska przewijania ([`ScrollView`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScrollViewMetrics {
    /// Grubość paska.
    pub thickness: f64,
    /// Minimalna długość uchwytu („kciuka”).
    pub min_thumb: f64,
    /// Odstęp paska od krawędzi obszaru.
    pub margin: f64,
}

/// Geometria krokowca ([`Stepper`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StepperMetrics {
    /// Wysokość kontrolki.
    pub height: f64,
    /// Domyślna szerokość.
    pub width: f64,
    /// Bok przycisku `−`/`+`.
    pub button: f64,
}

/// Geometria pola wyboru ([`CheckButton`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CheckMetrics {
    /// Bok kwadratu (checkbox).
    pub size: f64,
    /// Odstęp kwadratu od etykiety.
    pub gap: f64,
}

/// Geometria grupy opcji ([`RadioGroup`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RadioMetrics {
    /// Średnica kółka.
    pub dot: f64,
    /// Odstęp kółka od etykiety.
    pub gap: f64,
    /// Wysokość wiersza opcji.
    pub row_h: f64,
}

/// Geometria karty ([`Card`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CardMetrics {
    /// Wysokość nagłówka karty.
    pub header: f64,
    /// Padding wewnętrzny (nagłówek).
    pub pad: f64,
}

/// Geometria wiersza akcji ([`ActionRow`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ActionRowMetrics {
    /// Padding poziomy (lewy i prawy).
    pub pad_x: f64,
    /// Odstęp między tytułem a podtytułem.
    pub gap: f64,
}

/// Geometria powiadomienia ([`Toast`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ToastMetrics {
    /// Odstęp od dolnej krawędzi okna.
    pub margin_bottom: f64,
    /// Padding poziomy (wewnątrz).
    pub pad_x: f64,
    /// Odstęp między tekstem a akcją.
    pub gap: f64,
    /// Wysokość powiadomienia.
    pub height: f64,
}

/// Geometria strony stanu ([`StatusPage`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StatusPageMetrics {
    /// Bok ikony.
    pub icon: f64,
    /// Odstęp pionowy między elementami.
    pub gap: f64,
    /// Szerokość przycisku akcji.
    pub button_w: f64,
    /// Wysokość przycisku akcji.
    pub button_h: f64,
}

/// Geometria wskaźnika zajętości ([`Spinner`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpinnerMetrics {
    /// Domyślny bok.
    pub size: f64,
    /// Grubość łuku.
    pub thickness: f64,
    /// Długość łuku jako ułamek pełnego okręgu (0..1).
    pub sweep: f64,
}

/// Geometria dymka podpowiedzi ([`Tooltip`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TooltipMetrics {
    /// Padding poziomy.
    pub pad_x: f64,
    /// Padding pionowy.
    pub pad_y: f64,
    /// Promień zaokrąglenia.
    pub radius: f64,
    /// Odstęp od kotwicy (nad kotwicą).
    pub gap: f64,
}

/// Geometria banera ([`Banner`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BannerMetrics {
    /// Padding wewnętrzny.
    pub pad: f64,
    /// Odstęp między elementami.
    pub gap: f64,
    /// Bok ikony.
    pub icon: f64,
    /// Minimalna wysokość.
    pub min_height: f64,
    /// Wysokość przycisku akcji.
    pub button_h: f64,
}

/// Geometria awatara ([`Avatar`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AvatarMetrics {
    /// Domyślna średnica.
    pub size: f64,
}

/// Geometria etykiety skrótu ([`ShortcutLabel`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ShortcutMetrics {
    /// Padding poziomy klawisza.
    pub pad_x: f64,
    /// Padding pionowy klawisza.
    pub pad_y: f64,
    /// Promień klawisza.
    pub radius: f64,
    /// Odstęp między klawiszami (na separator `+`).
    pub gap: f64,
}

/// Geometria przycisku dzielonego ([`SplitButton`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SplitMetrics {
    /// Szerokość części ze strzałką.
    pub arrow_w: f64,
    /// Grubość separatora między częściami.
    pub divider: f64,
}

/// Geometria grupy preferencji ([`PreferencesGroup`](crate::style)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GroupMetrics {
    /// Wysokość nagłówka grupy (tytuł nad ramką).
    pub header: f64,
    /// Odstęp między grupami.
    pub gap: f64,
    /// Wcięcie poziome zawartości grupy.
    pub pad_x: f64,
}

/// Geometria pasków (postęp, poziom).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BarMetrics {
    /// Grubość paska.
    pub thickness: f64,
    /// Promień zaokrąglenia.
    pub radius: f64,
}

/// Geometria kontenerów (Box, Grid, Paned, CenterBox…).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ContainerMetrics {
    /// Domyślny odstęp między dziećmi (Box, Grid).
    pub gap: f64,
    /// Grubość separatora paneli (Paned).
    pub separator: f64,
    /// Dodatkowa strefa trafienia wokół separatora (po każdej stronie).
    pub hit_pad: f64,
}

/// Geometria kontrolek („component metrics”) — rozmiary i paddingi w pikselach logicznych.
///
/// Widgety nie mają już własnych stałych rozmiarów: czytają je z motywu, dzięki czemu
/// zmiana tokenów (np. w przyszłym, instalowanym stylu) steruje także geometrią kontrolek —
/// nie tylko kolorami.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Metrics {
    pub headerbar: HeaderBarMetrics,
    pub button: ButtonMetrics,
    pub switch: SwitchMetrics,
    pub slider: SliderMetrics,
    pub dropdown: DropDownMetrics,
    pub menu: MenuMetrics,
    pub nav: NavMetrics,
    pub segmented: SegmentedMetrics,
    pub tabs: TabsMetrics,
    pub sheet: BottomSheetMetrics,
    pub pager: PagerMetrics,
    pub pathbar: PathBarMetrics,
    pub entry: EntryMetrics,
    pub itemview: ItemViewMetrics,
    pub scrollview: ScrollViewMetrics,
    pub stepper: StepperMetrics,
    pub check: CheckMetrics,
    pub radio: RadioMetrics,
    pub card: CardMetrics,
    pub row: ActionRowMetrics,
    pub toast: ToastMetrics,
    pub status: StatusPageMetrics,
    pub spinner: SpinnerMetrics,
    pub tooltip: TooltipMetrics,
    pub banner: BannerMetrics,
    pub avatar: AvatarMetrics,
    pub shortcut: ShortcutMetrics,
    pub split: SplitMetrics,
    pub group: GroupMetrics,
    pub bar: BarMetrics,
    pub container: ContainerMetrics,
}

impl Metrics {
    pub const fn standard() -> Self {
        Self {
            headerbar: HeaderBarMetrics {
                height: 38.0,
                control: 30.0,
                control_gap: 2.0,
            },
            button: ButtonMetrics {
                height: 34.0,
                pad_x: 14.0,
                chevron: 9.0,
                chevron_gap: 8.0,
            },
            switch: SwitchMetrics {
                width: 44.0,
                height: 24.0,
            },
            slider: SliderMetrics {
                knob_r: 9.0,
                hit_pad: 12.0,
                track_height: 6.0,
            },
            dropdown: DropDownMetrics {
                height: 34.0,
                pad_x: 12.0,
                chevron: 9.0,
                chevron_gap: 10.0,
                min_width: 140.0,
            },
            menu: MenuMetrics {
                item_h: 34.0,
                section_h: 26.0,
                separator_h: 9.0,
                pad_v: 6.0,
                pad_h: 10.0,
                label_gap: 20.0,
                min_width: 200.0,
                margin: 6.0,
                anchor_gap: 4.0,
            },
            nav: NavMetrics {
                item_h: 34.0,
                section_h: 30.0,
                pad_v: 6.0,
                pad_x: 12.0,
            },
            segmented: SegmentedMetrics { track_pad: 2.0 },
            tabs: TabsMetrics {
                height: 34.0,
                min_width: 120.0,
                max_width: 200.0,
                pad_x: 12.0,
                gap: 2.0,
                close: 16.0,
            },
            sheet: BottomSheetMetrics {
                height: 320.0,
                handle_width: 36.0,
                handle_height: 4.0,
                header: 44.0,
                radius: 12.0,
                scrim: 0.4,
                dismiss: 96.0,
            },
            pager: PagerMetrics {
                dot: 8.0,
                gap: 8.0,
                pad: 12.0,
            },
            pathbar: PathBarMetrics {
                height: 34.0,
                pad_x: 10.0,
                gap: 6.0,
            },
            entry: EntryMetrics {
                height: 34.0,
                pad_x: 12.0,
                icon: 16.0,
            },
            itemview: ItemViewMetrics {
                tile_w: 100.0,
                tile_h: 76.0,
                gap: 12.0,
                row_h: 44.0,
                icon: 28.0,
            },
            scrollview: ScrollViewMetrics {
                thickness: 6.0,
                min_thumb: 24.0,
                margin: 2.0,
            },
            stepper: StepperMetrics {
                height: 34.0,
                width: 120.0,
                button: 34.0,
            },
            check: CheckMetrics {
                size: 20.0,
                gap: 10.0,
            },
            radio: RadioMetrics {
                dot: 20.0,
                gap: 10.0,
                row_h: 30.0,
            },
            card: CardMetrics {
                header: 40.0,
                pad: 12.0,
            },
            row: ActionRowMetrics {
                pad_x: 12.0,
                gap: 8.0,
            },
            toast: ToastMetrics {
                margin_bottom: 40.0,
                pad_x: 16.0,
                gap: 16.0,
                height: 40.0,
            },
            status: StatusPageMetrics {
                icon: 64.0,
                gap: 12.0,
                button_w: 160.0,
                button_h: 34.0,
            },
            spinner: SpinnerMetrics {
                size: 24.0,
                thickness: 2.5,
                sweep: 0.75,
            },
            tooltip: TooltipMetrics {
                pad_x: 10.0,
                pad_y: 6.0,
                radius: 8.0,
                gap: 8.0,
            },
            banner: BannerMetrics {
                pad: 12.0,
                gap: 12.0,
                icon: 20.0,
                min_height: 56.0,
                button_h: 30.0,
            },
            avatar: AvatarMetrics { size: 40.0 },
            shortcut: ShortcutMetrics {
                pad_x: 6.0,
                pad_y: 3.0,
                radius: 5.0,
                gap: 4.0,
            },
            split: SplitMetrics {
                arrow_w: 30.0,
                divider: 1.0,
            },
            group: GroupMetrics {
                header: 30.0,
                gap: 18.0,
                pad_x: 12.0,
            },
            bar: BarMetrics {
                thickness: 8.0,
                radius: 4.0,
            },
            container: ContainerMetrics {
                gap: 8.0,
                separator: 1.0,
                hit_pad: 4.0,
            },
        }
    }
}

impl Default for Metrics {
    fn default() -> Self {
        Self::standard()
    }
}

/// Kompletny motyw: odstępy, promienie, typografia, geometria kontrolek i paleta.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Theme {
    pub spacing: Spacing,
    pub radius: Radius,
    pub metrics: Metrics,
    pub typography: Typography,
    pub colors: ColorScheme,
    pub appearance: Appearance,
}

impl Theme {
    pub const fn light() -> Self {
        Self {
            spacing: Spacing::standard(),
            radius: Radius::standard(),
            metrics: Metrics::standard(),
            typography: Typography::standard(),
            colors: ColorScheme::light(),
            appearance: Appearance::Light,
        }
    }

    pub const fn dark() -> Self {
        Self {
            spacing: Spacing::standard(),
            radius: Radius::standard(),
            metrics: Metrics::standard(),
            typography: Typography::standard(),
            colors: ColorScheme::dark(),
            appearance: Appearance::Dark,
        }
    }

    /// Ten sam motyw z podmienionym akcentem oraz kolorem treści na akcencie.
    pub fn accented(mut self, accent: Color, foreground: Color) -> Self {
        self.colors.accent = accent;
        self.colors.accent_fg = foreground;
        self
    }
}

impl Default for Theme {
    fn default() -> Self {
        Theme::light()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn light_and_dark_differ() {
        assert_ne!(
            Theme::light().colors.window_bg,
            Theme::dark().colors.window_bg
        );
        assert_eq!(Theme::light().appearance, Appearance::Light);
        assert_eq!(Theme::dark().appearance, Appearance::Dark);
    }

    #[test]
    fn standard_tokens_are_sane() {
        let s = Spacing::standard();
        assert!(s.xs < s.sm && s.sm < s.md && s.md < s.lg && s.lg < s.xl);
        let t = Typography::standard();
        assert!(t.title.size > t.heading.size && t.heading.size > t.body.size);
    }
}