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