Skip to main content

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>;