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