update sidebar

This commit is contained in:
2026-09-23 20:19:42 +07:00
parent 273ddabde5
commit c61be4a139
6 changed files with 485 additions and 596 deletions
+94 -49
View File
@@ -25,6 +25,7 @@ pub struct Tab {
children: Vec<AnyElement>,
pub(super) disabled: bool,
pub(super) selected: bool,
pub(super) segmented: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
}
@@ -69,6 +70,7 @@ impl Default for Tab {
children: Vec::new(),
disabled: false,
selected: false,
segmented: false,
prefix: None,
suffix: None,
on_click: None,
@@ -132,6 +134,12 @@ impl Tab {
self.tab_bar_prefix = Some(tab_bar_prefix);
self
}
/// Render the tab as a segment inside a segmented control.
pub(crate) fn segmented(mut self, segmented: bool) -> Self {
self.segmented = segmented;
self
}
}
impl ParentElement for Tab {
@@ -167,74 +175,111 @@ impl Styled for Tab {
impl RenderOnce for Tab {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let fg = if self.disabled {
let Self {
ix,
base,
label,
icon,
prefix,
suffix,
children,
disabled,
selected,
segmented,
on_click,
..
} = self;
let fg = if disabled {
cx.theme().text_muted
} else if self.selected {
} else if selected {
cx.theme().tab_active_foreground
} else {
cx.theme().tab_foreground
};
self.base
.id(self.ix)
.flex()
.flex_wrap()
.gap_1()
let content = h_flex()
.flex_1()
.map(|this| {
if segmented {
this.h(px(24.))
} else {
this.h(px(30.))
}
})
.line_height(relative(1.))
.whitespace_nowrap()
.items_center()
.flex_shrink_0()
.h(TABBAR_HEIGHT)
.relative()
.justify_center()
.overflow_hidden()
.when(segmented, |this| this.px_1())
.when(!segmented, |this| this.flex_shrink_0().px_3())
.map(|this| match icon {
Some(icon) => this.w(px(38.)).child(icon.size_4()),
None => this
.map(|this| match label {
Some(label) => this.child(label),
None => this,
})
.children(children),
});
base.id(ix)
.flex()
.items_center()
.text_color(fg)
.text_sm()
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
h_flex()
.when(segmented, |this| {
this.text_xs()
.flex_1()
.h(px(30.))
.line_height(relative(1.))
.whitespace_nowrap()
.items_center()
.justify_center()
.overflow_hidden()
.h(px(24.))
.rounded(cx.theme().radius)
.when(selected && !disabled, |this| {
this.bg(cx.theme().tab_active_background)
.when(cx.theme().shadow, |this| this.shadow_sm())
})
.when(!selected && !disabled, |this| {
this.hover(|this| this.bg(cx.theme().tab_hover_background))
})
})
.when(!segmented, |this| {
this.text_sm()
.flex_wrap()
.gap_1()
.flex_shrink_0()
.px_3()
.map(|this| match self.icon {
Some(icon) => this.w(px(38.)).child(icon.size_4()),
None => this
.map(|this| match self.label {
Some(label) => this.child(label),
None => this,
})
.children(self.children),
}),
)
.when_some(self.suffix, |this, suffix| {
.h(TABBAR_HEIGHT)
.relative()
.overflow_hidden()
})
.when_some(prefix, |this, prefix| this.child(prefix))
.child(content)
.when_some(suffix, |this, suffix| {
this.child(div().pr_2().child(suffix))
})
.on_mouse_down(MouseButton::Left, |_ev, _window, cx| {
cx.stop_propagation();
})
.when(!self.disabled, |this| {
this.when_some(self.on_click.clone(), |this, on_click| {
.when(!disabled, |this| {
this.when_some(on_click, |this, on_click| {
this.on_click(move |event, window, cx| on_click(event, window, cx))
})
})
.child(
div()
.absolute()
.bottom_0()
.left_0()
.right_0()
.h_0p5()
.when(self.selected && !self.disabled, |this| {
this.bg(cx.theme().element_active)
})
.when(!self.selected && !self.disabled, |this| {
this.invisible().group_hover("", |this| {
this.visible().bg(cx.theme().secondary_background)
.when(!segmented, |this| {
this.child(
div()
.absolute()
.bottom_0()
.left_0()
.right_0()
.h_0p5()
.when(selected && !disabled, |this| {
this.bg(cx.theme().element_active)
})
}),
)
.when(!selected && !disabled, |this| {
this.invisible().group_hover("", |this| {
this.visible().bg(cx.theme().secondary_background)
})
}),
)
})
}
}
+19 -3
View File
@@ -7,6 +7,7 @@ use gpui::{
Window, div, px,
};
use smallvec::SmallVec;
use theme::ActiveTheme;
use super::Tab;
use crate::button::{Button, ButtonVariants as _};
@@ -25,6 +26,7 @@ pub struct TabBar {
last_empty_space: AnyElement,
selected_index: Option<usize>,
menu: bool,
segmented: bool,
#[allow(clippy::type_complexity)]
on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,
}
@@ -43,9 +45,16 @@ impl TabBar {
selected_index: None,
on_click: None,
menu: false,
segmented: false,
}
}
/// Render the tabs as a segmented control inside a pill-shaped track.
pub fn segmented(mut self, segmented: bool) -> Self {
self.segmented = segmented;
self
}
/// Set whether to show the menu button when tabs overflow, default is false.
pub fn menu(mut self, menu: bool) -> Self {
self.menu = menu;
@@ -113,10 +122,11 @@ impl Styled for TabBar {
}
impl RenderOnce for TabBar {
fn render(self, _: &mut Window, _cx: &mut App) -> impl IntoElement {
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 segmented = self.segmented;
self.base
.group("tab-bar")
@@ -124,22 +134,28 @@ impl RenderOnce for TabBar {
.flex()
.items_center()
.refine_style(&self.style)
.when(segmented, |this| {
this.bg(cx.theme().tab_background)
.p_0p5()
.rounded(cx.theme().radius)
})
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
h_flex()
.id("tabs")
.flex_1()
.overflow_x_scroll()
.when(!segmented, |this| this.overflow_x_scroll())
.when_some(self.scroll_handle, |this, scroll_handle| {
this.track_scroll(&scroll_handle)
})
.gap(px(0.))
.gap_1()
.children(self.children.into_iter().enumerate().map(|(ix, child)| {
item_labels.push((child.label.clone(), child.disabled));
let tab_bar_prefix = child.tab_bar_prefix.unwrap_or(true);
child
.ix(ix)
.tab_bar_prefix(tab_bar_prefix)
.segmented(segmented)
.when_some(self.selected_index, |this, selected_ix| {
this.selected(selected_ix == ix)
})