diff --git a/crates/story/src/button_story.rs b/crates/story/src/button_story.rs index 3f03fd14..c15c4107 100644 --- a/crates/story/src/button_story.rs +++ b/crates/story/src/button_story.rs @@ -1,5 +1,5 @@ use gpui::{ - ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext, + px, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext, VisualContext as _, WindowContext, }; @@ -267,6 +267,12 @@ impl Render for ButtonStory { .icon(IconName::Close) .size(Size::XSmall) .style(ButtonStyle::Danger), + ) + .child( + Button::new("icon-button-9", cx) + .icon(IconName::Heart) + .size(px(24.)) + .style(ButtonStyle::Ghost), ), ) } diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index 990f0dde..7d492ea2 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -91,7 +91,7 @@ impl RenderOnce for CompanyListItem { .text_color(text_color) .child( v_flex() - .gap_2() + .gap_1() .max_w(px(500.)) .overflow_x_hidden() .flex_nowrap() @@ -274,32 +274,5 @@ impl Render for ListStory { .border_color(cx.theme().border) .child(self.company_list.clone()), ) - // .child( - // div() - // .invisible() - // .flex_1() - // .size_full() - // .border_1() - // .border_color(cx.theme().border) - // .py_1() - // .px_4() - // .rounded_md() - // .when_some(self.selected_company.clone(), |this, company| { - // this.child( - // div() - // .flex_1() - // .gap_2() - // .child( - // h_flex() - // .items_start() - // .justify_between() - // .child(div().text_3xl().mb_6().child(company.name.clone())) - // .child(format!("{:.2}", company.last_done)), - // ) - // .child(company.industry.clone()) - // .child(company.description.clone()), - // ) - // }), - // ) } } diff --git a/crates/story/src/picker_story.rs b/crates/story/src/picker_story.rs index 55d74a1a..43759afa 100644 --- a/crates/story/src/picker_story.rs +++ b/crates/story/src/picker_story.rs @@ -8,7 +8,7 @@ use gpui::{ }; use ui::{ - button::Button, + button::{Button, ButtonStyle}, h_flex, list::{List, ListDelegate, ListItem}, theme::ActiveTheme as _, @@ -54,7 +54,7 @@ impl ListDelegate for ListItemDeletegate { }) } - fn render_item(&self, ix: usize, _cx: &mut ViewContext>) -> Option { + fn render_item(&self, ix: usize, _: &mut ViewContext>) -> Option { let selected = ix == self.selected_index; if let Some(item) = self.matches.get(ix) { let list_item = ListItem::new(("item", ix)) @@ -62,7 +62,24 @@ impl ListDelegate for ListItemDeletegate { .selected(selected) .py_1() .px_3() - .child(item.to_string()); + .child( + h_flex() + .items_center() + .justify_between() + .child(item.to_string()), + ) + .suffix(|cx| { + Button::new("like", cx) + .icon(IconName::Heart) + .style(ButtonStyle::Ghost) + .size(px(18.)) + .on_click(move |_, cx| { + cx.stop_propagation(); + cx.prevent_default(); + + println!("You have clicked like."); + }) + }); Some(list_item) } else { None diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index fcf06dca..a633ae65 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -184,6 +184,10 @@ impl RenderOnce for Button { let style: ButtonStyle = self.style; let normal_style = style.normal(cx); let focused = self.focus_handle.is_focused(cx); + let icon_size = match self.size { + Size::Size(v) => Size::Size(v * 0.75), + _ => self.size, + }; self.base .id(self.id) @@ -191,6 +195,7 @@ impl RenderOnce for Button { .flex() .items_center() .justify_center() + .cursor_pointer() .when_some(self.width, |this, width| this.w(width)) .when_some(self.height, |this, height| this.h(height)) .map(|this| { @@ -205,6 +210,7 @@ impl RenderOnce for Button { } else { // Normal Button match self.size { + Size::Size(size) => this.p(size * 0.2), Size::XSmall => this.px_1().py_1().h_5(), Size::Small => this.px_3().py_2().h_6(), _ => this.px_4().py_2().h_8(), @@ -239,8 +245,9 @@ impl RenderOnce for Button { .when_some( self.on_click.filter(|_| !self.disabled), |this, on_click| { - this.on_mouse_down(MouseButton::Left, |_, cx| cx.prevent_default()) + this.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) .on_click(move |event, cx| { + cx.prevent_default(); cx.stop_propagation(); (on_click)(event, cx) }) @@ -267,7 +274,7 @@ impl RenderOnce for Button { .text_color(text_color) .when(!self.loading, |this| { this.when_some(self.icon, |this, icon| { - this.child(div().text_color(text_color).child(icon.size(self.size))) + this.child(div().text_color(text_color).child(icon.size(icon_size))) }) }) .when(self.loading, |this| { diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 9aabe07b..fd5a99c2 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -111,8 +111,8 @@ pub struct TextInput { text: SharedString, history: History, blink_cursor: Model, - prefix: Option AnyElement + 'static>>, - suffix: Option AnyElement + 'static>>, + prefix: Option AnyElement + 'static>>, + suffix: Option AnyElement + 'static>>, loading: bool, placeholder: SharedString, selected_range: Range, @@ -237,7 +237,7 @@ impl TextInput { /// Set the prefix element of the input field, for example a search Icon. pub fn prefix(mut self, builder: F) -> Self where - F: Fn(&WindowContext) -> E + 'static, + F: Fn(&mut WindowContext) -> E + 'static, E: IntoElement, { self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element())); @@ -247,7 +247,7 @@ impl TextInput { /// Set the suffix element of the input field, for example a clear button. pub fn suffix(mut self, builder: F) -> Self where - F: Fn(&WindowContext) -> E + 'static, + F: Fn(&mut WindowContext) -> E + 'static, E: IntoElement, { self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element())); diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 5b9b1263..be0fe0de 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -310,6 +310,7 @@ where .when_some(self.query_input.clone(), |this, input| { this.child( div() + .py_1() .px_2() .border_b_1() .border_color(cx.theme().border) diff --git a/crates/ui/src/list/list_item.rs b/crates/ui/src/list/list_item.rs index b1937840..5d15f1b2 100644 --- a/crates/ui/src/list/list_item.rs +++ b/crates/ui/src/list/list_item.rs @@ -1,8 +1,9 @@ use gpui::{ - div, prelude::FluentBuilder as _, ClickEvent, Div, ElementId, InteractiveElement, IntoElement, - MouseButton, MouseDownEvent, ParentElement, Pixels, RenderOnce, Stateful, + div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement, + IntoElement, MouseButton, MouseDownEvent, ParentElement, RenderOnce, Stateful, StatefulInteractiveElement as _, Styled, WindowContext, }; +use smallvec::SmallVec; use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable}; @@ -12,21 +13,23 @@ pub struct ListItem { disabled: bool, selected: bool, check_icon: Option, - border_radius: Option, on_click: Option>, on_secondary_mouse_down: Option>, + suffix: Option AnyElement + 'static>>, + children: SmallVec<[AnyElement; 2]>, } impl ListItem { pub fn new(id: impl Into) -> Self { Self { - base: div().id(id.into()), + base: h_flex().id(id.into()).gap_x_1().py_1().px_2().text_base(), disabled: false, selected: false, on_click: None, on_secondary_mouse_down: None, check_icon: None, - border_radius: None, + suffix: None, + children: SmallVec::new(), } } @@ -45,8 +48,13 @@ impl ListItem { self } - pub fn rounded(mut self, r: impl Into) -> Self { - self.border_radius = Some(r.into()); + /// Set the suffix element of the input field, for example a clear button. + pub fn suffix(mut self, builder: F) -> Self + where + F: Fn(&mut WindowContext) -> E + 'static, + E: IntoElement, + { + self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element())); self } @@ -86,21 +94,17 @@ impl Styled for ListItem { impl ParentElement for ListItem { fn extend(&mut self, elements: impl IntoIterator) { - self.base.extend(elements) + self.children.extend(elements); } } impl RenderOnce for ListItem { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_flex() - .id("list-item") + self.base + .text_color(cx.theme().foreground) .relative() - .gap_x_2() .items_center() .justify_between() - .text_base() - .text_color(cx.theme().foreground) - .when_some(self.border_radius, |this, r| this.rounded(r)) .when_some(self.on_click, |this, on_click| { if !self.disabled { this.cursor_pointer().on_click(on_click) @@ -108,9 +112,9 @@ impl RenderOnce for ListItem { this } }) - .when(self.selected, |this| this.bg(cx.theme().accent)) + .when(self.selected, |this| this.bg(cx.theme().list_item_active)) .when(!self.selected && !self.disabled, |this| { - this.hover(|this| this.bg(cx.theme().accent)) + this.hover(|this| this.bg(cx.theme().list_item_hover)) }) // Right click .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { @@ -120,13 +124,25 @@ impl RenderOnce for ListItem { this } }) - .child(self.base.w_full()) - .when(self.selected, |this| { - if let Some(icon) = self.check_icon { - this.child(icon.text_color(cx.theme().muted_foreground).mr_2()) - } else { - this - } - }) + .child( + h_flex() + .w_full() + .items_center() + .justify_between() + .gap_x_1() + .child(div().w_full().children(self.children)) + .when_some(self.check_icon, |this, icon| { + this.child( + div() + .w_5() + .items_center() + .justify_center() + .when(self.selected, |this| { + this.child(icon.text_color(cx.theme().muted_foreground)) + }), + ) + }), + ) + .when_some(self.suffix, |this, suffix| this.child(suffix(cx))) } } diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 5ea72c13..91851452 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -329,6 +329,8 @@ pub struct Theme { pub progress_bar: Hsla, pub slider_bar: Hsla, pub slider_thumb: Hsla, + pub list_item_active: Hsla, + pub list_item_hover: Hsla, } impl Global for Theme {} @@ -394,6 +396,8 @@ impl From for Theme { progress_bar: colors.primary, slider_bar: colors.primary, slider_thumb: colors.background, + list_item_active: colors.secondary_active, + list_item_hover: colors.secondary, } } }