Skip to main content

teksilo_core/styles/
tab_style.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for `TabBar`. See `docs/styling-system.md`.
5//!
6//! `TabBar` has two themable surfaces, so the trait carries two
7//! methods: [`TabStyle::make_body`] wraps a single tab header (accent
8//! indicator, focus ring, …) and [`TabStyle::make_bar`] wraps the
9//! whole strip (backdrop fill, content-pane separator, drag-reorder
10//! drop indicator). A custom `impl TabStyle` provides both.
11
12use std::rc::Rc;
13
14use serde::{Deserialize, Serialize};
15
16use crate::build_context::BuildContext;
17use crate::color_prop::ColorProp;
18use crate::signal::Signal;
19use crate::widget_id::WidgetId;
20
21#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
22pub enum TabBarOrientation {
23    #[default]
24    Horizontal,
25    Vertical,
26}
27
28/// Which edge of a tab the active-tab highlight indicator hugs.
29///
30/// The position is expressed relative to the content pane, so it stays
31/// meaningful in both orientations and under RTL:
32///
33/// - [`OuterEdge`](TabIndicatorPosition::OuterEdge) (default) — the edge
34///   pointing *away* from the content: **top** for a horizontal bar,
35///   **leading** for a vertical bar. The IntUI / browser-tab look.
36/// - [`InnerEdge`](TabIndicatorPosition::InnerEdge) — the edge pointing
37///   *toward* the content: **bottom** for a horizontal bar (the indicator
38///   sits below the label), **trailing** for a vertical bar.
39#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
40pub enum TabIndicatorPosition {
41    #[default]
42    OuterEdge,
43    InnerEdge,
44}
45
46#[derive(Clone, Debug)]
47pub struct TabStyleConfig {
48    pub label: WidgetId,
49    pub leading: Option<WidgetId>,
50    pub trailing: Option<WidgetId>,
51    pub is_active: Signal<bool>,
52    pub is_hovered: Signal<bool>,
53    pub is_focused: Signal<bool>,
54    pub is_disabled: Signal<bool>,
55    pub orientation: TabBarOrientation,
56    /// Which edge the active-tab highlight indicator hugs. See
57    /// [`TabIndicatorPosition`].
58    pub indicator_position: TabIndicatorPosition,
59}
60
61/// Inputs for the bar-level chrome — the surface the headers row,
62/// pinned strip, scroll arrows, and slots all sit on.
63#[derive(Clone, Debug)]
64pub struct TabBarChromeConfig {
65    /// The composed bar content (leading slot → pinned strip → scroll
66    /// arrows → headers row → overflow dropdown → trailing slot). The
67    /// style wraps this and returns the bar's root.
68    pub content: WidgetId,
69    pub orientation: TabBarOrientation,
70    /// Whether to draw the 1 px separator along the content-pane edge
71    /// (bottom for horizontal bars, trailing for vertical bars).
72    pub show_separator: bool,
73    /// Optional app-set backdrop fill spanning the whole bar. `None`
74    /// leaves the bar transparent.
75    pub surface_role: Option<ColorProp>,
76    /// Drag-reorder drop-indicator position — layout-axis offset in
77    /// bar-local coords. `None` when no reorder drag is in progress
78    /// over the bar.
79    pub drop_indicator: Signal<Option<f32>>,
80}
81
82pub trait TabStyle: 'static {
83    /// Chrome for a single tab header.
84    fn make_body(&self, cfg: &TabStyleConfig, ctx: &mut BuildContext) -> WidgetId;
85    /// Chrome for the whole bar strip — wraps [`TabBarChromeConfig::content`].
86    fn make_bar(&self, cfg: &TabBarChromeConfig, ctx: &mut BuildContext) -> WidgetId;
87}
88
89pub type SharedTabStyle = Rc<dyn TabStyle>;