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