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::{
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue