{
+ None
}
fn try_get_preview_data_for_match(&self, _cx: &App) -> Option
{
@@ -365,6 +355,17 @@ pub trait PickerDelegate: Sized + 'static {
cx: &mut Context>,
) -> Option;
+ fn render_match_with_checkbox(
+ &self,
+ _ix: usize,
+ _selected: bool,
+ _checkbox: AnyElement,
+ _window: &mut Window,
+ _cx: &mut Context>,
+ ) -> Option {
+ None
+ }
+
fn render_header(
&self,
_window: &mut Window,
@@ -922,6 +923,7 @@ impl Picker {
cx: &mut Context,
) {
if !self.delegate.supports_multi_select() {
+ cx.propagate();
return;
}
self.select_instead_of_open = !self.select_instead_of_open;
@@ -1263,12 +1265,55 @@ impl Picker {
let is_multi_selected = supports_multi_select && self.delegate.is_item_selected(ix);
let multi_select_active = supports_multi_select && self.select_instead_of_open;
+ let item_with_checkbox = if multi_select_active && selectable {
+ let checkbox = self
+ .render_multi_select_indicator(ix, is_multi_selected, cx)
+ .into_any_element();
+ self.delegate.render_match_with_checkbox(
+ ix,
+ ix == self.delegate.selected_index(),
+ checkbox,
+ window,
+ cx,
+ )
+ } else {
+ None
+ };
+
+ let use_fallback_indicator =
+ multi_select_active && selectable && item_with_checkbox.is_none();
+ let focus_handle = self.focus_handle(cx);
+
div()
.id(("item", ix))
.when(selectable, |this| this.cursor_pointer())
- .when(selectable && multi_select_active, |this| {
+ .when(use_fallback_indicator, |this| {
this.hover(|s| s.bg(cx.theme().colors().ghost_element_hover))
})
+ .when(multi_select_active && selectable, |this| {
+ this.tooltip(Tooltip::element(move |_window, cx| {
+ h_flex()
+ .gap_2()
+ .child(
+ h_flex()
+ .gap_1()
+ .child(KeyBinding::for_action_in(
+ &MultiSelectNext,
+ &focus_handle,
+ cx,
+ ))
+ .child(Label::new("Select")),
+ )
+ .child(Divider::vertical())
+ .child(
+ h_flex()
+ .gap_1()
+ .child(KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx))
+ .child(Label::new("Open")),
+ )
+ .into_any_element()
+ }))
+ })
.child(
canvas(
move |bounds, _window, _cx| {
@@ -1310,17 +1355,19 @@ impl Picker {
}))
})
.map(|row| {
- // Pickers without multi-select keep their element tree
- // unchanged.
- if supports_multi_select {
+ if let Some(item) = item_with_checkbox {
+ row.child(item)
+ } else if supports_multi_select {
row.child(
h_flex()
// Headers and separators cannot be part of the
// selection, so they get no indicator.
- .when(multi_select_active && selectable, |this| {
- this.child(
- self.render_multi_select_indicator(is_multi_selected, cx),
- )
+ .when(use_fallback_indicator, |this| {
+ this.child(self.render_multi_select_indicator(
+ ix,
+ is_multi_selected,
+ cx,
+ ))
})
.children(self.delegate.render_match(
ix,
@@ -1349,39 +1396,21 @@ impl Picker {
)
}
- /// The checkbox in front of items while in multi-select mode.
fn render_multi_select_indicator(
&self,
+ ix: usize,
is_selected: bool,
cx: &mut Context,
) -> impl IntoElement {
- h_flex()
- .w_6()
- .flex_none()
- .justify_center()
- .items_center()
- .child(
- div()
- .size_4()
- .flex_none()
- .rounded_sm()
- .border_1()
- .border_color(if is_selected {
- cx.theme().colors().border_focused
- } else {
- cx.theme().colors().border
- })
- .flex()
- .items_center()
- .justify_center()
- .when(is_selected, |this| {
- this.bg(cx.theme().colors().element_selected).child(
- Icon::new(IconName::Check)
- .size(IconSize::Small)
- .color(Color::Accent),
- )
- }),
- )
+ Checkbox::new(("picker-multi-select-checkbox", ix), is_selected.into())
+ .fill()
+ .elevation(ui::ElevationIndex::ModalSurface)
+ .on_click(cx.listener(move |this, _: &ui::ToggleState, window, cx| {
+ // Don't let the row's click handler also toggle the item.
+ cx.stop_propagation();
+ this.delegate.toggle_item_selected(ix, window, cx);
+ cx.notify();
+ }))
}
fn render_element_container(&self, cx: &mut Context) -> impl IntoElement {
diff --git a/crates/picker/src/render.rs b/crates/picker/src/render.rs
index 499b0982ee..e61425466c 100644
--- a/crates/picker/src/render.rs
+++ b/crates/picker/src/render.rs
@@ -10,12 +10,17 @@ use ui::{
use crate::shape::Shape;
use crate::{
- ElementContainer, Picker, PickerDelegate, PickerEditorPosition, Preview,
+ ElementContainer, Picker, PickerDelegate, PickerEditorPosition, Preview, ToggleMultiSelect,
head::Head,
preview::Layout,
render::window_controls::{Bottom, Left, LeftCorner, Middle, Right, RightCorner},
};
use crate::{persistence, preview};
+use gpui::Action as _;
+use gpui::Focusable as _;
+use std::sync::Arc;
+use ui::{Divider, Tooltip, prelude::*};
+use ui_input::ErasedEditor;
pub mod window_controls;
@@ -88,6 +93,54 @@ impl Render for Picker {
}
impl Picker {
+ fn render_editor(
+ &self,
+ editor: &Arc,
+ window: &mut Window,
+ cx: &mut Context,
+ ) -> gpui::Div {
+ if let Some(custom) = self.delegate.render_editor(editor, window, cx) {
+ return custom;
+ }
+ let editor_position = self.delegate.editor_position();
+
+ v_flex()
+ .when(editor_position == PickerEditorPosition::End, |this| {
+ this.child(Divider::horizontal())
+ })
+ .child(
+ h_flex()
+ .h_9()
+ .px_2p5()
+ .flex_none()
+ .overflow_hidden()
+ .child(div().flex_1().child(editor.render(window, cx)))
+ .children(self.delegate.searchbar_trailer(window, cx))
+ .when(self.delegate.supports_multi_select(), |this| {
+ this.child(self.render_multi_select_toggle(cx))
+ }),
+ )
+ .when(editor_position == PickerEditorPosition::Start, |this| {
+ this.child(Divider::horizontal())
+ })
+ }
+
+ /// The multi-select toggle is picker-owned so it can reflect the mode,
+ /// which delegates don't know about.
+ fn render_multi_select_toggle(&self, cx: &mut Context) -> impl IntoElement {
+ let active = self.select_instead_of_open;
+ let focus_handle = self.focus_handle(cx);
+ IconButton::new("picker-multi-select-toggle", IconName::FileMultiple)
+ .icon_size(IconSize::Small)
+ .toggle_state(active)
+ .tooltip(move |_window, cx| {
+ Tooltip::for_action_in("Toggle Multi Select", &ToggleMultiSelect, &focus_handle, cx)
+ })
+ .on_click(cx.listener(|_, _, window, cx| {
+ window.dispatch_action(ToggleMultiSelect.boxed_clone(), cx);
+ }))
+ }
+
pub(crate) fn render_results(
&self,
window: &mut Window,
@@ -154,13 +207,14 @@ impl Picker {
.children(match &self.head {
Head::Editor(editor) => {
if editor_position == PickerEditorPosition::Start {
- Some(h_flex().w_full().child(
- div().flex_1().child(self.delegate.render_editor(
- &editor.clone(),
- window,
- cx,
- )),
- ))
+ let editor = editor.clone();
+ Some(
+ h_flex().w_full().child(
+ div()
+ .flex_1()
+ .child(self.render_editor(&editor, window, cx)),
+ ),
+ )
} else {
None
}
@@ -220,7 +274,8 @@ impl Picker {
.children(match &self.head {
Head::Editor(editor) => {
if editor_position == PickerEditorPosition::End {
- Some(self.delegate.render_editor(&editor.clone(), window, cx))
+ let editor = editor.clone();
+ Some(self.render_editor(&editor, window, cx))
} else {
None
}
diff --git a/crates/picker/src/shape.rs b/crates/picker/src/shape.rs
index 217ff6a8fc..034834ef78 100644
--- a/crates/picker/src/shape.rs
+++ b/crates/picker/src/shape.rs
@@ -1,6 +1,6 @@
use gpui::Window;
use gpui::{Pixels, Rems, Size};
-use ui::{Div, Styled};
+use ui::{Div, Styled, rems_from_px};
use crate::preview::Layout;
@@ -242,12 +242,12 @@ impl Default for SizeBounds {
// over the lower bar so clear another 5 rems there.
max_height: (RelativeHeight::FULL - Rems(10.0)) * 0.95,
min_results: Size {
- width: Rems(15.0),
- height: Rems(20.0),
+ width: rems_from_px(280.),
+ height: rems_from_px(320.),
},
min_preview: Size {
- width: Rems(8.0),
- height: Rems(6.0),
+ width: rems_from_px(128.),
+ height: rems_from_px(96.),
},
}
}
diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml
index 013e00e964..99a563aff4 100644
--- a/crates/recent_projects/Cargo.toml
+++ b/crates/recent_projects/Cargo.toml
@@ -47,7 +47,6 @@ settings.workspace = true
task.workspace = true
telemetry.workspace = true
ui.workspace = true
-ui_input.workspace = true
util.workspace = true
workspace.workspace = true
worktree.workspace = true
diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs
index 781d85c874..369cff1cc6 100644
--- a/crates/recent_projects/src/recent_projects.rs
+++ b/crates/recent_projects/src/recent_projects.rs
@@ -36,7 +36,6 @@ use project::{Worktree, git_store::Repository};
pub use remote_connections::RemoteSettings;
pub use remote_servers::RemoteServerProjects;
use settings::{DefaultOpenBehavior, Settings, WorktreeId};
-use ui_input::ErasedEditor;
use workspace::ProjectGroupKey;
use dev_container::{DevContainerContext, find_devcontainer_configs};
@@ -938,22 +937,6 @@ impl PickerDelegate for RecentProjectsDelegate {
"Search projects…".into()
}
- fn render_editor(
- &self,
- editor: &Arc,
- window: &mut Window,
- cx: &mut Context>,
- ) -> Div {
- h_flex()
- .flex_none()
- .h_9()
- .px_2p5()
- .justify_between()
- .border_b_1()
- .border_color(cx.theme().colors().border_variant)
- .child(editor.render(window, cx))
- }
-
fn match_count(&self) -> usize {
self.filtered_entries.len()
}
diff --git a/crates/recent_projects/src/sidebar_recent_projects.rs b/crates/recent_projects/src/sidebar_recent_projects.rs
index 8955e63793..006fb7f35d 100644
--- a/crates/recent_projects/src/sidebar_recent_projects.rs
+++ b/crates/recent_projects/src/sidebar_recent_projects.rs
@@ -12,7 +12,6 @@ use picker::{
use remote::RemoteConnectionOptions;
use settings::Settings;
use ui::{ButtonLike, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
-use ui_input::ErasedEditor;
use util::{ResultExt, paths::PathExt};
use workspace::{
MultiWorkspace, OpenMode, OpenOptions, ProjectGroupKey, RecentWorkspace,
@@ -144,22 +143,6 @@ impl PickerDelegate for SidebarRecentProjectsDelegate {
"Search projects…".into()
}
- fn render_editor(
- &self,
- editor: &Arc,
- window: &mut Window,
- cx: &mut Context>,
- ) -> Div {
- h_flex()
- .flex_none()
- .h_9()
- .px_2p5()
- .justify_between()
- .border_b_1()
- .border_color(cx.theme().colors().border_variant)
- .child(editor.render(window, cx))
- }
-
fn match_count(&self) -> usize {
self.filtered_workspaces.len()
}
diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs
index dc71ec5c4a..cef989dbdf 100644
--- a/crates/search/src/search.rs
+++ b/crates/search/src/search.rs
@@ -5,8 +5,7 @@ use editor::SearchSettings;
use gpui::{Action, App, ClickEvent, Entity, FocusHandle, IntoElement, actions};
use project::search::SearchQuery;
pub use project_search::ProjectSearchView;
-use ui::{ButtonStyle, IconButton, IconButtonShape};
-use ui::{Tooltip, prelude::*};
+use ui::{IconButtonShape, Tooltip, prelude::*};
use util::paths::PathMatcher;
use workspace::notifications::NotificationId;
use workspace::{Toast, Workspace};
@@ -115,10 +114,10 @@ impl SearchOption {
pub fn icon(&self) -> ui::IconName {
match self {
- SearchOption::WholeWord => ui::IconName::WholeWord,
- SearchOption::CaseSensitive => ui::IconName::CaseSensitive,
- SearchOption::IncludeIgnored => ui::IconName::Sliders,
- SearchOption::Regex => ui::IconName::Regex,
+ SearchOption::WholeWord => IconName::WholeWord,
+ SearchOption::CaseSensitive => IconName::CaseSensitive,
+ SearchOption::IncludeIgnored => IconName::FileIgnored,
+ SearchOption::Regex => IconName::Regex,
_ => panic!("{self:?} is not a named SearchOption"),
}
}
@@ -141,6 +140,7 @@ impl SearchOption {
) -> impl IntoElement {
let action = self.to_toggle_action();
let label = self.label();
+
IconButton::new(
(label, matches!(search_source, SearchSource::Buffer) as u32),
self.icon(),
@@ -162,7 +162,6 @@ impl SearchOption {
}))
}
})
- .style(ButtonStyle::Subtle)
.shape(IconButtonShape::Square)
.toggle_state(active.contains(self.as_options()))
.tooltip(move |_window, cx| Tooltip::for_action_in(label, action, &focus_handle, cx))
diff --git a/crates/search/src/text_finder/delegate.rs b/crates/search/src/text_finder/delegate.rs
index 195223782f..8acc4338d4 100644
--- a/crates/search/src/text_finder/delegate.rs
+++ b/crates/search/src/text_finder/delegate.rs
@@ -45,8 +45,8 @@ use smol::future::yield_now;
use text::Anchor;
use theme_settings::ThemeSettings;
use ui::{
- Disclosure, Divider, FluentBuilder, IconButtonShape, ListItem, ListItemSpacing, Toggleable,
- Tooltip, prelude::*, text_for_keystroke,
+ Disclosure, Divider, FluentBuilder, ListItem, ListItemSpacing, Toggleable, Tooltip, prelude::*,
+ text_for_keystroke,
};
use util::ResultExt;
use workspace::SplitDirection;
@@ -719,12 +719,12 @@ impl PickerDelegate for Delegate {
let label = option.label();
let focus_handle = focus_handle.clone();
let picker = picker.clone();
+
IconButton::new(
("text-finder-search-option", option as usize),
option.icon(),
)
.icon_size(IconSize::Small)
- .shape(IconButtonShape::Square)
.toggle_state(active.contains(options))
.tooltip(move |_window, cx| Tooltip::for_action_in(label, action, &focus_handle, cx))
.on_click(move |_, window, cx| {
@@ -737,8 +737,9 @@ impl PickerDelegate for Delegate {
Some(
h_flex()
- .gap_1()
+ .gap_px()
.children(filter_buttons)
+ .child(Divider::vertical().ml_px().mr_0p5())
.children(picker::parts::project_scan_indicator(
self.active_query.is_some(),
self.project(cx),
@@ -772,7 +773,7 @@ impl PickerDelegate for Delegate {
picker::PickerAction::separator(),
picker::PickerAction::button(
if self.selected_matches.len() > 1 {
- "Open multiple"
+ "Open Multiple"
} else {
"Open File"
},
@@ -1009,9 +1010,33 @@ impl PickerDelegate for Delegate {
&self,
ix: usize,
selected: bool,
- _: &mut Window,
+ window: &mut Window,
cx: &mut Context>,
) -> Option {
+ self.render_match_impl(ix, selected, None, window, cx)
+ }
+
+ fn render_match_with_checkbox(
+ &self,
+ ix: usize,
+ selected: bool,
+ checkbox: AnyElement,
+ window: &mut Window,
+ cx: &mut Context>,
+ ) -> Option {
+ self.render_match_impl(ix, selected, Some(checkbox), window, cx)
+ }
+}
+
+impl Delegate {
+ fn render_match_impl(
+ &self,
+ ix: usize,
+ selected: bool,
+ checkbox: Option,
+ _: &mut Window,
+ cx: &mut Context>,
+ ) -> Option {
match self.entries.get(ix)? {
Entry::Separator => Some(
div()
@@ -1129,6 +1154,7 @@ impl PickerDelegate for Delegate {
.spacing(ListItemSpacing::Sparse)
.inset(true)
.toggle_state(selected)
+ .when_some(checkbox, |this, checkbox| this.start_slot(checkbox))
.child(
h_flex()
.w_full()