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