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 const HEIGHT_IN_REMS: f32 = 30. / 16.;
 52
 53    pub fn position(mut self, position: TabPosition) -> Self {
 54        self.position = position;
 55        self
 56    }
 57
 58    pub fn close_side(mut self, close_side: TabCloseSide) -> Self {
 59        self.close_side = close_side;
 60        self
 61    }
 62
 63    pub fn start_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
 64        self.start_slot = element.into().map(IntoElement::into_any_element);
 65        self
 66    }
 67
 68    pub fn end_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
 69        self.end_slot = element.into().map(IntoElement::into_any_element);
 70        self
 71    }
 72}
 73
 74impl InteractiveElement for Tab {
 75    fn interactivity(&mut self) -> &mut gpui::Interactivity {
 76        self.div.interactivity()
 77    }
 78}
 79
 80impl StatefulInteractiveElement for Tab {}
 81
 82impl Selectable for Tab {
 83    fn selected(mut self, selected: bool) -> Self {
 84        self.selected = selected;
 85        self
 86    }
 87}
 88
 89impl ParentElement for Tab {
 90    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
 91        &mut self.children
 92    }
 93}
 94
 95impl RenderOnce for Tab {
 96    #[allow(refining_impl_trait)]
 97    fn render(self, cx: &mut WindowContext) -> Stateful<Div> {
 98        let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected {
 99            false => (
100                cx.theme().colors().text_muted,
101                cx.theme().colors().tab_inactive_background,
102                cx.theme().colors().ghost_element_hover,
103                cx.theme().colors().ghost_element_active,
104            ),
105            true => (
106                cx.theme().colors().text,
107                cx.theme().colors().tab_active_background,
108                cx.theme().colors().element_hover,
109                cx.theme().colors().element_active,
110            ),
111        };
112
113        self.div
114            .h(rems(Self::HEIGHT_IN_REMS))
115            .bg(tab_bg)
116            .border_color(cx.theme().colors().border)
117            .map(|this| match self.position {
118                TabPosition::First => {
119                    if self.selected {
120                        this.pl_px().border_r().pb_px()
121                    } else {
122                        this.pl_px().pr_px().border_b()
123                    }
124                }
125                TabPosition::Last => {
126                    if self.selected {
127                        this.border_l().border_r().pb_px()
128                    } else {
129                        this.pr_px().pl_px().border_b().border_r()
130                    }
131                }
132                TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(),
133                TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(),
134                TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(),
135            })
136            .cursor_pointer()
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                            .w_3()
162                            .h_3()
163                            .justify_center()
164                            .absolute()
165                            .map(|this| match self.close_side {
166                                TabCloseSide::Start => this.left_1(),
167                                TabCloseSide::End => this.right_1(),
168                            })
169                            .visible_on_hover("")
170                            .children(self.end_slot),
171                    )
172                    .children(self.children),
173            )
174    }
175}