tab.rs

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