use gpui::prelude::FluentBuilder;
use gpui::*;
use crate::theme::*;
#[derive(Clone, Debug)]
pub enum TabsEvent {
TabChanged { index: usize },
}
impl EventEmitter<TabsEvent> for Tabs {}
pub struct TabItem {
pub label: String,
pub content: Box<dyn Fn() -> AnyElement>,
}
impl TabItem {
pub fn new<F, E>(label: impl Into<String>, content: F) -> Self
where
F: Fn() -> E + 'static,
E: IntoElement + 'static,
{
Self {
label: label.into(),
content: Box::new(move || content().into_any_element()),
}
}
}
pub struct Tabs {
tabs: Vec<TabItem>,
active_index: usize,
size: ComponentSize,
}
impl Tabs {
pub fn new() -> Self {
Self {
tabs: Vec::new(),
active_index: 0,
size: ComponentSize::Medium,
}
}
pub fn tabs(mut self, tabs: Vec<TabItem>) -> Self {
self.tabs = tabs;
if self.active_index >= self.tabs.len() {
self.active_index = 0;
}
self
}
pub fn active_index(mut self, index: usize) -> Self {
if index < self.tabs.len() {
self.active_index = index;
}
self
}
pub fn size(mut self, size: ComponentSize) -> Self {
self.size = size;
self
}
fn tab_padding(&self) -> (Pixels, Pixels) {
let (py, px) = self.size.padding();
(py, px)
}
}
impl Default for Tabs {
fn default() -> Self {
Self::new()
}
}
impl Render for Tabs {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::default();
let tab_labels: Vec<String> = self.tabs.iter().map(|t| t.label.clone()).collect();
let tabs_ref = &self.tabs;
let active_index = self.active_index;
let size = self.size;
let (tab_py, tab_px) = self.tab_padding();
let tab_bar_bg = rgb(0xF5F5F5); let tab_bar_radius = px(BorderRadius::LG);
let tab_bar_shadow = BoxShadow {
color: rgba(0x0000000A).into(), offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
};
let make_active_tab_shadow = || BoxShadow {
color: rgba(0x0000000A).into(),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
};
div()
.flex()
.flex_col()
.w_full()
.gap_4()
.child(
div()
.flex()
.flex_row()
.bg(tab_bar_bg)
.rounded(tab_bar_radius)
.shadow(vec![tab_bar_shadow])
.p_1() .gap_1() .children(tab_labels.iter().enumerate().map({
let theme = theme.clone();
move |(index, label)| {
let is_active = index == active_index;
let label = label.clone();
div()
.relative()
.flex()
.flex_1()
.items_center()
.justify_center()
.py(tab_py)
.px(tab_px)
.rounded(px(BorderRadius::MD))
.text_size(size.font_size())
.when(is_active, |this| {
this.bg(rgb(0xFFFFFF)) .font_weight(FontWeight::BOLD)
.text_color(rgb(0x000000)) .shadow(vec![make_active_tab_shadow()])
})
.when(!is_active, |this| {
this.text_color(theme.colors.text_secondary) .cursor(CursorStyle::PointingHand)
})
.on_mouse_down(MouseButton::Left, cx.listener({
let index = index;
move |this, _event: &MouseDownEvent, _window, cx| {
if this.active_index != index {
this.active_index = index;
cx.emit(TabsEvent::TabChanged { index });
cx.notify();
}
}
}))
.child(label)
}
}))
)
.child(
{
div()
.flex()
.flex_col()
.w_full()
.items_center()
.text_color(theme.colors.text_secondary)
.when(active_index < tabs_ref.len(), |this| {
let tab = &tabs_ref[active_index];
let content = (tab.content)();
this.child(content)
})
.when(active_index >= tabs_ref.len(), |this| {
this.child("No content")
})
}
)
}
}