Skip to main content

teksilo_core/styles/
splitter_style.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for `Splitter`. See `docs/styling-system.md`.
5//!
6//! The `Splitter` widget owns all input handling (drag, keyboard,
7//! collapse, accessibility) and delegates only the *paint* of each
8//! divider handle to a [`SplitterStyle`] impl. Layout-affecting
9//! dimensions (gutter thickness, min pane sizes, keyboard step, snap
10//! offset) live on the `SplitterModel`, not here — the styling-system
11//! rule is that anything consumed by `place_children` must be resolved
12//! before layout, whereas a style body is built lazily and paints at
13//! paint time.
14//!
15//! The IntUI default ([`crate::styles`]'s `RecipeSplitterStyle`, in
16//! `teksilo-widgets`) reproduces the thin-line-with-hover-dwell-focus
17//! look out of the box; apps install a different look per-call via
18//! `Splitter::style(...)` or theme-wide via `theme.style_slots.splitter`.
19
20use std::rc::Rc;
21
22use teksilo_tokens::Orientation;
23
24use crate::build_context::BuildContext;
25use crate::focus::FocusOrigin;
26use crate::signal::Signal;
27use crate::widget_id::WidgetId;
28
29/// Reactive inputs handed to a [`SplitterStyle`] when it builds one
30/// divider handle's visual body. Every field a default impl repaints on
31/// is a `Signal`, so the body re-renders without a rebuild.
32#[derive(Clone, Debug)]
33pub struct SplitterStyleConfig {
34    /// Orientation of the parent `Splitter`. Note: a *horizontal*
35    /// splitter (panes side-by-side) draws a *vertical* handle bar, and
36    /// vice versa — the recipe accounts for this when painting the line.
37    pub orientation: Orientation,
38    pub is_hovered: Signal<bool>,
39    /// `true` while the user is drag-pressing this handle.
40    pub is_dragging: Signal<bool>,
41    pub is_disabled: Signal<bool>,
42    /// `Some(FocusOrigin::Keyboard)` while the handle has keyboard
43    /// focus; the IntUI default uses this to gate the full-strength
44    /// focus indicator. `Some(Pointer)` and `None` fall back to the
45    /// hover-dwell ramp.
46    pub focus_origin: Signal<Option<FocusOrigin>>,
47    /// Hover-dwell progress `0.0..=1.0` driving the focus-indicator
48    /// fade-in (animated by the handle; the style maps it to alpha).
49    pub hover_progress: Signal<f32>,
50}
51
52/// The visual contract for a `Splitter` divider handle.
53///
54/// `make_handle` returns the `WidgetId` of a leaf (or subtree) that
55/// paints the divider chrome. The host `Splitter` sizes it to the
56/// model's gutter thickness × the cross axis and routes all input
57/// itself — the body is purely presentational and should mark itself
58/// hidden from the accessibility tree (the splitter handle owns the
59/// `Role::Splitter` node).
60pub trait SplitterStyle: 'static {
61    fn make_handle(&self, cfg: &SplitterStyleConfig, ctx: &mut BuildContext) -> WidgetId;
62}
63
64pub type SharedSplitterStyle = Rc<dyn SplitterStyle>;