#![allow(missing_docs)] use std::cmp::Ordering; use gpui::{AnyElement, IntoElement, Stateful}; use smallvec::SmallVec; use crate::prelude::*; /// The position of a [`Tab`] within a list of tabs. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TabPosition { /// The tab is first in the list. First, /// The tab is in the middle of the list (i.e., it is not the first or last tab). /// /// The [`Ordering`] is where this tab is positioned with respect to the selected tab. Middle(Ordering), /// The tab is last in the list. Last, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TabCloseSide { Start, End, } #[derive(IntoElement)] pub struct Tab { div: Stateful
, selected: bool, position: TabPosition, close_side: TabCloseSide, start_slot: Option, end_slot: Option, children: SmallVec<[AnyElement; 2]>, } impl Tab { pub fn new(id: impl Into) -> Self { let id = id.into(); Self { div: div() .id(id.clone()) .debug_selector(|| format!("TAB-{}", id)), selected: false, position: TabPosition::First, close_side: TabCloseSide::End, start_slot: None, end_slot: None, children: SmallVec::new(), } } pub fn position(mut self, position: TabPosition) -> Self { self.position = position; self } pub fn close_side(mut self, close_side: TabCloseSide) -> Self { self.close_side = close_side; self } pub fn start_slot(mut self, element: impl Into>) -> Self { self.start_slot = element.into().map(IntoElement::into_any_element); self } pub fn end_slot(mut self, element: impl Into>) -> Self { self.end_slot = element.into().map(IntoElement::into_any_element); self } pub fn content_height(cx: &mut WindowContext) -> Pixels { DynamicSpacing::Base32.px(cx) - px(1.) } pub fn container_height(cx: &mut WindowContext) -> Pixels { DynamicSpacing::Base32.px(cx) } } impl InteractiveElement for Tab { fn interactivity(&mut self) -> &mut gpui::Interactivity { self.div.interactivity() } } impl StatefulInteractiveElement for Tab {} impl Toggleable for Tab { fn toggle_state(mut self, selected: bool) -> Self { self.selected = selected; self } } impl ParentElement for Tab { fn extend(&mut self, elements: impl IntoIterator) { self.children.extend(elements) } } impl RenderOnce for Tab { #[allow(refining_impl_trait)] fn render(self, cx: &mut WindowContext) -> Stateful
{ let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected { false => ( cx.theme().colors().text_muted, cx.theme().colors().tab_inactive_background, cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), true => ( cx.theme().colors().text, cx.theme().colors().tab_active_background, cx.theme().colors().element_hover, cx.theme().colors().element_active, ), }; let (start_slot, end_slot) = { let start_slot = h_flex().size_3().justify_center().children(self.start_slot); let end_slot = h_flex().size_3().justify_center().children(self.end_slot); match self.close_side { TabCloseSide::End => (start_slot, end_slot), TabCloseSide::Start => (end_slot, start_slot), } }; self.div .h(Tab::container_height(cx)) .bg(tab_bg) .border_color(cx.theme().colors().border) .map(|this| match self.position { TabPosition::First => { if self.selected { this.pl_px().border_r_1().pb_px() } else { this.pl_px().pr_px().border_b_1() } } TabPosition::Last => { if self.selected { this.border_l_1().border_r_1().pb_px() } else { this.pr_px().pl_px().border_b_1().border_r_1() } } TabPosition::Middle(Ordering::Equal) => this.border_l_1().border_r_1().pb_px(), TabPosition::Middle(Ordering::Less) => this.border_l_1().pr_px().border_b_1(), TabPosition::Middle(Ordering::Greater) => this.border_r_1().pl_px().border_b_1(), }) .cursor_pointer() .child( h_flex() .group("") .relative() .h(Tab::content_height(cx)) .px(DynamicSpacing::Base04.px(cx)) .gap(DynamicSpacing::Base04.rems(cx)) .text_color(text_color) .child(start_slot) .children(self.children) .child(end_slot), ) } }