teksilo_core/styles/standard_item_style.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for `StandardListItem` / `StandardTreeItem`.
5//! See `docs/styling-system.md`.
6
7use std::rc::Rc;
8
9use teksilo_tokens::TextRole;
10
11use crate::build_context::BuildContext;
12use crate::signal::Signal;
13use crate::widget_id::WidgetId;
14
15#[derive(Clone, Debug)]
16pub struct StandardItemStyleConfig {
17 /// Pre-built row content — typically an HStack of `[checkbox?]
18 /// [leading?] [center?] [label / VStack { label, subtitle row }]
19 /// [Spacer] [trailing?]` composed by the host
20 /// `StandardListItem` / `StandardTreeItem`. The style is
21 /// responsible for the chrome (selection background, corner
22 /// radius, padding) but not for row-internal layout — the
23 /// per-slot composition is StandardItem-specific (subtitle has
24 /// its own sub-row, the checkbox carries `labels_hidden` AT
25 /// metadata, etc.) and would force every custom style to
26 /// reimplement it.
27 pub content: WidgetId,
28 pub is_selected: Signal<bool>,
29 pub is_hovered: Signal<bool>,
30 pub is_pressed: Signal<bool>,
31 pub is_focused: Signal<bool>,
32 /// Input-modality "focus-visible": `true` after keyboard input. A focus ring
33 /// should render only when this and `is_focused` are both true, so a mouse
34 /// click selects without a ring while keyboard navigation reveals one.
35 pub is_focus_visible: Signal<bool>,
36 pub is_disabled: Signal<bool>,
37 /// Whether the host window is currently active (`focused AND not occluded`).
38 /// Composed with `is_focused` so a selected row shows the vivid `Selected`
39 /// surface only while the view holds keyboard focus **and** the window is
40 /// active; otherwise it falls back to the muted `SelectedInactive` — the
41 /// same desaturation a view-focus loss produces (macOS "unemphasized"
42 /// selection serves both states). Populated from
43 /// `BuildContext::window_active_signal`.
44 pub is_window_active: Signal<bool>,
45}
46
47pub trait StandardItemStyle: 'static {
48 fn make_body(&self, cfg: &StandardItemStyleConfig, ctx: &mut BuildContext) -> WidgetId;
49
50 /// The text role a row's label takes while the row is **emphasised**
51 /// — selected, with its view focused and its window active. `None`
52 /// (the default) keeps the row's own mapping, `TextRole::Primary`.
53 ///
54 /// The row builds its label before a style ever sees it, so a style
55 /// that fills the selection with a saturated colour cannot recolour
56 /// the text on top of it. Design languages whose selected row is a
57 /// solid accent fill with a light label — macOS's
58 /// `alternateSelectedControlTextColor` is the canonical case — return
59 /// [`TextRole::OnAccent`] here; ones whose selection is a pale wash
60 /// (IntUI, Fluent) leave it `None` and the label keeps its contrast
61 /// against the wash.
62 ///
63 /// Same shape as
64 /// [`ButtonStyle::label_text_role`](crate::styles::ButtonStyle::label_text_role),
65 /// and defaulted for the same reason: an existing style needs no
66 /// change.
67 fn selected_label_role(&self) -> Option<TextRole> {
68 None
69 }
70}
71
72pub type SharedStandardItemStyle = Rc<dyn StandardItemStyle>;