diff --git a/crates/ui-story/src/picker_story.rs b/crates/ui-story/src/picker_story.rs index 843eda83..a9e1cc4c 100644 --- a/crates/ui-story/src/picker_story.rs +++ b/crates/ui-story/src/picker_story.rs @@ -8,10 +8,11 @@ use ui::{ button::Button, h_flex, label::Label, + list::ListItem, picker::{Picker, PickerDelegate}, switch::{LabelSide, Switch}, theme::{ActiveTheme, Colorize}, - v_flex, Clickable as _, Disableable as _, StyledExt, + v_flex, Clickable as _, Disableable as _, Selectable, StyledExt, }; use super::story_case; @@ -23,7 +24,7 @@ pub struct ListItemDeletegate { } impl PickerDelegate for ListItemDeletegate { - type ListItem = Div; + type ListItem = ListItem; fn match_count(&self) -> usize { self.matches.len() @@ -43,17 +44,13 @@ impl PickerDelegate for ListItemDeletegate { selected: bool, cx: &mut ViewContext>, ) -> Option { + let is_selected = ix == self.selected_index; if let Some(item) = self.matches.get(ix) { - let list_item = div() + let list_item = ListItem::new(format!("item-{}", ix)) + .selected(is_selected) .py_1() .px_3() - .when(!selected, |this| { - this.hover(|this| this.bg(cx.theme().accent)) - }) - .child(item.clone()) - .text_base() - .text_color(cx.theme().foreground) - .when(selected, |this| this.bg(cx.theme().accent)); + .child(item.clone()); Some(list_item) } else { None diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index e69de29b..42469743 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -0,0 +1,9 @@ + + +pub struct DropdownItemDelegate { + items: Vec, +} + +pub struct Dropdown { + picker: +} \ No newline at end of file diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs index 8c5b2380..b178dcc4 100644 --- a/crates/ui/src/icon.rs +++ b/crates/ui/src/icon.rs @@ -1,5 +1,6 @@ -use gpui::{svg, IntoElement, RenderOnce, SharedString, Styled as _, Svg, WindowContext}; +use gpui::{svg, Element, IntoElement, RenderOnce, SharedString, Styled as _, Svg, WindowContext}; +#[derive(IntoElement)] pub enum IconName { Check, Minus, @@ -23,6 +24,12 @@ impl IconName { } } +impl RenderOnce for IconName { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + Icon::new(self) + } +} + #[derive(IntoElement)] pub struct Icon { base: Svg, diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 08508f30..d861c5de 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -16,7 +16,8 @@ pub mod theme; pub mod title_bar; pub use styled_ext::StyledExt; pub mod divider; -pub mod dropdown; +// pub mod dropdown; +pub mod list; pub mod picker; pub mod switch; pub mod tab; diff --git a/crates/ui/src/list/list_item.rs b/crates/ui/src/list/list_item.rs new file mode 100644 index 00000000..ac4e7cf5 --- /dev/null +++ b/crates/ui/src/list/list_item.rs @@ -0,0 +1,96 @@ +use gpui::{ + div, prelude::FluentBuilder as _, ClickEvent, Div, InteractiveElement, IntoElement, + MouseButton, MouseDownEvent, ParentElement, RenderOnce, SharedString, Stateful, + StatefulInteractiveElement as _, Style, Styled, WindowContext, +}; + +use crate::{h_flex, theme::ActiveTheme, Disableable, IconName, Selectable}; + +#[derive(IntoElement)] +pub struct ListItem { + base: Stateful
, + disabled: bool, + selected: bool, + on_click: Option>, + on_secondary_mouse_down: Option>, +} + +impl ListItem { + pub fn new(id: impl Into) -> Self { + Self { + base: h_flex().id(id.into()), + disabled: false, + selected: false, + on_click: None, + on_secondary_mouse_down: None, + } + } + + pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { + self.on_click = Some(Box::new(handler)); + self + } + + pub fn on_secondary_mouse_down( + mut self, + handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) -> Self { + self.on_secondary_mouse_down = Some(Box::new(handler)); + self + } +} + +impl Disableable for ListItem { + fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +impl Selectable for ListItem { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +impl Styled for ListItem { + fn style(&mut self) -> &mut gpui::StyleRefinement { + self.base.style() + } +} + +impl ParentElement for ListItem { + fn extend(&mut self, elements: impl IntoIterator) { + self.base.extend(elements) + } +} + +impl RenderOnce for ListItem { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + self.base + .w_full() + .relative() + .gap_x_2() + .text_base() + .text_color(cx.theme().foreground) + .when_some(self.on_click, |this, on_click| { + this.cursor_pointer().on_click(on_click) + }) + // Right click + .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { + this.on_mouse_down(MouseButton::Right, move |ev, cx| (on_mouse_down)(ev, cx)) + }) + .when(!self.selected, |this| { + this.hover(|this| this.bg(cx.theme().accent)) + }) + .when(self.selected, |this| this.bg(cx.theme().accent)) + .map(|this| { + if self.selected { + this.child(IconName::Check) + } else { + this.child(div()) + } + }) + } +} diff --git a/crates/ui/src/list/mod.rs b/crates/ui/src/list/mod.rs new file mode 100644 index 00000000..e51e8f56 --- /dev/null +++ b/crates/ui/src/list/mod.rs @@ -0,0 +1,3 @@ +mod list_item; + +pub use list_item::*;