Skip to main content

oino_ui/widgets/
tabs.rs

1//! ratatui `Tabs` (§4.5.1).
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::node::{ActionId, Common, IntoNode, Key, Node};
8use crate::style::Style;
9use crate::text::{Line, Span};
10
11/// One tab title.
12#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct TabTitle {
15    /// Tab identity (drives `selected` and the action value).
16    pub key: Key,
17    /// Tab title.
18    pub title: Line,
19    /// GUI extra: right-aligned badge.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub badge: Option<Line>,
22}
23
24impl TabTitle {
25    /// A tab title.
26    #[must_use]
27    pub fn new(key: impl Into<Key>, title: impl Into<Line>) -> Self {
28        Self {
29            key: key.into(),
30            title: title.into(),
31            badge: None,
32        }
33    }
34
35    /// Sets the right-aligned badge.
36    #[must_use]
37    pub fn badge(mut self, badge: impl Into<Line>) -> Self {
38        self.badge = Some(badge.into());
39        self
40    }
41}
42
43/// ratatui `Tabs` (§4.5.1). With `panes`, the selected pane renders below the bar
44/// and switching is local (no round-trip) unless `onSelect` is set.
45#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct Tabs {
48    /// Common node props (`key`, `style`, `block`, `fallback`).
49    #[serde(flatten)]
50    pub common: Common,
51    /// Tab titles.
52    pub titles: Vec<TabTitle>,
53    /// Selected tab key.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub selected: Option<Key>,
56    /// Divider between titles (ratatui default `"│"`).
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub divider: Option<Span>,
59    /// Left / right padding lines around the bar.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub padding: Option<[Line; 2]>,
62    /// Selected-title style.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub highlight_style: Option<Style>,
65    /// Pane contents keyed by tab key.
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub panes: Option<BTreeMap<Key, Node>>,
68    /// Action on selection change (tab key).
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub on_select: Option<ActionId>,
71}
72
73impl Tabs {
74    /// Tabs from titles.
75    #[must_use]
76    pub fn new(titles: impl IntoIterator<Item = TabTitle>) -> Self {
77        Self {
78            titles: titles.into_iter().collect(),
79            ..Self::default()
80        }
81    }
82
83    /// Sets the selected tab key.
84    #[must_use]
85    pub fn selected(mut self, key: impl Into<Key>) -> Self {
86        self.selected = Some(key.into());
87        self
88    }
89
90    /// Sets the divider span.
91    #[must_use]
92    pub fn divider(mut self, divider: impl Into<Span>) -> Self {
93        self.divider = Some(divider.into());
94        self
95    }
96
97    /// Sets the left / right padding lines.
98    #[must_use]
99    pub fn padding(mut self, padding: [Line; 2]) -> Self {
100        self.padding = Some(padding);
101        self
102    }
103
104    /// Sets the selected-title style.
105    #[must_use]
106    pub fn highlight_style(mut self, style: Style) -> Self {
107        self.highlight_style = Some(style);
108        self
109    }
110
111    /// Attaches pane contents keyed by tab key.
112    #[must_use]
113    pub fn panes(mut self, panes: impl IntoIterator<Item = (Key, impl IntoNode)>) -> Self {
114        self.panes = Some(panes.into_iter().map(|(k, n)| (k, n.into_node())).collect());
115        self
116    }
117
118    /// Sets the selection action.
119    #[must_use]
120    pub fn on_select(mut self, action: impl Into<ActionId>) -> Self {
121        self.on_select = Some(action.into());
122        self
123    }
124}
125
126impl IntoNode for Tabs {}
127impl From<Tabs> for Node {
128    fn from(t: Tabs) -> Self {
129        Node::Tabs(t)
130    }
131}