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:
parent
f30944543e
commit
301a8900a5
4 changed files with 109 additions and 78 deletions
|
@ -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)
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -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| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue