Add consistency between buffer and project search design (#20754)

Follow up to https://github.com/zed-industries/zed/pull/20242

This PR adds the `SearchInputWidth` util, which sets a threshold
container size in which an input's width stops filling the available
space. In practice, this is in place to make the buffer and project
search input fill the whole container width up to a certain point (where
this point is really an arbitrary number that can be fine-tuned per
taste). For folks using huge monitors, the UX isn't excellent if you
have a gigantic input.

In the future, upon further review, maybe it makes more sense to
reorganize this code better, baking it in as a default behavior of the
input component. Or even exposing this is a function many other
components could use, given we may want to have dynamic width in
different scenarios.

For now, I just wanted to make the design of these search UIs better and
more consistent.

| Buffer Search | Project Search |
|--------|--------|
| <img width="1042" alt="Screenshot 2024-11-15 at 20 39 21"
src="https://github.com/user-attachments/assets/f9cbf0b3-8c58-46d1-8380-e89cd9c89699">
| <img width="1042" alt="Screenshot 2024-11-15 at 20 39 24"
src="https://github.com/user-attachments/assets/ed244a51-ea55-4fe3-a719-a3d9cd119aa9">
|

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2024-11-28 13:39:49 -03:00 committed by GitHub
parent f30944543e
commit 301a8900a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 109 additions and 78 deletions

View file

@ -27,7 +27,10 @@ use settings::Settings;
use std::sync::Arc;
use theme::ThemeSettings;
use ui::{h_flex, prelude::*, IconButton, IconButtonShape, IconName, Tooltip, BASE_REM_SIZE_IN_PX};
use ui::{
h_flex, prelude::*, utils::SearchInputWidth, IconButton, IconButtonShape, IconName, Tooltip,
BASE_REM_SIZE_IN_PX,
};
use util::ResultExt;
use workspace::{
item::ItemHandle,
@ -38,8 +41,6 @@ use workspace::{
pub use registrar::DivRegistrar;
use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
const MIN_INPUT_WIDTH_REMS: f32 = 10.;
const MAX_INPUT_WIDTH_REMS: f32 = 30.;
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
#[derive(PartialEq, Clone, Deserialize)]
@ -160,12 +161,12 @@ impl Render for BufferSearchBar {
query_editor.placeholder_text(cx).is_none()
}) {
self.query_editor.update(cx, |editor, cx| {
editor.set_placeholder_text("Search", cx);
editor.set_placeholder_text("Search", cx);
});
}
self.replacement_editor.update(cx, |editor, cx| {
editor.set_placeholder_text("Replace with...", cx);
editor.set_placeholder_text("Replace with", cx);
});
let mut text_color = Color::Default;
@ -203,21 +204,26 @@ impl Render for BufferSearchBar {
cx.theme().colors().border
};
let container_width = cx.viewport_size().width;
let input_width = SearchInputWidth::calc_width(container_width);
let input_base_styles = || {
h_flex()
.w(input_width)
.h_8()
.px_2()
.py_1()
.border_1()
.border_color(editor_border)
.rounded_lg()
};
let search_line = h_flex()
.gap_2()
.child(
h_flex()
input_base_styles()
.id("editor-scroll")
.track_scroll(&self.editor_scroll_handle)
.flex_1()
.h_8()
.px_2()
.py_1()
.border_1()
.border_color(editor_border)
.min_w(rems(MIN_INPUT_WIDTH_REMS))
.max_w(rems(MAX_INPUT_WIDTH_REMS))
.rounded_lg()
.child(self.render_text_input(&self.query_editor, text_color.color(cx), cx))
.when(!hide_inline_icons, |div| {
div.children(supported_options.case.then(|| {
@ -249,8 +255,8 @@ impl Render for BufferSearchBar {
)
.child(
h_flex()
.flex_none()
.gap_0p5()
.gap_1()
.min_w_64()
.when(supported_options.replacement, |this| {
this.child(
IconButton::new(
@ -323,20 +329,27 @@ impl Render for BufferSearchBar {
}
}),
)
.child(render_nav_button(
ui::IconName::ChevronLeft,
self.active_match_index.is_some(),
"Select Previous Match",
&SelectPrevMatch,
focus_handle.clone(),
))
.child(render_nav_button(
ui::IconName::ChevronRight,
self.active_match_index.is_some(),
"Select Next Match",
&SelectNextMatch,
focus_handle.clone(),
))
.child(
h_flex()
.pl_2()
.ml_2()
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.child(render_nav_button(
ui::IconName::ChevronLeft,
self.active_match_index.is_some(),
"Select Previous Match",
&SelectPrevMatch,
focus_handle.clone(),
))
.child(render_nav_button(
ui::IconName::ChevronRight,
self.active_match_index.is_some(),
"Select Next Match",
&SelectNextMatch,
focus_handle.clone(),
)),
)
.when(!narrow_mode, |this| {
this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child(
Label::new(match_text).size(LabelSize::Small).color(
@ -353,30 +366,15 @@ impl Render for BufferSearchBar {
let replace_line = should_show_replace_input.then(|| {
h_flex()
.gap_2()
.flex_1()
.child(input_base_styles().child(self.render_text_input(
&self.replacement_editor,
cx.theme().colors().text,
cx,
)))
.child(
h_flex()
.flex_1()
// We're giving this a fixed height to match the height of the search input,
// which has an icon inside that is increasing its height.
.h_8()
.px_2()
.py_1()
.border_1()
.border_color(cx.theme().colors().border)
.rounded_lg()
.min_w(rems(MIN_INPUT_WIDTH_REMS))
.max_w(rems(MAX_INPUT_WIDTH_REMS))
.child(self.render_text_input(
&self.replacement_editor,
cx.theme().colors().text,
cx,
)),
)
.child(
h_flex()
.flex_none()
.gap_0p5()
.min_w_64()
.gap_1()
.child(
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
.shape(IconButtonShape::Square)
@ -418,6 +416,7 @@ impl Render for BufferSearchBar {
v_flex()
.id("buffer_search")
.gap_2()
.track_scroll(&self.scroll_handle)
.key_context(key_context)
.capture_action(cx.listener(Self::tab))
@ -446,20 +445,22 @@ impl Render for BufferSearchBar {
.when(self.supported_options().selection, |this| {
this.on_action(cx.listener(Self::toggle_selection))
})
.gap_2()
.child(
h_flex()
.relative()
.child(search_line.w_full())
.when(!narrow_mode, |div| {
div.child(
IconButton::new(SharedString::from("Close"), IconName::Close)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action("Close Search Bar", &Dismiss, cx)
})
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
this.dismiss(&Dismiss, cx)
})),
h_flex().absolute().right_0().child(
IconButton::new(SharedString::from("Close"), IconName::Close)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action("Close Search Bar", &Dismiss, cx)
})
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
this.dismiss(&Dismiss, cx)
})),
),
)
}),
)

View file

@ -34,8 +34,8 @@ use std::{
};
use theme::ThemeSettings;
use ui::{
h_flex, prelude::*, v_flex, Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label,
LabelCommon, LabelSize, Selectable, Tooltip,
h_flex, prelude::*, utils::SearchInputWidth, v_flex, Icon, IconButton, IconButtonShape,
IconName, KeyBinding, Label, LabelCommon, LabelSize, Selectable, Tooltip,
};
use util::paths::PathMatcher;
use workspace::{
@ -669,7 +669,7 @@ impl ProjectSearchView {
let query_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("Search all files...", cx);
editor.set_placeholder_text("Search all files", cx);
editor.set_text(query_text, cx);
editor
});
@ -692,7 +692,7 @@ impl ProjectSearchView {
);
let replacement_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("Replace in project...", cx);
editor.set_placeholder_text("Replace in project", cx);
if let Some(text) = replacement_text {
editor.set_text(text, cx);
}
@ -1586,9 +1586,12 @@ impl Render for ProjectSearchBar {
let search = search.read(cx);
let focus_handle = search.focus_handle(cx);
let container_width = cx.viewport_size().width;
let input_width = SearchInputWidth::calc_width(container_width);
let input_base_styles = || {
h_flex()
.w_full()
.w(input_width)
.h_8()
.px_2()
.py_1()
@ -1701,6 +1704,10 @@ impl Render for ProjectSearchBar {
.unwrap_or_else(|| "0/0".to_string());
let matches_column = h_flex()
.pl_2()
.ml_2()
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.child(
IconButton::new("project-search-prev-match", IconName::ChevronLeft)
.shape(IconButtonShape::Square)
@ -1751,13 +1758,13 @@ impl Render for ProjectSearchBar {
div()
.id("matches")
.ml_1()
.child(
Label::new(match_text).color(if search.active_match_index.is_some() {
.child(Label::new(match_text).size(LabelSize::Small).color(
if search.active_match_index.is_some() {
Color::Default
} else {
Color::Disabled
}),
)
},
))
.when(limit_reached, |el| {
el.tooltip(|cx| {
Tooltip::text("Search limits reached.\nTry narrowing your search.", cx)
@ -1767,9 +1774,9 @@ impl Render for ProjectSearchBar {
let search_line = h_flex()
.w_full()
.gap_1p5()
.gap_2()
.child(query_column)
.child(h_flex().min_w_40().child(mode_column).child(matches_column));
.child(h_flex().min_w_64().child(mode_column).child(matches_column));
let replace_line = search.replace_enabled.then(|| {
let replace_column =
@ -1779,7 +1786,7 @@ impl Render for ProjectSearchBar {
let replace_actions =
h_flex()
.min_w_40()
.min_w_64()
.gap_1()
.when(search.replace_enabled, |this| {
this.child(
@ -1830,7 +1837,7 @@ impl Render for ProjectSearchBar {
h_flex()
.w_full()
.gap_1p5()
.gap_2()
.child(replace_column)
.child(replace_actions)
});
@ -1838,7 +1845,7 @@ impl Render for ProjectSearchBar {
let filter_line = search.filters_enabled.then(|| {
h_flex()
.w_full()
.gap_1p5()
.gap_2()
.child(
input_base_styles()
.on_action(
@ -1861,12 +1868,11 @@ impl Render for ProjectSearchBar {
)
.child(
h_flex()
.min_w_40()
.min_w_64()
.gap_1()
.child(
IconButton::new("project-search-opened-only", IconName::FileSearch)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.selected(self.is_opened_only_enabled(cx))
.tooltip(|cx| Tooltip::text("Only Search Open Files", cx))
.on_click(cx.listener(|this, _, cx| {