diff --git a/crates/ui/src/menu/menu_item.rs b/crates/ui/src/menu/menu_item.rs new file mode 100644 index 00000000..65433719 --- /dev/null +++ b/crates/ui/src/menu/menu_item.rs @@ -0,0 +1,138 @@ +use crate::{h_flex, ActiveTheme, Disableable, Selectable, StyledExt}; +use gpui::{ + div, prelude::FluentBuilder as _, AnyElement, App, ClickEvent, ElementId, InteractiveElement, + IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce, + StatefulInteractiveElement as _, StyleRefinement, Styled, Window, +}; +use smallvec::SmallVec; + +#[derive(IntoElement)] +pub struct MenuItem { + id: ElementId, + style: StyleRefinement, + disabled: bool, + selected: bool, + on_click: Option>, + on_mouse_enter: Option>, + children: SmallVec<[AnyElement; 2]>, +} + +impl MenuItem { + pub fn new(id: impl Into) -> Self { + let id: ElementId = id.into(); + Self { + id: id.clone(), + style: StyleRefinement::default(), + disabled: false, + selected: false, + on_click: None, + on_mouse_enter: None, + children: SmallVec::new(), + } + } + + /// Set ListItem as the selected item style. + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + pub fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } + + pub fn on_click( + mut self, + handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_click = Some(Box::new(handler)); + self + } + + pub fn on_mouse_enter( + mut self, + handler: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_mouse_enter = Some(Box::new(handler)); + self + } +} + +impl Disableable for MenuItem { + fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +impl Selectable for MenuItem { + fn element_id(&self) -> &ElementId { + &self.id + } + + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + fn is_selected(&self) -> bool { + self.selected + } +} + +impl Styled for MenuItem { + fn style(&mut self) -> &mut gpui::StyleRefinement { + &mut self.style + } +} + +impl ParentElement for MenuItem { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements); + } +} + +impl RenderOnce for MenuItem { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + h_flex() + .id(self.id) + .gap_x_1() + .py_1() + .px_2() + .text_base() + .text_color(cx.theme().foreground) + .relative() + .items_center() + .justify_between() + .refine_style(&self.style) + .when(!self.disabled, |this| { + this.when(!self.selected, |this| { + this.hover(|this| { + this.bg(cx.theme().accent) + .text_color(cx.theme().accent_foreground) + }) + }) + .when_some(self.on_mouse_enter, |this, on_mouse_enter| { + this.on_mouse_move(move |ev, window, cx| (on_mouse_enter)(ev, window, cx)) + }) + .when_some(self.on_click, |this, on_click| { + this.on_mouse_down(MouseButton::Left, move |_, _, cx| { + cx.stop_propagation(); + }) + .on_click(on_click) + }) + }) + .when(self.disabled, |this| { + this.text_color(cx.theme().muted_foreground) + }) + .child( + h_flex() + .w_full() + .items_center() + .justify_between() + .gap_x_1() + .child(div().w_full().children(self.children)), + ) + } +} diff --git a/crates/ui/src/menu/mod.rs b/crates/ui/src/menu/mod.rs index 4a6cb823..f2e2729d 100644 --- a/crates/ui/src/menu/mod.rs +++ b/crates/ui/src/menu/mod.rs @@ -1,5 +1,7 @@ use gpui::App; +mod menu_item; + pub mod context_menu; pub mod popup_menu; diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index c1a21f2f..4b317d73 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -1,8 +1,9 @@ use crate::actions::{Cancel, Confirm, SelectNext, SelectPrev}; +use crate::menu::menu_item::MenuItem; use crate::scroll::{Scrollbar, ScrollbarState}; use crate::{ - button::Button, h_flex, list::ListItem, popover::Popover, v_flex, ActiveTheme, Icon, IconName, - Selectable, Sizable as _, + button::Button, h_flex, popover::Popover, v_flex, ActiveTheme, Icon, IconName, Selectable, + Sizable as _, }; use crate::{Kbd, StyledExt}; use gpui::{ @@ -686,7 +687,7 @@ impl PopupMenu { const EDGE_PADDING: Pixels = px(8.); const INNER_PADDING: Pixels = px(4.); - let this = ListItem::new(ix) + let this = MenuItem::new(ix) .relative() .text_sm() .py_0()