tabs: Add menu to TabBar to display more items. (#731)

<img width="752" alt="image"
src="https://github.com/user-attachments/assets/a7efb600-553a-4b78-a8f4-aaba504d44f0"
/>

- tabs: Use default cursor when Tab is disabled.
- popup_menu: Add to support add menu item with disabled state.
This commit is contained in:
Jason Lee 2025-03-24 18:05:54 +08:00 committed by GitHub
parent 760c0597eb
commit 8a3b858d1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 329 additions and 106 deletions

View file

@ -4,6 +4,7 @@ use gpui::{
use gpui_component::{ use gpui_component::{
button::{Button, ButtonGroup, ButtonVariants}, button::{Button, ButtonGroup, ButtonVariants},
checkbox::Checkbox,
h_flex, h_flex,
tab::{Tab, TabBar}, tab::{Tab, TabBar},
v_flex, IconName, Selectable as _, Sizable, Size, v_flex, IconName, Selectable as _, Sizable, Size,
@ -15,6 +16,7 @@ pub struct TabsStory {
focus_handle: gpui::FocusHandle, focus_handle: gpui::FocusHandle,
active_tab_ix: usize, active_tab_ix: usize,
size: Size, size: Size,
menu: bool,
} }
impl super::Story for TabsStory { impl super::Story for TabsStory {
@ -41,6 +43,7 @@ impl TabsStory {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
active_tab_ix: 0, active_tab_ix: 0,
size: Size::default(), size: Size::default(),
menu: false,
} }
} }
@ -66,45 +69,59 @@ impl Render for TabsStory {
v_flex() v_flex()
.gap_6() .gap_6()
.child( .child(
ButtonGroup::new("toggle-size") h_flex()
.outline() .gap_3()
.compact()
.child( .child(
Button::new("xsmall") ButtonGroup::new("toggle-size")
.label("XSmall") .outline()
.selected(self.size == Size::XSmall), .compact()
.child(
Button::new("xsmall")
.label("XSmall")
.selected(self.size == Size::XSmall),
)
.child(
Button::new("small")
.label("Small")
.selected(self.size == Size::Small),
)
.child(
Button::new("medium")
.label("Medium")
.selected(self.size == Size::Medium),
)
.child(
Button::new("large")
.label("Large")
.selected(self.size == Size::Large),
)
.on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {
let size = match selecteds[0] {
0 => Size::XSmall,
1 => Size::Small,
2 => Size::Medium,
3 => Size::Large,
_ => unreachable!(),
};
this.set_size(size, window, cx);
})),
) )
.child( .child(
Button::new("small") Checkbox::new("show-menu")
.label("Small") .label("More menu")
.selected(self.size == Size::Small), .checked(self.menu)
) .on_click(cx.listener(|this, _, _, cx| {
.child( this.menu = !this.menu;
Button::new("medium") cx.notify();
.label("Medium") })),
.selected(self.size == Size::Medium), ),
)
.child(
Button::new("large")
.label("Large")
.selected(self.size == Size::Large),
)
.on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {
let size = match selecteds[0] {
0 => Size::XSmall,
1 => Size::Small,
2 => Size::Medium,
3 => Size::Large,
_ => unreachable!(),
};
this.set_size(size, window, cx);
})),
) )
.child( .child(
section("Tabs", cx).child( section("Tabs", cx).child(
TabBar::new("tabs") TabBar::new("tabs")
.w_full() .w_full()
.with_size(self.size) .with_size(self.size)
.with_menu(self.menu)
.selected_index(self.active_tab_ix) .selected_index(self.active_tab_ix)
.on_click(cx.listener(|this, ix: &usize, window, cx| { .on_click(cx.listener(|this, ix: &usize, window, cx| {
this.set_active_tab(*ix, window, cx); this.set_active_tab(*ix, window, cx);
@ -152,6 +169,7 @@ impl Render for TabsStory {
.w_full() .w_full()
.underline() .underline()
.with_size(self.size) .with_size(self.size)
.with_menu(self.menu)
.selected_index(self.active_tab_ix) .selected_index(self.active_tab_ix)
.on_click(cx.listener(|this, ix: &usize, window, cx| { .on_click(cx.listener(|this, ix: &usize, window, cx| {
this.set_active_tab(*ix, window, cx); this.set_active_tab(*ix, window, cx);
@ -172,6 +190,7 @@ impl Render for TabsStory {
.w_full() .w_full()
.pill() .pill()
.with_size(self.size) .with_size(self.size)
.with_menu(self.menu)
.selected_index(self.active_tab_ix) .selected_index(self.active_tab_ix)
.on_click(cx.listener(|this, ix: &usize, window, cx| { .on_click(cx.listener(|this, ix: &usize, window, cx| {
this.set_active_tab(*ix, window, cx); this.set_active_tab(*ix, window, cx);
@ -192,6 +211,7 @@ impl Render for TabsStory {
.w_full() .w_full()
.outline() .outline()
.with_size(self.size) .with_size(self.size)
.with_menu(self.menu)
.selected_index(self.active_tab_ix) .selected_index(self.active_tab_ix)
.on_click(cx.listener(|this, ix: &usize, window, cx| { .on_click(cx.listener(|this, ix: &usize, window, cx| {
this.set_active_tab(*ix, window, cx); this.set_active_tab(*ix, window, cx);
@ -212,6 +232,7 @@ impl Render for TabsStory {
.w_full() .w_full()
.segmented() .segmented()
.with_size(self.size) .with_size(self.size)
.with_menu(self.menu)
.selected_index(self.active_tab_ix) .selected_index(self.active_tab_ix)
.on_click(cx.listener(|this, ix: &usize, window, cx| { .on_click(cx.listener(|this, ix: &usize, window, cx| {
this.set_active_tab(*ix, window, cx); this.set_active_tab(*ix, window, cx);

View file

@ -143,6 +143,10 @@ impl RenderOnce for ListItem {
this.hover(|this| this.bg(cx.theme().list_hover)) this.hover(|this| this.bg(cx.theme().list_hover))
}) })
}) })
.when(self.disabled, |this| {
this.cursor_not_allowed()
.text_color(cx.theme().muted_foreground)
})
.child( .child(
h_flex() h_flex()
.w_full() .w_full()

View file

@ -64,17 +64,20 @@ enum PopupMenuItem {
Item { Item {
icon: Option<Icon>, icon: Option<Icon>,
label: SharedString, label: SharedString,
disabled: bool,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
handler: Rc<dyn Fn(&mut Window, &mut App)>, handler: Rc<dyn Fn(&mut Window, &mut App)>,
}, },
ElementItem { ElementItem {
icon: Option<Icon>, icon: Option<Icon>,
disabled: bool,
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>, render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
handler: Rc<dyn Fn(&mut Window, &mut App)>, handler: Rc<dyn Fn(&mut Window, &mut App)>,
}, },
Submenu { Submenu {
icon: Option<Icon>, icon: Option<Icon>,
label: SharedString, label: SharedString,
disabled: bool,
menu: Entity<PopupMenu>, menu: Entity<PopupMenu>,
}, },
} }
@ -173,17 +176,38 @@ impl PopupMenu {
} }
/// Add Menu Item /// Add Menu Item
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self { pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.add_menu_item(label, None, action); self.menu_with_disabled(label, action, false)
}
/// Add Menu Item with disabled state
pub fn menu_with_disabled(
mut self,
label: impl Into<SharedString>,
action: Box<dyn Action>,
disabled: bool,
) -> Self {
self.add_menu_item(label, None, action, disabled);
self self
} }
/// Add Menu to open link /// Add Menu to open link
pub fn link(mut self, label: impl Into<SharedString>, href: impl Into<String>) -> Self { pub fn link(self, label: impl Into<SharedString>, href: impl Into<String>) -> Self {
self.link_with_disabled(label, href, false)
}
/// Add Menu to open link with disabled state
pub fn link_with_disabled(
mut self,
label: impl Into<SharedString>,
href: impl Into<String>,
disabled: bool,
) -> Self {
let href = href.into(); let href = href.into();
self.menu_items.push(PopupMenuItem::Item { self.menu_items.push(PopupMenuItem::Item {
icon: None, icon: None,
label: label.into(), label: label.into(),
disabled,
action: None, action: None,
handler: Rc::new(move |_, cx| cx.open_url(&href)), handler: Rc::new(move |_, cx| cx.open_url(&href)),
}); });
@ -192,43 +216,77 @@ impl PopupMenu {
/// Add Menu to open link /// Add Menu to open link
pub fn link_with_icon( pub fn link_with_icon(
self,
label: impl Into<SharedString>,
icon: impl Into<Icon>,
href: impl Into<String>,
) -> Self {
self.link_with_icon_and_disabled(label, icon, href, false)
}
/// Add Menu to open link with icon and disabled state
pub fn link_with_icon_and_disabled(
mut self, mut self,
label: impl Into<SharedString>, label: impl Into<SharedString>,
icon: impl Into<Icon>, icon: impl Into<Icon>,
href: impl Into<String>, href: impl Into<String>,
disabled: bool,
) -> Self { ) -> Self {
let href = href.into(); let href = href.into();
self.menu_items.push(PopupMenuItem::Item { self.menu_items.push(PopupMenuItem::Item {
icon: Some(icon.into()), icon: Some(icon.into()),
label: label.into(), label: label.into(),
disabled,
action: None, action: None,
handler: Rc::new(move |_, cx| cx.open_url(&href)), handler: Rc::new(move |_, cx| cx.open_url(&href)),
}); });
self self
} }
/// Add Menu Item with Icon /// Add Menu Item with Icon.
pub fn menu_with_icon( pub fn menu_with_icon(
mut self, self,
label: impl Into<SharedString>, label: impl Into<SharedString>,
icon: impl Into<Icon>, icon: impl Into<Icon>,
action: Box<dyn Action>, action: Box<dyn Action>,
) -> Self { ) -> Self {
self.add_menu_item(label, Some(icon.into()), action); self.menu_with_icon_and_disabled(label, icon, action, false)
}
/// Add Menu Item with Icon and disabled state
pub fn menu_with_icon_and_disabled(
mut self,
label: impl Into<SharedString>,
icon: impl Into<Icon>,
action: Box<dyn Action>,
disabled: bool,
) -> Self {
self.add_menu_item(label, Some(icon.into()), action, disabled);
self self
} }
/// Add Menu Item with check icon /// Add Menu Item with check icon
pub fn menu_with_check( pub fn menu_with_check(
mut self, self,
label: impl Into<SharedString>, label: impl Into<SharedString>,
checked: bool, checked: bool,
action: Box<dyn Action>, action: Box<dyn Action>,
) -> Self {
self.menu_with_check_and_disabled(label, checked, action, false)
}
/// Add Menu Item with check icon and disabled state
pub fn menu_with_check_and_disabled(
mut self,
label: impl Into<SharedString>,
checked: bool,
action: Box<dyn Action>,
disabled: bool,
) -> Self { ) -> Self {
if checked { if checked {
self.add_menu_item(label, Some(IconName::Check.into()), action); self.add_menu_item(label, Some(IconName::Check.into()), action, disabled);
} else { } else {
self.add_menu_item(label, None, action); self.add_menu_item(label, None, action, disabled);
} }
self self
@ -243,11 +301,40 @@ impl PopupMenu {
self.menu_element_with_check(false, action, builder) self.menu_element_with_check(false, action, builder)
} }
/// Add Menu Item with custom element render with disabled state.
pub fn menu_element_with_disabled<F, E>(
self,
action: Box<dyn Action>,
disabled: bool,
builder: F,
) -> Self
where
F: Fn(&mut Window, &mut App) -> E + 'static,
E: IntoElement,
{
self.menu_element_with_check_and_disabled(false, action, disabled, builder)
}
/// Add Menu Item with custom element render with icon. /// Add Menu Item with custom element render with icon.
pub fn menu_element_with_icon<F, E>( pub fn menu_element_with_icon<F, E>(
self,
icon: impl Into<Icon>,
action: Box<dyn Action>,
builder: F,
) -> Self
where
F: Fn(&mut Window, &mut App) -> E + 'static,
E: IntoElement,
{
self.menu_element_with_icon_and_disabled(icon, action, false, builder)
}
/// Add Menu Item with custom element render with icon and disabled state
pub fn menu_element_with_icon_and_disabled<F, E>(
mut self, mut self,
icon: impl Into<Icon>, icon: impl Into<Icon>,
action: Box<dyn Action>, action: Box<dyn Action>,
disabled: bool,
builder: F, builder: F,
) -> Self ) -> Self
where where
@ -258,6 +345,7 @@ impl PopupMenu {
render: Box::new(move |window, cx| builder(window, cx).into_any_element()), render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
handler: self.wrap_handler(action), handler: self.wrap_handler(action),
icon: Some(icon.into()), icon: Some(icon.into()),
disabled,
}); });
self.has_icon = true; self.has_icon = true;
self self
@ -265,9 +353,24 @@ impl PopupMenu {
/// Add Menu Item with custom element render with check state /// Add Menu Item with custom element render with check state
pub fn menu_element_with_check<F, E>( pub fn menu_element_with_check<F, E>(
self,
checked: bool,
action: Box<dyn Action>,
builder: F,
) -> Self
where
F: Fn(&mut Window, &mut App) -> E + 'static,
E: IntoElement,
{
self.menu_element_with_check_and_disabled(checked, action, false, builder)
}
/// Add Menu Item with custom element render with check state and disabled state
pub fn menu_element_with_check_and_disabled<F, E>(
mut self, mut self,
checked: bool, checked: bool,
action: Box<dyn Action>, action: Box<dyn Action>,
disabled: bool,
builder: F, builder: F,
) -> Self ) -> Self
where where
@ -279,6 +382,7 @@ impl PopupMenu {
render: Box::new(move |window, cx| builder(window, cx).into_any_element()), render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
handler: self.wrap_handler(action), handler: self.wrap_handler(action),
icon: Some(IconName::Check.into()), icon: Some(IconName::Check.into()),
disabled,
}); });
self.has_icon = true; self.has_icon = true;
} else { } else {
@ -286,6 +390,7 @@ impl PopupMenu {
render: Box::new(move |window, cx| builder(window, cx).into_any_element()), render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
handler: self.wrap_handler(action), handler: self.wrap_handler(action),
icon: None, icon: None,
disabled,
}); });
} }
self self
@ -297,25 +402,6 @@ impl PopupMenu {
}) })
} }
fn add_menu_item(
&mut self,
label: impl Into<SharedString>,
icon: Option<Icon>,
action: Box<dyn Action>,
) -> &mut Self {
if icon.is_some() {
self.has_icon = true;
}
self.menu_items.push(PopupMenuItem::Item {
icon,
label: label.into(),
action: Some(action.boxed_clone()),
handler: self.wrap_handler(action),
});
self
}
/// Add a separator Menu Item /// Add a separator Menu Item
pub fn separator(mut self) -> Self { pub fn separator(mut self) -> Self {
if self.menu_items.is_empty() { if self.menu_items.is_empty() {
@ -330,6 +416,7 @@ impl PopupMenu {
self self
} }
/// Add a Submenu
pub fn submenu( pub fn submenu(
self, self,
label: impl Into<SharedString>, label: impl Into<SharedString>,
@ -340,11 +427,36 @@ impl PopupMenu {
self.submenu_with_icon(None, label, window, cx, f) self.submenu_with_icon(None, label, window, cx, f)
} }
/// Add a Submenu item with disabled state
pub fn submenu_with_disabled(
self,
label: impl Into<SharedString>,
disabled: bool,
window: &mut Window,
cx: &mut Context<Self>,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self {
self.submenu_with_icon_with_disabled(None, label, disabled, window, cx, f)
}
/// Add a Submenu item with icon /// Add a Submenu item with icon
pub fn submenu_with_icon( pub fn submenu_with_icon(
self,
icon: Option<Icon>,
label: impl Into<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self {
self.submenu_with_icon_with_disabled(icon, label, false, window, cx, f)
}
/// Add a Submenu item with icon and disabled state
pub fn submenu_with_icon_with_disabled(
mut self, mut self,
icon: Option<Icon>, icon: Option<Icon>,
label: impl Into<SharedString>, label: impl Into<SharedString>,
disabled: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static, f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
@ -359,6 +471,28 @@ impl PopupMenu {
icon, icon,
label: label.into(), label: label.into(),
menu: submenu, menu: submenu,
disabled,
});
self
}
fn add_menu_item(
&mut self,
label: impl Into<SharedString>,
icon: Option<Icon>,
action: Box<dyn Action>,
disabled: bool,
) -> &mut Self {
if icon.is_some() {
self.has_icon = true;
}
self.menu_items.push(PopupMenuItem::Item {
icon,
label: label.into(),
disabled,
action: Some(action.boxed_clone()),
handler: self.wrap_handler(action),
}); });
self self
} }
@ -556,8 +690,18 @@ impl PopupMenu {
.my_0p5() .my_0p5()
.bg(cx.theme().muted), .bg(cx.theme().muted),
), ),
PopupMenuItem::ElementItem { render, icon, .. } => this PopupMenuItem::ElementItem {
.on_click(cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx))) render,
icon,
disabled,
..
} => this
.when(!disabled, |this| {
this.on_click(
cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),
)
})
.disabled(*disabled)
.child( .child(
h_flex() h_flex()
.min_h(ITEM_HEIGHT) .min_h(ITEM_HEIGHT)
@ -570,30 +714,41 @@ impl PopupMenu {
icon, icon,
label, label,
action, action,
disabled,
.. ..
} => { } => {
let action = action.as_ref().map(|action| action.boxed_clone()); let action = action.as_ref().map(|action| action.boxed_clone());
let key = Self::render_keybinding(action, window, cx); let key = Self::render_keybinding(action, window, cx);
this.on_click(cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx))) this.when(!disabled, |this| {
.child( this.on_click(
h_flex() cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),
.h(ITEM_HEIGHT)
.items_center()
.gap_x_1()
.children(Self::render_icon(has_icon, icon.clone(), window, cx))
.child(
h_flex()
.flex_1()
.gap_2()
.items_center()
.justify_between()
.child(label.clone())
.children(key),
),
) )
})
.disabled(*disabled)
.child(
h_flex()
.h(ITEM_HEIGHT)
.items_center()
.gap_x_1()
.children(Self::render_icon(has_icon, icon.clone(), window, cx))
.child(
h_flex()
.flex_1()
.gap_2()
.items_center()
.justify_between()
.child(label.clone())
.children(key),
),
)
} }
PopupMenuItem::Submenu { icon, label, menu } => this.selected(hovered).child( PopupMenuItem::Submenu {
icon,
label,
menu,
disabled,
} => this.selected(hovered).disabled(*disabled).child(
h_flex() h_flex()
.when(hovered, |this| { .when(hovered, |this| {
this.rounded(cx.theme().radius) this.rounded(cx.theme().radius)

View file

@ -379,15 +379,15 @@ impl TabVariant {
pub struct Tab { pub struct Tab {
id: ElementId, id: ElementId,
base: Div, base: Div,
label: SharedString, pub(super) label: SharedString,
icon: Option<Icon>, icon: Option<Icon>,
prefix: Option<AnyElement>, prefix: Option<AnyElement>,
suffix: Option<AnyElement>, suffix: Option<AnyElement>,
children: Vec<AnyElement>, children: Vec<AnyElement>,
variant: TabVariant, variant: TabVariant,
size: Size, size: Size,
disabled: bool, pub(super) disabled: bool,
selected: bool, pub(super) selected: bool,
on_click: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>, on_click: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
} }
@ -583,7 +583,7 @@ impl RenderOnce for Tab {
.flex_wrap() .flex_wrap()
.items_center() .items_center()
.flex_shrink_0() .flex_shrink_0()
.cursor_pointer() .when(!self.disabled, |this| this.cursor_pointer())
.overflow_hidden() .overflow_hidden()
.h(height) .h(height)
.overflow_hidden() .overflow_hidden()

View file

@ -1,16 +1,24 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{h_flex, ActiveTheme, Selectable, Sizable, Size, StyledExt}; use crate::button::{Button, ButtonVariants as _};
use crate::popup_menu::PopupMenuExt as _;
use crate::{h_flex, ActiveTheme, IconName, Selectable, Sizable, Size, StyledExt};
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::{ use gpui::{
div, AnyElement, App, Div, Edges, ElementId, IntoElement, ParentElement, RenderOnce, div, impl_internal_actions, AnyElement, App, Corner, Div, Edges, ElementId, IntoElement,
ScrollHandle, Stateful, StatefulInteractiveElement as _, Styled, Window, ParentElement, RenderOnce, ScrollHandle, Stateful, StatefulInteractiveElement as _,
StyleRefinement, Styled, Window,
}; };
use gpui::{px, InteractiveElement}; use gpui::{px, InteractiveElement};
use smallvec::SmallVec; use smallvec::SmallVec;
use super::{Tab, TabVariant}; use super::{Tab, TabVariant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SelectTab(usize);
impl_internal_actions!(tab_bar, [SelectTab]);
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct TabBar { pub struct TabBar {
base: Stateful<Div>, base: Stateful<Div>,
@ -22,6 +30,7 @@ pub struct TabBar {
selected_index: Option<usize>, selected_index: Option<usize>,
variant: TabVariant, variant: TabVariant,
size: Size, size: Size,
menu: bool,
on_click: Option<Arc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>, on_click: Option<Arc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,
} }
@ -39,6 +48,7 @@ impl TabBar {
last_empty_space: div().w_3().into_any_element(), last_empty_space: div().w_3().into_any_element(),
selected_index: None, selected_index: None,
on_click: None, on_click: None,
menu: false,
} }
} }
@ -72,6 +82,12 @@ impl TabBar {
self self
} }
/// Enable or disable the popup menu for the TabBar
pub fn with_menu(mut self, menu: bool) -> Self {
self.menu = menu;
self
}
/// Track the scroll of the TabBar /// Track the scroll of the TabBar
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self { pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
self.scroll_handle = Some(scroll_handle); self.scroll_handle = Some(scroll_handle);
@ -125,7 +141,7 @@ impl TabBar {
} }
impl Styled for TabBar { impl Styled for TabBar {
fn style(&mut self) -> &mut gpui::StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
self.base.style() self.base.style()
} }
} }
@ -190,8 +206,19 @@ impl RenderOnce for TabBar {
} }
}; };
let mut item_labels = Vec::new();
let selected_index = self.selected_index;
self.base self.base
.group("tab-bar") .group("tab-bar")
.on_action({
let on_click = self.on_click.clone();
move |action: &SelectTab, window: &mut Window, cx: &mut App| {
if let Some(on_click) = on_click.clone() {
on_click(&action.0, window, cx);
}
}
})
.relative() .relative()
.flex() .flex()
.items_center() .items_center()
@ -216,6 +243,7 @@ impl RenderOnce for TabBar {
self.variant == TabVariant::Pill || self.variant == TabVariant::Segmented, self.variant == TabVariant::Pill || self.variant == TabVariant::Segmented,
|this| this.rounded(cx.theme().radius), |this| this.rounded(cx.theme().radius),
) )
.paddings(paddings)
.when_some(self.prefix, |this, prefix| this.child(prefix)) .when_some(self.prefix, |this, prefix| this.child(prefix))
.child( .child(
h_flex() h_flex()
@ -226,30 +254,45 @@ impl RenderOnce for TabBar {
this.track_scroll(&scroll_handle) this.track_scroll(&scroll_handle)
}) })
.gap(gap) .gap(gap)
.paddings(paddings) .children(self.children.into_iter().enumerate().map(|(ix, child)| {
.children( item_labels.push((child.label.clone(), child.disabled));
self.children child
.into_iter() .id(ix)
.enumerate() .variant(self.variant)
.map(move |(ix, child)| { .with_size(self.size)
child .when_some(self.selected_index, |this, selected_ix| {
.id(ix) this.selected(selected_ix == ix)
.variant(self.variant) })
.with_size(self.size) .when_some(self.on_click.clone(), move |this, on_click| {
.when_some(self.selected_index, |this, selected_ix| { this.on_click(move |_, window, cx| on_click(&ix, window, cx))
this.selected(selected_ix == ix) })
}) }))
.when_some(self.on_click.clone(), move |this, on_click| { .when(self.suffix.is_some() || self.menu, |this| {
this.on_click(move |_, window, cx| {
on_click(&ix, window, cx)
})
})
}),
)
.when(self.suffix.is_some(), |this| {
this.child(self.last_empty_space) this.child(self.last_empty_space)
}), }),
) )
.when(self.menu, |this| {
this.child(
Button::new("more")
.xsmall()
.ghost()
.icon(IconName::ChevronDown)
.popup_menu(move |mut this, _, _| {
this = this.scrollable();
for (ix, (label, disabled)) in item_labels.iter().enumerate() {
this = this.menu_with_check_and_disabled(
label.clone(),
selected_index == Some(ix),
Box::new(SelectTab(ix)),
*disabled,
);
}
this
})
.anchor(Corner::TopRight),
)
})
.when_some(self.suffix, |this, suffix| this.child(suffix)) .when_some(self.suffix, |this, suffix| this.child(suffix))
} }
} }