From a773a3cb9234ae7a4ce03c704dafe2e7c855cfa9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Dec 2023 16:07:55 -0500 Subject: [PATCH 1/3] Increase horizontal padding for inset `ListItem`s --- crates/ui2/src/components/list/list_item.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs index bdb5c2b854f2cfb7a04cbd70334d81a4daab98c4..797a6faba58025a013bdd5af60bb520ba675a841 100644 --- a/crates/ui2/src/components/list/list_item.rs +++ b/crates/ui2/src/components/list/list_item.rs @@ -134,7 +134,7 @@ impl RenderOnce for ListItem { // When an item is inset draw the indent spacing outside of the item .when(self.inset, |this| { this.ml(self.indent_level as f32 * self.indent_step_size) - .px_1() + .px_2() }) .when(!self.inset, |this| { this From dc09dbc98c2d27886146cdb1ab328d6f55031441 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Dec 2023 16:17:04 -0500 Subject: [PATCH 2/3] Use a `ListItem` as the empty state for `Picker`s --- crates/picker2/src/picker2.rs | 27 ++++++--------------- crates/ui2/src/components/list/list_item.rs | 11 ++++++++- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 8a75996f482c2c692adf95eed1015ef783d8ee1f..3cc9f8a1ade1046cad82780760ef02252a827874 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,11 +1,11 @@ use editor::Editor; use gpui::{ - div, prelude::*, rems, uniform_list, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, + div, prelude::*, uniform_list, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; -use ui::{prelude::*, v_stack, Color, Divider, Label}; +use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem}; use workspace::ModalView; pub struct Picker { @@ -229,21 +229,10 @@ impl Render for Picker { .px_3() .child(self.editor.clone()); - let empty_state = div().p_1().child( - h_stack() - // TODO: This number matches the height of the uniform list items. - // Align these two with a less magic number. - .h(rems(1.4375)) - .px_2() - .child(Label::new("No matches").color(Color::Muted)), - ); - div() .key_context("Picker") .size_full() - .when_some(self.width, |el, width| { - el.w(width) - }) + .when_some(self.width, |el, width| el.w(width)) .overflow_hidden() // This is a bit of a hack to remove the modal styling when we're rendering the `Picker` // as a part of a modal rather than the entire modal. @@ -257,9 +246,7 @@ impl Render for Picker { .on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::secondary_confirm)) - .child( - picker_editor - ) + .child(picker_editor) .child(Divider::horizontal()) .when(self.delegate.match_count() > 0, |el| { el.child( @@ -272,7 +259,6 @@ impl Render for Picker { self.delegate.match_count(), { let selected_index = self.delegate.selected_index(); - move |picker, visible_range, cx| { visible_range .map(|ix| { @@ -305,7 +291,10 @@ impl Render for Picker { }) .when(self.delegate.match_count() == 0, |el| { el.child( - empty_state + ListItem::new("empty_state") + .inset(true) + .disabled(true) + .child(Label::new("No matches").color(Color::Muted)), ) }) } diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs index 797a6faba58025a013bdd5af60bb520ba675a841..ca161b12f4790e6f9bee18a52b4dcb594e3deeca 100644 --- a/crates/ui2/src/components/list/list_item.rs +++ b/crates/ui2/src/components/list/list_item.rs @@ -8,6 +8,7 @@ use crate::{prelude::*, Disclosure}; #[derive(IntoElement)] pub struct ListItem { id: ElementId, + disabled: bool, selected: bool, indent_level: usize, indent_step_size: Pixels, @@ -32,6 +33,7 @@ impl ListItem { pub fn new(id: impl Into) -> Self { Self { id: id.into(), + disabled: false, selected: false, indent_level: 0, indent_step_size: px(12.), @@ -110,6 +112,13 @@ impl ListItem { } } +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; @@ -157,7 +166,7 @@ impl RenderOnce for ListItem { .gap_1() .px_2() .group("list_item") - .when(self.inset, |this| { + .when(self.inset && !self.disabled, |this| { this // TODO: Add focus state // .when(self.state == InteractionState::Focused, |this| { From 71a222abcb597e7b306aecb771e250f7587b5406 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Dec 2023 16:33:41 -0500 Subject: [PATCH 3/3] Use sparse spacing for `ListItem`s in `Picker`s --- .../src/collab_panel/channel_modal.rs | 3 +- .../src/collab_panel/contact_finder.rs | 3 +- .../command_palette2/src/command_palette.rs | 34 +++++++++++-------- crates/file_finder2/src/file_finder.rs | 21 +++++++----- .../src/language_selector.rs | 3 +- crates/outline2/src/outline.rs | 18 ++++++---- crates/picker2/src/picker2.rs | 3 +- .../project_symbols2/src/project_symbols.rs | 28 ++++++++------- .../recent_projects2/src/recent_projects.rs | 20 ++++++----- crates/storybook2/src/stories/picker.rs | 3 +- crates/theme_selector2/src/theme_selector.rs | 3 +- crates/ui2/src/components/list/list_item.rs | 18 ++++++++++ crates/vcs_menu2/src/lib.rs | 9 +++-- crates/welcome2/src/base_keymap_picker.rs | 5 +-- 14 files changed, 110 insertions(+), 61 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel/channel_modal.rs b/crates/collab_ui2/src/collab_panel/channel_modal.rs index f844c609cac074d6457613b81b654a8e0b1c5d3c..f373dbad1fbc2dbdbe6f022bac806c6cf2f22bf0 100644 --- a/crates/collab_ui2/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui2/src/collab_panel/channel_modal.rs @@ -11,7 +11,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; use std::sync::Arc; -use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem}; +use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing}; use util::TryFutureExt; use workspace::ModalView; @@ -366,6 +366,7 @@ impl PickerDelegate for ChannelModalDelegate { Some( ListItem::new(ix) .inset(true) + .spacing(ListItemSpacing::Sparse) .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) diff --git a/crates/collab_ui2/src/collab_panel/contact_finder.rs b/crates/collab_ui2/src/collab_panel/contact_finder.rs index 31d836279b6a17d4b119dcad0d562d66388f30ac..a38d22c096b0caffc5f6f3ba520ba79ecc2a8924 100644 --- a/crates/collab_ui2/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui2/src/collab_panel/contact_finder.rs @@ -6,7 +6,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use std::sync::Arc; use theme::ActiveTheme as _; -use ui::{prelude::*, Avatar, ListItem}; +use ui::{prelude::*, Avatar, ListItem, ListItemSpacing}; use util::{ResultExt as _, TryFutureExt}; use workspace::ModalView; @@ -153,6 +153,7 @@ impl PickerDelegate for ContactFinderDelegate { Some( ListItem::new(ix) .inset(true) + .spacing(ListItemSpacing::Sparse) .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 04efe1df53ae200273dc6d35e9231b55304571c2..05891bc0f199a83c2eb911179d042c259a46702a 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -11,7 +11,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; -use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem}; +use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; use util::{ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, ResultExt, @@ -308,20 +308,24 @@ impl PickerDelegate for CommandPaletteDelegate { let r#match = self.matches.get(ix)?; let command = self.commands.get(r#match.candidate_id)?; Some( - ListItem::new(ix).inset(true).selected(selected).child( - h_stack() - .w_full() - .justify_between() - .child(HighlightedLabel::new( - command.name.clone(), - r#match.positions.clone(), - )) - .children(KeyBinding::for_action_in( - &*command.action, - &self.previous_focus_handle, - cx, - )), - ), + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) + .child( + h_stack() + .w_full() + .justify_between() + .child(HighlightedLabel::new( + command.name.clone(), + r#match.positions.clone(), + )) + .children(KeyBinding::for_action_in( + &*command.action, + &self.previous_focus_handle, + cx, + )), + ), ) } } diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index 8faba99cb23a9bedffed5ab0c0f3a8147870b25a..369128a753754f3e53de84f2701fc74a947f9f1a 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -15,7 +15,7 @@ use std::{ }, }; use text::Point; -use ui::{prelude::*, HighlightedLabel, ListItem}; +use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing}; use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use workspace::{ModalView, Workspace}; @@ -714,13 +714,18 @@ impl PickerDelegate for FileFinderDelegate { self.labels_for_match(path_match, cx, ix); Some( - ListItem::new(ix).inset(true).selected(selected).child( - v_stack() - .child(HighlightedLabel::new(file_name, file_name_positions)) - .child( - HighlightedLabel::new(full_path, full_path_positions).color(Color::Muted), - ), - ), + ListItem::new(ix) + .spacing(ListItemSpacing::Sparse) + .inset(true) + .selected(selected) + .child( + v_stack() + .child(HighlightedLabel::new(file_name, file_name_positions)) + .child( + HighlightedLabel::new(full_path, full_path_positions) + .color(Color::Muted), + ), + ), ) } } diff --git a/crates/language_selector2/src/language_selector.rs b/crates/language_selector2/src/language_selector.rs index 8b28eab11d165cf7f7a893d39232b0ee76d830e2..3da8649c6e006fcc56a0255ef96babf76121b881 100644 --- a/crates/language_selector2/src/language_selector.rs +++ b/crates/language_selector2/src/language_selector.rs @@ -12,7 +12,7 @@ use language::{Buffer, LanguageRegistry}; use picker::{Picker, PickerDelegate}; use project::Project; use std::sync::Arc; -use ui::{prelude::*, HighlightedLabel, ListItem}; +use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ModalView, Workspace}; @@ -226,6 +226,7 @@ impl PickerDelegate for LanguageSelectorDelegate { Some( ListItem::new(ix) .inset(true) + .spacing(ListItemSpacing::Sparse) .selected(selected) .child(HighlightedLabel::new(label, mat.positions.clone())), ) diff --git a/crates/outline2/src/outline.rs b/crates/outline2/src/outline.rs index f3d1f327da7b587f48a5175ea3ebea3112d3bfb5..ba67f33b25de382bdf0b4c30ec8bbf7d635c44b3 100644 --- a/crates/outline2/src/outline.rs +++ b/crates/outline2/src/outline.rs @@ -18,7 +18,7 @@ use std::{ }; use theme::{color_alpha, ActiveTheme, ThemeSettings}; -use ui::{prelude::*, ListItem}; +use ui::{prelude::*, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::ModalView; @@ -296,12 +296,16 @@ impl PickerDelegate for OutlineViewDelegate { StyledText::new(outline_item.text.clone()).with_highlights(&text_style, highlights); Some( - ListItem::new(ix).inset(true).selected(selected).child( - div() - .text_ui() - .pl(rems(outline_item.depth as f32)) - .child(styled_text), - ), + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) + .child( + div() + .text_ui() + .pl(rems(outline_item.depth as f32)) + .child(styled_text), + ), ) } } diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 3cc9f8a1ade1046cad82780760ef02252a827874..d37638293ce16ce75b7d7fd52961f7aba5a1d6b0 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -5,7 +5,7 @@ use gpui::{ View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; -use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem}; +use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing}; use workspace::ModalView; pub struct Picker { @@ -293,6 +293,7 @@ impl Render for Picker { el.child( ListItem::new("empty_state") .inset(true) + .spacing(ListItemSpacing::Sparse) .disabled(true) .child(Label::new("No matches").color(Color::Muted)), ) diff --git a/crates/project_symbols2/src/project_symbols.rs b/crates/project_symbols2/src/project_symbols.rs index da67fc888f43deed6e470940a8deaaaf22030b16..d9bdd41b6e4d1915f8962cce89e7e68534ecb8f0 100644 --- a/crates/project_symbols2/src/project_symbols.rs +++ b/crates/project_symbols2/src/project_symbols.rs @@ -11,7 +11,7 @@ use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use theme::ActiveTheme; use util::ResultExt; use workspace::{ - ui::{v_stack, Color, Label, LabelCommon, LabelLike, ListItem, Selectable}, + ui::{v_stack, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable}, Workspace, }; @@ -237,17 +237,21 @@ impl PickerDelegate for ProjectSymbolsDelegate { ); Some( - ListItem::new(ix).inset(true).selected(selected).child( - // todo!() combine_syntax_and_fuzzy_match_highlights() - v_stack() - .child( - LabelLike::new().child( - StyledText::new(label) - .with_highlights(&cx.text_style().clone(), highlights), - ), - ) - .child(Label::new(path).color(Color::Muted)), - ), + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) + .child( + // todo!() combine_syntax_and_fuzzy_match_highlights() + v_stack() + .child( + LabelLike::new().child( + StyledText::new(label) + .with_highlights(&cx.text_style().clone(), highlights), + ), + ) + .child(Label::new(path).color(Color::Muted)), + ), ) } } diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index 13790a5b23ee47ff4cb343fab2fbe26b70cb16d0..1a38a5c2004044085dbfaed394255e7dd87c1a19 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -10,7 +10,7 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use std::sync::Arc; -use ui::{prelude::*, ListItem}; +use ui::{prelude::*, ListItem, ListItemSpacing}; use util::paths::PathExt; use workspace::{ notifications::simple_message_notification::MessageNotification, ModalView, Workspace, @@ -251,13 +251,17 @@ impl PickerDelegate for RecentProjectsDelegate { ); Some( - ListItem::new(ix).inset(true).selected(selected).child( - v_stack() - .child(highlighted_location.names) - .when(self.render_paths, |this| { - this.children(highlighted_location.paths) - }), - ), + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) + .child( + v_stack() + .child(highlighted_location.names) + .when(self.render_paths, |this| { + this.children(highlighted_location.paths) + }), + ), ) } } diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index 75aa7aed055c16e2303dc1b76870ba60c2aa7ab0..edaaaa70bd1e8522785ded0c0771963505dbe2fd 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -4,7 +4,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; use std::sync::Arc; -use ui::prelude::*; +use ui::{prelude::*, ListItemSpacing}; use ui::{Label, ListItem}; pub struct PickerStory { @@ -62,6 +62,7 @@ impl PickerDelegate for Delegate { Some( ListItem::new(ix) .inset(true) + .spacing(ListItemSpacing::Sparse) .selected(selected) .child(Label::new(candidate)), ) diff --git a/crates/theme_selector2/src/theme_selector.rs b/crates/theme_selector2/src/theme_selector.rs index e0cdc455557ed948d3f69aba1fa74809579cc794..ae067ce87cfe037f8b75f0ca2f8edabfdb2f3bf7 100644 --- a/crates/theme_selector2/src/theme_selector.rs +++ b/crates/theme_selector2/src/theme_selector.rs @@ -9,7 +9,7 @@ use picker::{Picker, PickerDelegate}; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; -use ui::{prelude::*, v_stack, ListItem}; +use ui::{prelude::*, v_stack, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; @@ -276,6 +276,7 @@ impl PickerDelegate for ThemeSelectorDelegate { Some( ListItem::new(ix) .inset(true) + .spacing(ListItemSpacing::Sparse) .selected(selected) .child(HighlightedLabel::new( theme_match.string.clone(), diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs index ca161b12f4790e6f9bee18a52b4dcb594e3deeca..6295e2940d3a086d77b4d1aadec98b8c44f1d765 100644 --- a/crates/ui2/src/components/list/list_item.rs +++ b/crates/ui2/src/components/list/list_item.rs @@ -5,11 +5,19 @@ use smallvec::SmallVec; use crate::{prelude::*, Disclosure}; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum ListItemSpacing { + #[default] + Dense, + Sparse, +} + #[derive(IntoElement)] pub struct ListItem { id: ElementId, disabled: bool, selected: bool, + spacing: ListItemSpacing, indent_level: usize, indent_step_size: Pixels, /// A slot for content that appears before the children, like an icon or avatar. @@ -35,6 +43,7 @@ impl ListItem { id: id.into(), disabled: false, selected: false, + spacing: ListItemSpacing::Dense, indent_level: 0, indent_step_size: px(12.), start_slot: None, @@ -50,6 +59,11 @@ impl ListItem { } } + pub fn spacing(mut self, spacing: ListItemSpacing) -> Self { + self.spacing = spacing; + self + } + pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { self.on_click = Some(Box::new(handler)); self @@ -165,6 +179,10 @@ impl RenderOnce for ListItem { .relative() .gap_1() .px_2() + .map(|this| match self.spacing { + ListItemSpacing::Dense => this, + ListItemSpacing::Sparse => this.py_1(), + }) .group("list_item") .when(self.inset && !self.disabled, |this| { this diff --git a/crates/vcs_menu2/src/lib.rs b/crates/vcs_menu2/src/lib.rs index 1483a2e81e8c6fe6470ed1b264cc4ff2a18a2a06..cece283d4ee3025ea1afe0071da43839774db3b3 100644 --- a/crates/vcs_menu2/src/lib.rs +++ b/crates/vcs_menu2/src/lib.rs @@ -8,7 +8,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; use std::sync::Arc; -use ui::{v_stack, HighlightedLabel, ListItem, Selectable}; +use ui::{v_stack, HighlightedLabel, ListItem, ListItemSpacing, Selectable}; use util::ResultExt; use workspace::{ModalView, Toast, Workspace}; @@ -134,6 +134,7 @@ impl BranchListDelegate { impl PickerDelegate for BranchListDelegate { type ListItem = ListItem; + fn placeholder_text(&self) -> Arc { "Select branch...".into() } @@ -281,8 +282,10 @@ impl PickerDelegate for BranchListDelegate { .collect(); Some( ListItem::new(SharedString::from(format!("vcs-menu-{ix}"))) - .start_slot(HighlightedLabel::new(shortened_branch_name, highlights)) - .selected(selected), + .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) + .start_slot(HighlightedLabel::new(shortened_branch_name, highlights)), ) } // fn render_header( diff --git a/crates/welcome2/src/base_keymap_picker.rs b/crates/welcome2/src/base_keymap_picker.rs index 7f799c633949dc3481c496c827909dceef658889..9655d7e13516e8ec14708bcb27142aed9f5a3e14 100644 --- a/crates/welcome2/src/base_keymap_picker.rs +++ b/crates/welcome2/src/base_keymap_picker.rs @@ -8,7 +8,7 @@ use picker::{Picker, PickerDelegate}; use project::Fs; use settings::{update_settings_file, Settings}; use std::sync::Arc; -use ui::{prelude::*, ListItem}; +use ui::{prelude::*, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; @@ -198,8 +198,9 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { Some( ListItem::new(ix) - .selected(selected) .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) .child(HighlightedLabel::new( keymap_match.string.clone(), keymap_match.positions.clone(),