Skip to main content

teksilo_core/styles/
spin_box_style.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for `SpinBox`. See `docs/styling-system.md`.
5//!
6//! `SpinBox` composes a themed `TextInputField` with an optional pair
7//! of step-up / step-down buttons (each themed via `IconButtonStyle`
8//! at construction time inside `StepButton`). The remaining picker-
9//! specific chrome is the *arrangement*: where the field sits, where
10//! the buttons sit, and the focus-aware border that frames them as
11//! one control.
12//!
13//! `make_body` receives the pre-built field plus optional step buttons
14//! and returns the visual core (the bordered surface holding both).
15//! The widget keeps responsibility for sizing policy (`width_chars` /
16//! `width_pixels`), keyboard / wheel handlers, and the `Role::SpinButton`
17//! accessibility.
18
19use std::rc::Rc;
20
21use crate::build_context::BuildContext;
22use crate::signal::Signal;
23use crate::widget_id::WidgetId;
24
25/// Step-button visibility / placement. Moved up from
26/// `teksilo_widgets::spin_box::ButtonLayout` so the trait config can carry
27/// it without forcing the recipe to depend on the widget crate.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum ButtonLayout {
30    /// Up arrow on top, down arrow below, stacked vertically to the
31    /// right of the field. Default, matches Qt and WinUI.
32    #[default]
33    Stacked,
34    /// No visible step buttons. Useful for read-only displays and
35    /// for SpinBoxes driven entirely by keyboard / wheel.
36    Hidden,
37}
38
39pub struct SpinBoxStyleConfig {
40    /// Pre-built `TextInputField` subtree (already padded to the
41    /// caller's policy).
42    pub field: WidgetId,
43    /// Pre-built up-step icon button. `None` when `layout == Hidden`.
44    pub step_up: Option<WidgetId>,
45    /// Pre-built down-step icon button. `None` when `layout == Hidden`.
46    pub step_down: Option<WidgetId>,
47    /// Layout selector — `Stacked` puts the buttons in a divided
48    /// vertical column to the trailing side of the field; `Hidden`
49    /// drops the divider and button column entirely.
50    pub layout: ButtonLayout,
51    /// Reactive focus signal — drives the border colour (focused →
52    /// accent, otherwise default).
53    pub is_focused: Signal<bool>,
54    /// Reactive disabled signal — the AND of the SpinBox's own `enabled`
55    /// prop and every ancestor's, via
56    /// `BuildContext::effective_enabled_signal`. Drives the inert grey
57    /// fill / outline. A SpinBox frames a `TextInputField` in *neutral*
58    /// roles (`SurfaceRole::Content`, `BorderRole::Default`), and the
59    /// disabled-role substitution in `ColorProp::resolve` only rewrites
60    /// the *accent* family — so unlike an accent-filled Button, this
61    /// control has to dim itself explicitly.
62    pub is_disabled: Signal<bool>,
63}
64
65pub trait SpinBoxStyle: 'static {
66    fn make_body(&self, cfg: &SpinBoxStyleConfig, ctx: &mut BuildContext) -> WidgetId;
67}
68
69pub type SharedSpinBoxStyle = Rc<dyn SpinBoxStyle>;