Skip to main content

guise/data/
tabs.rs

1//! `Tabs` — a tab bar with switchable panels (gpui entity).
2
3use gpui::prelude::*;
4use gpui::{div, px, transparent_black, App, Context, IntoElement, SharedString, Window};
5
6use super::Content;
7use crate::theme::{theme, Size};
8
9struct TabItem {
10    label: SharedString,
11    content: Content,
12}
13
14/// A tabbed view. Create with `cx.new(|cx| Tabs::new(cx).tab("One", |_, _| ...))`.
15pub struct Tabs {
16    tabs: Vec<TabItem>,
17    active: usize,
18}
19
20impl Tabs {
21    pub fn new(_cx: &mut Context<Self>) -> Self {
22        Tabs {
23            tabs: Vec::new(),
24            active: 0,
25        }
26    }
27
28    /// Add a tab. `content` is rebuilt each render so it can show live data.
29    pub fn tab<E>(
30        mut self,
31        label: impl Into<SharedString>,
32        content: impl Fn(&mut Window, &mut App) -> E + 'static,
33    ) -> Self
34    where
35        E: IntoElement,
36    {
37        self.tabs.push(TabItem {
38            label: label.into(),
39            content: Box::new(move |window, cx| content(window, cx).into_any_element()),
40        });
41        self
42    }
43
44    pub fn active(mut self, index: usize) -> Self {
45        self.active = index;
46        self
47    }
48
49    /// The index of the active tab.
50    pub fn active_index(&self) -> usize {
51        self.active
52    }
53}
54
55impl Render for Tabs {
56    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
57        let t = theme(cx);
58        let accent = t.primary().hsla();
59        let dimmed = t.dimmed().hsla();
60        let text = t.text().hsla();
61        let line = t.border().hsla();
62        let font = t.font_size(Size::Sm);
63
64        let count = self.tabs.len();
65        let active = if count == 0 { 0 } else { self.active.min(count - 1) };
66
67        let mut bar = div().flex().border_b_1().border_color(line);
68        for (i, tab) in self.tabs.iter().enumerate() {
69            let is_active = i == active;
70            bar = bar.child(
71                div()
72                    .id(("guise-tab", i))
73                    .px(px(16.0))
74                    .py(px(8.0))
75                    .border_b_2()
76                    .border_color(if is_active { accent } else { transparent_black() })
77                    .text_size(px(font))
78                    .text_color(if is_active { accent } else { dimmed })
79                    .hover(move |s| s.text_color(text))
80                    .child(tab.label.clone())
81                    .on_click(cx.listener(move |this, _ev, _window, cx| {
82                        this.active = i;
83                        cx.notify();
84                    })),
85            );
86        }
87
88        let mut root = div().flex().flex_col().gap(px(12.0)).child(bar);
89        if count > 0 {
90            let panel = (self.tabs[active].content)(window, cx);
91            root = root.child(panel);
92        }
93        root
94    }
95}