tab.rs

  1use crate::prelude::*;
  2use gpui::{AnyElement, IntoElement, Stateful};
  3use smallvec::SmallVec;
  4use std::cmp::Ordering;
  5
  6/// The position of a [`Tab`] within a list of tabs.
  7#[derive(Debug, PartialEq, Eq, Clone, Copy)]
  8pub enum TabPosition {
  9    /// The tab is first in the list.
 10    First,
 11
 12    /// The tab is in the middle of the list (i.e., it is not the first or last tab).
 13    ///
 14    /// The [`Ordering`] is where this tab is positioned with respect to the selected tab.
 15    Middle(Ordering),
 16
 17    /// The tab is last in the list.
 18    Last,
 19}
 20
 21#[derive(Debug, PartialEq, Eq, Clone, Copy)]
 22pub enum TabCloseSide {
 23    Start,
 24    End,
 25}
 26
 27#[derive(IntoElement)]
 28pub struct Tab {
 29    div: Stateful<Div>,
 30    selected: bool,
 31    position: TabPosition,
 32    close_side: TabCloseSide,
 33    start_slot: Option<AnyElement>,
 34    end_slot: Option<AnyElement>,
 35    children: SmallVec<[AnyElement; 2]>,
 36}
 37
 38impl Tab {
 39    pub fn new(id: impl Into<ElementId>) -> Self {
 40        Self {
 41            div: div().id(id),
 42            selected: false,
 43            position: TabPosition::First,
 44            close_side: TabCloseSide::End,
 45            start_slot: None,
 46            end_slot: None,
 47            children: SmallVec::new(),
 48        }
 49    }
 50
 51    pub fn position(mut self, position: TabPosition) -> Self {
 52        self.position = position;
 53        self
 54    }
 55
 56    pub fn close_side(mut self, close_side: TabCloseSide) -> Self {
 57        self.close_side = close_side;
 58        self
 59    }
 60
 61    pub fn start_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
 62        self.start_slot = element.into().map(IntoElement::into_any_element);
 63        self
 64    }
 65
 66    pub fn end_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
 67        self.end_slot = element.into().map(IntoElement::into_any_element);
 68        self
 69    }
 70}
 71
 72impl InteractiveElement for Tab {
 73    fn interactivity(&mut self) -> &mut gpui::Interactivity {
 74        self.div.interactivity()
 75    }
 76}
 77
 78impl StatefulInteractiveElement for Tab {}
 79
 80impl Selectable for Tab {
 81    fn selected(mut self, selected: bool) -> Self {
 82        self.selected = selected;
 83        self
 84    }
 85}
 86
 87impl ParentElement for Tab {
 88    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
 89        &mut self.children
 90    }
 91}
 92
 93impl RenderOnce for Tab {
 94    type Rendered = Stateful<Div>;
 95
 96    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
 97        const HEIGHT_IN_REMS: f32 = 30. / 16.;
 98
 99        let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected {
100            false => (
101                cx.theme().colors().text_muted,
102                cx.theme().colors().tab_inactive_background,
103                cx.theme().colors().ghost_element_hover,
104                cx.theme().colors().ghost_element_active,
105            ),
106            true => (
107                cx.theme().colors().text,
108                cx.theme().colors().tab_active_background,
109                cx.theme().colors().element_hover,
110                cx.theme().colors().element_active,
111            ),
112        };
113
114        self.div
115            .h(rems(HEIGHT_IN_REMS))
116            .bg(tab_bg)
117            .border_color(cx.theme().colors().border)
118            .map(|this| match self.position {
119                TabPosition::First => {
120                    if self.selected {
121                        this.pl_px().border_r().pb_px()
122                    } else {
123                        this.pl_px().pr_px().border_b()
124                    }
125                }
126                TabPosition::Last => {
127                    if self.selected {
128                        this.border_l().border_r().pb_px()
129                    } else {
130                        this.pr_px().pl_px().border_b()
131                    }
132                }
133                TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(),
134                TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(),
135                TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(),
136            })
137            .child(
138                h_stack()
139                    .group("")
140                    .relative()
141                    .h_full()
142                    .px_5()
143                    .gap_1()
144                    .text_color(text_color)
145                    // .hover(|style| style.bg(tab_hover_bg))
146                    // .active(|style| style.bg(tab_active_bg))
147                    .child(
148                        h_stack()
149                            .w_3()
150                            .h_3()
151                            .justify_center()
152                            .absolute()
153                            .map(|this| match self.close_side {
154                                TabCloseSide::Start => this.right_1(),
155                                TabCloseSide::End => this.left_1(),
156                            })
157                            .children(self.start_slot),
158                    )
159                    .child(
160                        h_stack()
161                            .invisible()
162                            .w_3()
163                            .h_3()
164                            .justify_center()
165                            .absolute()
166                            .map(|this| match self.close_side {
167                                TabCloseSide::Start => this.left_1(),
168                                TabCloseSide::End => this.right_1(),
169                            })
170                            .group_hover("", |style| style.visible())
171                            .children(self.end_slot),
172                    )
173                    .children(self.children),
174            )
175    }
176}