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:
parent
760c0597eb
commit
8a3b858d1a
5 changed files with 329 additions and 106 deletions
|
|
@ -4,6 +4,7 @@ use gpui::{
|
|||
|
||||
use gpui_component::{
|
||||
button::{Button, ButtonGroup, ButtonVariants},
|
||||
checkbox::Checkbox,
|
||||
h_flex,
|
||||
tab::{Tab, TabBar},
|
||||
v_flex, IconName, Selectable as _, Sizable, Size,
|
||||
|
|
@ -15,6 +16,7 @@ pub struct TabsStory {
|
|||
focus_handle: gpui::FocusHandle,
|
||||
active_tab_ix: usize,
|
||||
size: Size,
|
||||
menu: bool,
|
||||
}
|
||||
|
||||
impl super::Story for TabsStory {
|
||||
|
|
@ -41,6 +43,7 @@ impl TabsStory {
|
|||
focus_handle: cx.focus_handle(),
|
||||
active_tab_ix: 0,
|
||||
size: Size::default(),
|
||||
menu: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,45 +69,59 @@ impl Render for TabsStory {
|
|||
v_flex()
|
||||
.gap_6()
|
||||
.child(
|
||||
ButtonGroup::new("toggle-size")
|
||||
.outline()
|
||||
.compact()
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.child(
|
||||
Button::new("xsmall")
|
||||
.label("XSmall")
|
||||
.selected(self.size == Size::XSmall),
|
||||
ButtonGroup::new("toggle-size")
|
||||
.outline()
|
||||
.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(
|
||||
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);
|
||||
})),
|
||||
Checkbox::new("show-menu")
|
||||
.label("More menu")
|
||||
.checked(self.menu)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.menu = !this.menu;
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Tabs", cx).child(
|
||||
TabBar::new("tabs")
|
||||
.w_full()
|
||||
.with_size(self.size)
|
||||
.with_menu(self.menu)
|
||||
.selected_index(self.active_tab_ix)
|
||||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
|
|
@ -152,6 +169,7 @@ impl Render for TabsStory {
|
|||
.w_full()
|
||||
.underline()
|
||||
.with_size(self.size)
|
||||
.with_menu(self.menu)
|
||||
.selected_index(self.active_tab_ix)
|
||||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
|
|
@ -172,6 +190,7 @@ impl Render for TabsStory {
|
|||
.w_full()
|
||||
.pill()
|
||||
.with_size(self.size)
|
||||
.with_menu(self.menu)
|
||||
.selected_index(self.active_tab_ix)
|
||||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
|
|
@ -192,6 +211,7 @@ impl Render for TabsStory {
|
|||
.w_full()
|
||||
.outline()
|
||||
.with_size(self.size)
|
||||
.with_menu(self.menu)
|
||||
.selected_index(self.active_tab_ix)
|
||||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
|
|
@ -212,6 +232,7 @@ impl Render for TabsStory {
|
|||
.w_full()
|
||||
.segmented()
|
||||
.with_size(self.size)
|
||||
.with_menu(self.menu)
|
||||
.selected_index(self.active_tab_ix)
|
||||
.on_click(cx.listener(|this, ix: &usize, window, cx| {
|
||||
this.set_active_tab(*ix, window, cx);
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ impl RenderOnce for ListItem {
|
|||
this.hover(|this| this.bg(cx.theme().list_hover))
|
||||
})
|
||||
})
|
||||
.when(self.disabled, |this| {
|
||||
this.cursor_not_allowed()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
|
|
|
|||
|
|
@ -64,17 +64,20 @@ enum PopupMenuItem {
|
|||
Item {
|
||||
icon: Option<Icon>,
|
||||
label: SharedString,
|
||||
disabled: bool,
|
||||
action: Option<Box<dyn Action>>,
|
||||
handler: Rc<dyn Fn(&mut Window, &mut App)>,
|
||||
},
|
||||
ElementItem {
|
||||
icon: Option<Icon>,
|
||||
disabled: bool,
|
||||
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
|
||||
handler: Rc<dyn Fn(&mut Window, &mut App)>,
|
||||
},
|
||||
Submenu {
|
||||
icon: Option<Icon>,
|
||||
label: SharedString,
|
||||
disabled: bool,
|
||||
menu: Entity<PopupMenu>,
|
||||
},
|
||||
}
|
||||
|
|
@ -173,17 +176,38 @@ impl PopupMenu {
|
|||
}
|
||||
|
||||
/// Add Menu Item
|
||||
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.add_menu_item(label, None, action);
|
||||
pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
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
|
||||
}
|
||||
|
||||
/// 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();
|
||||
self.menu_items.push(PopupMenuItem::Item {
|
||||
icon: None,
|
||||
label: label.into(),
|
||||
disabled,
|
||||
action: None,
|
||||
handler: Rc::new(move |_, cx| cx.open_url(&href)),
|
||||
});
|
||||
|
|
@ -192,43 +216,77 @@ impl PopupMenu {
|
|||
|
||||
/// Add Menu to open link
|
||||
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,
|
||||
label: impl Into<SharedString>,
|
||||
icon: impl Into<Icon>,
|
||||
href: impl Into<String>,
|
||||
disabled: bool,
|
||||
) -> Self {
|
||||
let href = href.into();
|
||||
self.menu_items.push(PopupMenuItem::Item {
|
||||
icon: Some(icon.into()),
|
||||
label: label.into(),
|
||||
disabled,
|
||||
action: None,
|
||||
handler: Rc::new(move |_, cx| cx.open_url(&href)),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Menu Item with Icon
|
||||
/// Add Menu Item with Icon.
|
||||
pub fn menu_with_icon(
|
||||
mut self,
|
||||
self,
|
||||
label: impl Into<SharedString>,
|
||||
icon: impl Into<Icon>,
|
||||
action: Box<dyn Action>,
|
||||
) -> 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
|
||||
}
|
||||
|
||||
/// Add Menu Item with check icon
|
||||
pub fn menu_with_check(
|
||||
mut self,
|
||||
self,
|
||||
label: impl Into<SharedString>,
|
||||
checked: bool,
|
||||
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 {
|
||||
if checked {
|
||||
self.add_menu_item(label, Some(IconName::Check.into()), action);
|
||||
self.add_menu_item(label, Some(IconName::Check.into()), action, disabled);
|
||||
} else {
|
||||
self.add_menu_item(label, None, action);
|
||||
self.add_menu_item(label, None, action, disabled);
|
||||
}
|
||||
|
||||
self
|
||||
|
|
@ -243,11 +301,40 @@ impl PopupMenu {
|
|||
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.
|
||||
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,
|
||||
icon: impl Into<Icon>,
|
||||
action: Box<dyn Action>,
|
||||
disabled: bool,
|
||||
builder: F,
|
||||
) -> Self
|
||||
where
|
||||
|
|
@ -258,6 +345,7 @@ impl PopupMenu {
|
|||
render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
|
||||
handler: self.wrap_handler(action),
|
||||
icon: Some(icon.into()),
|
||||
disabled,
|
||||
});
|
||||
self.has_icon = true;
|
||||
self
|
||||
|
|
@ -265,9 +353,24 @@ impl PopupMenu {
|
|||
|
||||
/// Add Menu Item with custom element render with check state
|
||||
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,
|
||||
checked: bool,
|
||||
action: Box<dyn Action>,
|
||||
disabled: bool,
|
||||
builder: F,
|
||||
) -> Self
|
||||
where
|
||||
|
|
@ -279,6 +382,7 @@ impl PopupMenu {
|
|||
render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
|
||||
handler: self.wrap_handler(action),
|
||||
icon: Some(IconName::Check.into()),
|
||||
disabled,
|
||||
});
|
||||
self.has_icon = true;
|
||||
} else {
|
||||
|
|
@ -286,6 +390,7 @@ impl PopupMenu {
|
|||
render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
|
||||
handler: self.wrap_handler(action),
|
||||
icon: None,
|
||||
disabled,
|
||||
});
|
||||
}
|
||||
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
|
||||
pub fn separator(mut self) -> Self {
|
||||
if self.menu_items.is_empty() {
|
||||
|
|
@ -330,6 +416,7 @@ impl PopupMenu {
|
|||
self
|
||||
}
|
||||
|
||||
/// Add a Submenu
|
||||
pub fn submenu(
|
||||
self,
|
||||
label: impl Into<SharedString>,
|
||||
|
|
@ -340,11 +427,36 @@ impl PopupMenu {
|
|||
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
|
||||
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,
|
||||
icon: Option<Icon>,
|
||||
label: impl Into<SharedString>,
|
||||
disabled: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
|
|
@ -359,6 +471,28 @@ impl PopupMenu {
|
|||
icon,
|
||||
label: label.into(),
|
||||
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
|
||||
}
|
||||
|
|
@ -556,8 +690,18 @@ impl PopupMenu {
|
|||
.my_0p5()
|
||||
.bg(cx.theme().muted),
|
||||
),
|
||||
PopupMenuItem::ElementItem { render, icon, .. } => this
|
||||
.on_click(cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)))
|
||||
PopupMenuItem::ElementItem {
|
||||
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(
|
||||
h_flex()
|
||||
.min_h(ITEM_HEIGHT)
|
||||
|
|
@ -570,30 +714,41 @@ impl PopupMenu {
|
|||
icon,
|
||||
label,
|
||||
action,
|
||||
disabled,
|
||||
..
|
||||
} => {
|
||||
let action = action.as_ref().map(|action| action.boxed_clone());
|
||||
let key = Self::render_keybinding(action, window, cx);
|
||||
|
||||
this.on_click(cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)))
|
||||
.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),
|
||||
),
|
||||
this.when(!disabled, |this| {
|
||||
this.on_click(
|
||||
cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),
|
||||
)
|
||||
})
|
||||
.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()
|
||||
.when(hovered, |this| {
|
||||
this.rounded(cx.theme().radius)
|
||||
|
|
|
|||
|
|
@ -379,15 +379,15 @@ impl TabVariant {
|
|||
pub struct Tab {
|
||||
id: ElementId,
|
||||
base: Div,
|
||||
label: SharedString,
|
||||
pub(super) label: SharedString,
|
||||
icon: Option<Icon>,
|
||||
prefix: Option<AnyElement>,
|
||||
suffix: Option<AnyElement>,
|
||||
children: Vec<AnyElement>,
|
||||
variant: TabVariant,
|
||||
size: Size,
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
pub(super) disabled: bool,
|
||||
pub(super) selected: bool,
|
||||
on_click: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
|
|
@ -583,7 +583,7 @@ impl RenderOnce for Tab {
|
|||
.flex_wrap()
|
||||
.items_center()
|
||||
.flex_shrink_0()
|
||||
.cursor_pointer()
|
||||
.when(!self.disabled, |this| this.cursor_pointer())
|
||||
.overflow_hidden()
|
||||
.h(height)
|
||||
.overflow_hidden()
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
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::{
|
||||
div, AnyElement, App, Div, Edges, ElementId, IntoElement, ParentElement, RenderOnce,
|
||||
ScrollHandle, Stateful, StatefulInteractiveElement as _, Styled, Window,
|
||||
div, impl_internal_actions, AnyElement, App, Corner, Div, Edges, ElementId, IntoElement,
|
||||
ParentElement, RenderOnce, ScrollHandle, Stateful, StatefulInteractiveElement as _,
|
||||
StyleRefinement, Styled, Window,
|
||||
};
|
||||
use gpui::{px, InteractiveElement};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{Tab, TabVariant};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct SelectTab(usize);
|
||||
|
||||
impl_internal_actions!(tab_bar, [SelectTab]);
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct TabBar {
|
||||
base: Stateful<Div>,
|
||||
|
|
@ -22,6 +30,7 @@ pub struct TabBar {
|
|||
selected_index: Option<usize>,
|
||||
variant: TabVariant,
|
||||
size: Size,
|
||||
menu: bool,
|
||||
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(),
|
||||
selected_index: None,
|
||||
on_click: None,
|
||||
menu: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +82,12 @@ impl TabBar {
|
|||
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
|
||||
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
|
||||
self.scroll_handle = Some(scroll_handle);
|
||||
|
|
@ -125,7 +141,7 @@ impl TabBar {
|
|||
}
|
||||
|
||||
impl Styled for TabBar {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
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
|
||||
.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()
|
||||
.flex()
|
||||
.items_center()
|
||||
|
|
@ -216,6 +243,7 @@ impl RenderOnce for TabBar {
|
|||
self.variant == TabVariant::Pill || self.variant == TabVariant::Segmented,
|
||||
|this| this.rounded(cx.theme().radius),
|
||||
)
|
||||
.paddings(paddings)
|
||||
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||
.child(
|
||||
h_flex()
|
||||
|
|
@ -226,30 +254,45 @@ impl RenderOnce for TabBar {
|
|||
this.track_scroll(&scroll_handle)
|
||||
})
|
||||
.gap(gap)
|
||||
.paddings(paddings)
|
||||
.children(
|
||||
self.children
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(ix, child)| {
|
||||
child
|
||||
.id(ix)
|
||||
.variant(self.variant)
|
||||
.with_size(self.size)
|
||||
.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)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
.when(self.suffix.is_some(), |this| {
|
||||
.children(self.children.into_iter().enumerate().map(|(ix, child)| {
|
||||
item_labels.push((child.label.clone(), child.disabled));
|
||||
child
|
||||
.id(ix)
|
||||
.variant(self.variant)
|
||||
.with_size(self.size)
|
||||
.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))
|
||||
})
|
||||
}))
|
||||
.when(self.suffix.is_some() || self.menu, |this| {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue