Update PopupMenu to support checked style. (#96)
<img width="256" alt="image" src="https://github.com/user-attachments/assets/87009870-e426-48e5-9860-ba91b4702ea4">
This commit is contained in:
parent
eb75df5d99
commit
3dd149e711
3 changed files with 61 additions and 13 deletions
|
|
@ -15,7 +15,10 @@ use ui::{
|
||||||
v_flex, Clickable, IconName, Size,
|
v_flex, Clickable, IconName, Size,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(popover_story, [Copy, Paste, Cut, SearchAll]);
|
actions!(
|
||||||
|
popover_story,
|
||||||
|
[Copy, Paste, Cut, SearchAll, ToggleWindowMode]
|
||||||
|
);
|
||||||
|
|
||||||
struct Form {
|
struct Form {
|
||||||
input1: View<TextInput>,
|
input1: View<TextInput>,
|
||||||
|
|
@ -97,8 +100,8 @@ impl FocusableView for PopoverStory {
|
||||||
impl Render for PopoverStory {
|
impl Render for PopoverStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let form = self.form.clone();
|
let form = self.form.clone();
|
||||||
let _focused = self.focus_handle.is_focused(cx);
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
let window_mode = self.window_mode;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
|
@ -176,7 +179,7 @@ impl Render for PopoverStory {
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(
|
.child(
|
||||||
Popover::new("popup-menu")
|
Popover::new("popup-menu")
|
||||||
.when(self.window_mode, |this| this.window_mode())
|
.when(window_mode, |this| this.window_mode())
|
||||||
.trigger(Button::new("popup-menu-1", cx).icon(IconName::Info))
|
.trigger(Button::new("popup-menu-1", cx).icon(IconName::Info))
|
||||||
.content(move |cx| {
|
.content(move |cx| {
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
|
|
@ -190,7 +193,9 @@ impl Render for PopoverStory {
|
||||||
IconName::Search,
|
IconName::Search,
|
||||||
"Search",
|
"Search",
|
||||||
Box::new(SearchAll),
|
Box::new(SearchAll),
|
||||||
);
|
)
|
||||||
|
.separator()
|
||||||
|
.menu_with_check("Check Menu", true, Box::new(SearchAll));
|
||||||
|
|
||||||
this
|
this
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,10 @@ impl Icon {
|
||||||
self.base = self.base.with_transformation(transformation);
|
self.base = self.base.with_transformation(transformation);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styled for Icon {
|
impl Styled for Icon {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use gpui::{
|
||||||
VisualContext as _, WindowContext,
|
VisualContext as _, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{h_flex, list::ListItem, theme::ActiveTheme, v_flex, Icon};
|
use crate::{h_flex, list::ListItem, theme::ActiveTheme, v_flex, Icon, IconName};
|
||||||
|
|
||||||
actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
|
actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
|
||||||
|
|
||||||
|
|
@ -42,6 +42,7 @@ pub struct PopupMenu {
|
||||||
window_handle: AnyWindowHandle,
|
window_handle: AnyWindowHandle,
|
||||||
action_context: Option<FocusHandle>,
|
action_context: Option<FocusHandle>,
|
||||||
menu_items: Vec<PopupMenuItem>,
|
menu_items: Vec<PopupMenuItem>,
|
||||||
|
has_icon: bool,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
min_width: Pixels,
|
min_width: Pixels,
|
||||||
max_width: Pixels,
|
max_width: Pixels,
|
||||||
|
|
@ -67,6 +68,7 @@ impl PopupMenu {
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
min_width: px(120.),
|
min_width: px(120.),
|
||||||
max_width: px(500.),
|
max_width: px(500.),
|
||||||
|
has_icon: false,
|
||||||
_subscriptions: [_on_blur_subscription],
|
_subscriptions: [_on_blur_subscription],
|
||||||
};
|
};
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
|
|
@ -108,6 +110,20 @@ impl PopupMenu {
|
||||||
self.add_menu_item(Some(icon.into()), label, action)
|
self.add_menu_item(Some(icon.into()), label, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add Menu Item with check icon
|
||||||
|
pub fn menu_with_check(
|
||||||
|
&mut self,
|
||||||
|
label: impl Into<SharedString>,
|
||||||
|
checked: bool,
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
) -> &mut Self {
|
||||||
|
if checked {
|
||||||
|
self.add_menu_item(Some(IconName::Check.into()), label, action)
|
||||||
|
} else {
|
||||||
|
self.add_menu_item(None, label, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn add_menu_item(
|
fn add_menu_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
|
|
@ -115,6 +131,10 @@ impl PopupMenu {
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
let window_handle = self.window_handle;
|
let window_handle = self.window_handle;
|
||||||
|
if icon.is_some() {
|
||||||
|
self.has_icon = true;
|
||||||
|
}
|
||||||
|
|
||||||
self.menu_items.push(PopupMenuItem::Item {
|
self.menu_items.push(PopupMenuItem::Item {
|
||||||
icon,
|
icon,
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
|
|
@ -210,6 +230,12 @@ impl FocusableView for PopupMenu {
|
||||||
|
|
||||||
impl Render for PopupMenu {
|
impl Render for PopupMenu {
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
|
let icon_placeholder = if self.has_icon {
|
||||||
|
Some(Icon::empty())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("PopupMenu")
|
.key_context("PopupMenu")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
|
@ -220,23 +246,36 @@ impl Render for PopupMenu {
|
||||||
.on_mouse_down_out(cx.listener(|this, _, cx| this.dismiss(&Dismiss, cx)))
|
.on_mouse_down_out(cx.listener(|this, _, cx| this.dismiss(&Dismiss, cx)))
|
||||||
.max_h(self.max_width)
|
.max_h(self.max_width)
|
||||||
.min_w(self.min_width)
|
.min_w(self.min_width)
|
||||||
.p_1p5()
|
.p_0p5()
|
||||||
.gap_y_0p5()
|
.gap_y_0p5()
|
||||||
.children(self.menu_items.iter_mut().enumerate().map(|(ix, item)| {
|
.children(self.menu_items.iter_mut().enumerate().map(|(ix, item)| {
|
||||||
let this = ListItem::new(("menu-item", ix))
|
let this = ListItem::new(("menu-item", ix))
|
||||||
|
.p_0()
|
||||||
.on_click(cx.listener(move |this, _, cx| this.on_click(ix, cx)));
|
.on_click(cx.listener(move |this, _, cx| this.on_click(ix, cx)));
|
||||||
match item {
|
match item {
|
||||||
PopupMenuItem::Separator => this
|
PopupMenuItem::Separator => this.disabled(true).child(
|
||||||
.disabled(true)
|
div()
|
||||||
.child(div().h(px(1.)).m_1().border_0().bg(cx.theme().border)),
|
.h(px(1.))
|
||||||
|
.w_full()
|
||||||
|
.my_px()
|
||||||
|
.border_0()
|
||||||
|
.bg(cx.theme().border),
|
||||||
|
),
|
||||||
PopupMenuItem::Item { icon, label, .. } => {
|
PopupMenuItem::Item { icon, label, .. } => {
|
||||||
this.py_1().px_2().text_sm().rounded(px(4.)).child(
|
this.py(px(3.)).px_3().rounded_md().text_sm().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
.items_center()
|
||||||
.children(icon.clone())
|
.map(|this| {
|
||||||
.child(label.clone()),
|
this.child(div().absolute().text_sm().map(|this| {
|
||||||
|
if let Some(icon) = icon {
|
||||||
|
this.child(icon.clone())
|
||||||
|
} else {
|
||||||
|
this.child(icon_placeholder.clone().unwrap())
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.child(div().pl_6().pr_2().child(label.clone())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue