use std::rc::Rc;
use gpui::{
AnyElement, App, Corner, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
RenderOnce, ScrollHandle, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled,
Window, div, prelude::FluentBuilder as _,
};
use smallvec::SmallVec;
use super::{
super::{
button::{Button, ButtonVariants as _},
menu::{DropdownMenu as _, PopupMenuItem},
},
Tab,
};
use crate::{
ActiveTheme, Icon, IconName, Selectable, Sizable, Size, StyleSized, StyledExt, h_flex, v_flex,
};
type TabBarClickHandler = dyn Fn(&usize, &mut Window, &mut App);
#[derive(IntoElement)]
pub struct TabBar {
base: Stateful<Div>,
style: StyleRefinement,
scroll_handle: Option<ScrollHandle>,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
children: SmallVec<[Tab; 2]>,
last_empty_space: AnyElement,
selected_index: Option<usize>,
size: Size,
menu: bool,
vertical: bool,
on_click: Option<Rc<TabBarClickHandler>>,
}
impl TabBar {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
base: h_flex().id(id),
style: StyleRefinement::default(),
children: SmallVec::new(),
scroll_handle: None,
prefix: None,
suffix: None,
size: Size::default(),
last_empty_space: div().w_3().into_any_element(),
selected_index: None,
on_click: None,
menu: false,
vertical: false,
}
}
pub fn vertical(mut self, vertical: bool) -> Self {
self.vertical = vertical;
self
}
pub fn menu(mut self, menu: bool) -> Self {
self.menu = menu;
self
}
pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.scroll_handle = Some(scroll_handle.clone());
self
}
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
}
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Tab>>) -> Self {
self.children.extend(children.into_iter().map(Into::into));
self
}
pub fn child(mut self, child: impl Into<Tab>) -> Self {
self.children.push(child.into());
self
}
pub fn selected_index(mut self, index: usize) -> Self {
self.selected_index = Some(index);
self
}
pub fn last_empty_space(mut self, last_empty_space: impl IntoElement) -> Self {
self.last_empty_space = last_empty_space.into_any_element();
self
}
pub fn on_click<F>(mut self, on_click: F) -> Self
where
F: Fn(&usize, &mut Window, &mut App) + 'static, {
self.on_click = Some(Rc::new(on_click));
self
}
}
impl_styled!(TabBar);
impl_sizable!(TabBar);
impl RenderOnce for TabBar {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let mut item_labels = Vec::new();
let selected_index = self.selected_index;
let on_click = self.on_click.clone();
let vertical = self.vertical;
let last_empty_space = if vertical {
div().flex_1().into_any_element()
} else {
self.last_empty_space
};
let tabs_children = self.children.into_iter().enumerate().map(|(ix, child)| {
item_labels.push((child.label.clone(), child.disabled));
child
.ix(ix)
.with_size(self.size)
.set_icon_only(vertical)
.when_some(self.selected_index, |this, selected_ix| {
this.selected(selected_ix == ix)
})
.when_some(self.on_click.clone(), move |this, on_click| {
this.on_click(move |_, window, cx| on_click(&ix, window, cx))
})
});
if vertical {
self
.base
.group("tab-bar")
.relative()
.flex()
.flex_col()
.items_center()
.container_size(self.size)
.w(self.size.container_height())
.h_full()
.container_gap(self.size)
.text_color(cx.theme().tab_foreground)
.refine_style(&self.style)
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
v_flex()
.id("tabs")
.flex_1()
.w_full()
.container_gap(self.size)
.items_center()
.overflow_y_scroll()
.when_some(self.scroll_handle, |this, scroll_handle| {
this.track_scroll(&scroll_handle)
})
.children(tabs_children)
.when(self.suffix.is_some() || self.menu, |this| {
this.child(last_empty_space)
}),
)
.when_some(self.suffix, |this, suffix| this.child(suffix))
} else {
self
.base
.group("tab-bar")
.relative()
.flex()
.items_center()
.container_size(self.size)
.container_h(self.size)
.container_gap(self.size)
.text_color(cx.theme().tab_foreground)
.refine_style(&self.style)
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
h_flex()
.id("tabs")
.flex_1()
.container_gap(self.size)
.items_center()
.overflow_x_scroll()
.when_some(self.scroll_handle, |this, scroll_handle| {
this.track_scroll(&scroll_handle)
})
.children(tabs_children)
.child(last_empty_space),
)
.when(self.menu, |this| {
this.child(
Button::new("more")
.flat()
.icon(Icon::new(IconName::ChevronDown))
.dropdown_menu(move |mut this: crate::PopupMenu, _, _| {
this = this.scrollable(true);
for (ix, (label, disabled)) in item_labels.iter().enumerate() {
this = this.item(
PopupMenuItem::new(label.clone().unwrap_or_default())
.checked(selected_index == Some(ix))
.disabled(*disabled)
.when_some(on_click.clone(), |this, on_click| {
this.on_click(move |_, window, cx| on_click(&ix, window, cx))
}),
)
}
this
})
.anchor(Corner::TopRight),
)
})
.when_some(self.suffix, |this, suffix| this.child(suffix))
}
}
}