mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Search UI polish (#2904)
This PR polishes the search bar UI, making the layout more dense, and the spacing more consistent with the rest of the app. I've also re-ordered the toolbar items to reflect some of @iamnbutler's original search designs. The items related to the search query are on the left, and the actions that navigate the buffer (next, prev, select all, result count) are on the right.
This commit is contained in:
commit
e7ba5a1edb
@ -88,8 +88,7 @@ impl<V: 'static> Flex<V> {
|
||||
cx: &mut LayoutContext<V>,
|
||||
) {
|
||||
let cross_axis = self.axis.invert();
|
||||
let last = self.children.len() - 1;
|
||||
for (ix, child) in &mut self.children.iter_mut().enumerate() {
|
||||
for child in self.children.iter_mut() {
|
||||
if let Some(metadata) = child.metadata::<FlexParentData>() {
|
||||
if let Some((flex, expanded)) = metadata.flex {
|
||||
if expanded != layout_expanded {
|
||||
@ -101,10 +100,6 @@ impl<V: 'static> Flex<V> {
|
||||
} else {
|
||||
let space_per_flex = *remaining_space / *remaining_flex;
|
||||
space_per_flex * flex
|
||||
} - if ix == 0 || ix == last {
|
||||
self.spacing / 2.
|
||||
} else {
|
||||
self.spacing
|
||||
};
|
||||
let child_min = if expanded { child_max } else { 0. };
|
||||
let child_constraint = match self.axis {
|
||||
@ -144,13 +139,12 @@ impl<V: 'static> Element<V> for Flex<V> {
|
||||
cx: &mut LayoutContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut total_flex = None;
|
||||
let mut fixed_space = 0.0;
|
||||
let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing;
|
||||
let mut contains_float = false;
|
||||
|
||||
let cross_axis = self.axis.invert();
|
||||
let mut cross_axis_max: f32 = 0.0;
|
||||
let last = self.children.len().saturating_sub(1);
|
||||
for (ix, child) in &mut self.children.iter_mut().enumerate() {
|
||||
for child in self.children.iter_mut() {
|
||||
let metadata = child.metadata::<FlexParentData>();
|
||||
contains_float |= metadata.map_or(false, |metadata| metadata.float);
|
||||
|
||||
@ -168,12 +162,7 @@ impl<V: 'static> Element<V> for Flex<V> {
|
||||
),
|
||||
};
|
||||
let size = child.layout(child_constraint, view, cx);
|
||||
fixed_space += size.along(self.axis)
|
||||
+ if ix == 0 || ix == last {
|
||||
self.spacing / 2.
|
||||
} else {
|
||||
self.spacing
|
||||
};
|
||||
fixed_space += size.along(self.axis);
|
||||
cross_axis_max = cross_axis_max.max(size.along(cross_axis));
|
||||
}
|
||||
}
|
||||
@ -333,8 +322,7 @@ impl<V: 'static> Element<V> for Flex<V> {
|
||||
}
|
||||
}
|
||||
|
||||
let last = self.children.len().saturating_sub(1);
|
||||
for (ix, child) in &mut self.children.iter_mut().enumerate() {
|
||||
for child in self.children.iter_mut() {
|
||||
if remaining_space > 0. {
|
||||
if let Some(metadata) = child.metadata::<FlexParentData>() {
|
||||
if metadata.float {
|
||||
@ -372,11 +360,9 @@ impl<V: 'static> Element<V> for Flex<V> {
|
||||
|
||||
child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
|
||||
|
||||
let spacing = if ix == last { 0. } else { self.spacing };
|
||||
|
||||
match self.axis {
|
||||
Axis::Horizontal => child_origin += vec2f(child.size().x() + spacing, 0.0),
|
||||
Axis::Vertical => child_origin += vec2f(0.0, child.size().y() + spacing),
|
||||
Axis::Horizontal => child_origin += vec2f(child.size().x() + self.spacing, 0.0),
|
||||
Axis::Vertical => child_origin += vec2f(0.0, child.size().y() + self.spacing),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,9 +152,10 @@ impl ToolbarItemView for QuickActionBar {
|
||||
cx.notify();
|
||||
}
|
||||
}));
|
||||
ToolbarItemLocation::PrimaryRight { flex: None }
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
|
||||
ToolbarItemLocation::PrimaryRight { flex: None }
|
||||
}
|
||||
None => {
|
||||
self.active_item = None;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
history::SearchHistory,
|
||||
mode::{next_mode, SearchMode},
|
||||
mode::{next_mode, SearchMode, Side},
|
||||
search_bar::{render_nav_button, render_search_mode_button},
|
||||
CycleMode, NextHistoryQuery, PreviousHistoryQuery, SearchOptions, SelectAllMatches,
|
||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
|
||||
@ -156,11 +156,12 @@ impl View for BufferSearchBar {
|
||||
self.query_editor.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text(new_placeholder_text, cx);
|
||||
});
|
||||
let search_button_for_mode = |mode, cx: &mut ViewContext<BufferSearchBar>| {
|
||||
let search_button_for_mode = |mode, side, cx: &mut ViewContext<BufferSearchBar>| {
|
||||
let is_active = self.current_mode == mode;
|
||||
|
||||
render_search_mode_button(
|
||||
mode,
|
||||
side,
|
||||
is_active,
|
||||
move |_, this, cx| {
|
||||
this.activate_search_mode(mode, cx);
|
||||
@ -212,20 +213,11 @@ impl View for BufferSearchBar {
|
||||
)
|
||||
};
|
||||
|
||||
let icon_style = theme.search.editor_icon.clone();
|
||||
let nav_column = Flex::row()
|
||||
.with_child(self.render_action_button("Select All", cx))
|
||||
.with_child(nav_button_for_direction("<", Direction::Prev, cx))
|
||||
.with_child(nav_button_for_direction(">", Direction::Next, cx))
|
||||
.with_child(Flex::row().with_children(match_count))
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height);
|
||||
|
||||
let query = Flex::row()
|
||||
let query_column = Flex::row()
|
||||
.with_child(
|
||||
Svg::for_style(icon_style.icon)
|
||||
Svg::for_style(theme.search.editor_icon.clone().icon)
|
||||
.contained()
|
||||
.with_style(icon_style.container),
|
||||
.with_style(theme.search.editor_icon.clone().container),
|
||||
)
|
||||
.with_child(ChildView::new(&self.query_editor, cx).flex(1., true))
|
||||
.with_child(
|
||||
@ -244,49 +236,45 @@ impl View for BufferSearchBar {
|
||||
.contained(),
|
||||
)
|
||||
.align_children_center()
|
||||
.flex(1., true);
|
||||
let editor_column = Flex::row()
|
||||
.with_child(
|
||||
query
|
||||
.contained()
|
||||
.with_style(query_container_style)
|
||||
.constrained()
|
||||
.with_min_width(theme.search.editor.min_width)
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., false),
|
||||
)
|
||||
.contained()
|
||||
.with_style(query_container_style)
|
||||
.constrained()
|
||||
.with_min_width(theme.search.editor.min_width)
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., false);
|
||||
|
||||
let mode_column = Flex::row()
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(search_button_for_mode(SearchMode::Text, cx))
|
||||
.with_child(search_button_for_mode(SearchMode::Regex, cx))
|
||||
.contained()
|
||||
.with_style(theme.search.modes_container),
|
||||
)
|
||||
.with_child(super::search_bar::render_close_button(
|
||||
"Dismiss Buffer Search",
|
||||
&theme.search,
|
||||
.with_child(search_button_for_mode(
|
||||
SearchMode::Text,
|
||||
Some(Side::Left),
|
||||
cx,
|
||||
|_, this, cx| this.dismiss(&Default::default(), cx),
|
||||
Some(Box::new(Dismiss)),
|
||||
))
|
||||
.with_child(search_button_for_mode(
|
||||
SearchMode::Regex,
|
||||
Some(Side::Right),
|
||||
cx,
|
||||
))
|
||||
.contained()
|
||||
.with_style(theme.search.modes_container)
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height);
|
||||
|
||||
let nav_column = Flex::row()
|
||||
.with_child(self.render_action_button("all", cx))
|
||||
.with_child(Flex::row().with_children(match_count))
|
||||
.with_child(nav_button_for_direction("<", Direction::Prev, cx))
|
||||
.with_child(nav_button_for_direction(">", Direction::Next, cx))
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.aligned()
|
||||
.right()
|
||||
.flex_float();
|
||||
|
||||
Flex::row()
|
||||
.with_child(editor_column)
|
||||
.with_child(nav_column)
|
||||
.with_child(query_column)
|
||||
.with_child(mode_column)
|
||||
.with_child(nav_column)
|
||||
.contained()
|
||||
.with_style(theme.search.container)
|
||||
.aligned()
|
||||
.into_any_named("search bar")
|
||||
}
|
||||
}
|
||||
@ -340,8 +328,9 @@ impl ToolbarItemView for BufferSearchBar {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
|
||||
fn row_count(&self, _: &ViewContext<Self>) -> usize {
|
||||
2
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,41 +48,18 @@ impl SearchMode {
|
||||
SearchMode::Regex => Box::new(ActivateRegexMode),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn border_right(&self) -> bool {
|
||||
match self {
|
||||
SearchMode::Regex => true,
|
||||
SearchMode::Text => true,
|
||||
SearchMode::Semantic => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn border_left(&self) -> bool {
|
||||
match self {
|
||||
SearchMode::Text => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn button_side(&self) -> Option<Side> {
|
||||
match self {
|
||||
SearchMode::Text => Some(Side::Left),
|
||||
SearchMode::Semantic => None,
|
||||
SearchMode::Regex => Some(Side::Right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
|
||||
let next_text_state = if semantic_enabled {
|
||||
SearchMode::Semantic
|
||||
} else {
|
||||
SearchMode::Regex
|
||||
};
|
||||
|
||||
match mode {
|
||||
SearchMode::Text => next_text_state,
|
||||
SearchMode::Semantic => SearchMode::Regex,
|
||||
SearchMode::Regex => SearchMode::Text,
|
||||
SearchMode::Text => SearchMode::Regex,
|
||||
SearchMode::Regex => {
|
||||
if semantic_enabled {
|
||||
SearchMode::Semantic
|
||||
} else {
|
||||
SearchMode::Text
|
||||
}
|
||||
}
|
||||
SearchMode::Semantic => SearchMode::Text,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
history::SearchHistory,
|
||||
mode::SearchMode,
|
||||
mode::{SearchMode, Side},
|
||||
search_bar::{render_nav_button, render_option_button_icon, render_search_mode_button},
|
||||
ActivateRegexMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, SearchOptions,
|
||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
|
||||
@ -1424,8 +1424,13 @@ impl View for ProjectSearchBar {
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
||||
let search = _search.read(cx);
|
||||
let is_semantic_available = SemanticIndex::enabled(cx);
|
||||
let is_semantic_disabled = search.semantic_state.is_none();
|
||||
let icon_style = theme.search.editor_icon.clone();
|
||||
let is_active = search.active_match_index.is_some();
|
||||
|
||||
let render_option_button_icon = |path, option, cx: &mut ViewContext<Self>| {
|
||||
crate::search_bar::render_option_button_icon(
|
||||
self.is_option_enabled(option, cx),
|
||||
@ -1451,28 +1456,23 @@ impl View for ProjectSearchBar {
|
||||
render_option_button_icon("icons/word_search_12.svg", SearchOptions::WHOLE_WORD, cx)
|
||||
});
|
||||
|
||||
let search = _search.read(cx);
|
||||
let icon_style = theme.search.editor_icon.clone();
|
||||
|
||||
// Editor Functionality
|
||||
let query = Flex::row()
|
||||
.with_child(
|
||||
Svg::for_style(icon_style.icon)
|
||||
.contained()
|
||||
.with_style(icon_style.container),
|
||||
let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| {
|
||||
let is_active = if let Some(search) = self.active_project_search.as_ref() {
|
||||
let search = search.read(cx);
|
||||
search.current_mode == mode
|
||||
} else {
|
||||
false
|
||||
};
|
||||
render_search_mode_button(
|
||||
mode,
|
||||
side,
|
||||
is_active,
|
||||
move |_, this, cx| {
|
||||
this.activate_search_mode(mode, cx);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.with_child(ChildView::new(&search.query_editor, cx).flex(1., true))
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(filter_button)
|
||||
.with_children(case_sensitive)
|
||||
.with_children(whole_word)
|
||||
.flex(1., false)
|
||||
.constrained()
|
||||
.contained(),
|
||||
)
|
||||
.align_children_center()
|
||||
.flex(1., true);
|
||||
};
|
||||
|
||||
let search = _search.read(cx);
|
||||
|
||||
@ -1490,50 +1490,6 @@ impl View for ProjectSearchBar {
|
||||
theme.search.include_exclude_editor.input.container
|
||||
};
|
||||
|
||||
let included_files_view = ChildView::new(&search.included_files_editor, cx)
|
||||
.contained()
|
||||
.flex(1., true);
|
||||
let excluded_files_view = ChildView::new(&search.excluded_files_editor, cx)
|
||||
.contained()
|
||||
.flex(1., true);
|
||||
let filters = search.filters_enabled.then(|| {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
included_files_view
|
||||
.contained()
|
||||
.with_style(include_container_style)
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.with_min_width(theme.search.include_exclude_editor.min_width)
|
||||
.with_max_width(theme.search.include_exclude_editor.max_width),
|
||||
)
|
||||
.with_child(
|
||||
excluded_files_view
|
||||
.contained()
|
||||
.with_style(exclude_container_style)
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.with_min_width(theme.search.include_exclude_editor.min_width)
|
||||
.with_max_width(theme.search.include_exclude_editor.max_width),
|
||||
)
|
||||
.contained()
|
||||
.with_padding_top(theme.workspace.toolbar.container.padding.bottom)
|
||||
});
|
||||
|
||||
let editor_column = Flex::column()
|
||||
.with_child(
|
||||
query
|
||||
.contained()
|
||||
.with_style(query_container_style)
|
||||
.constrained()
|
||||
.with_min_width(theme.search.editor.min_width)
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., false),
|
||||
)
|
||||
.with_children(filters)
|
||||
.flex(1., false);
|
||||
|
||||
let matches = search.active_match_index.map(|match_ix| {
|
||||
Label::new(
|
||||
format!(
|
||||
@ -1548,25 +1504,81 @@ impl View for ProjectSearchBar {
|
||||
.aligned()
|
||||
});
|
||||
|
||||
let search_button_for_mode = |mode, cx: &mut ViewContext<ProjectSearchBar>| {
|
||||
let is_active = if let Some(search) = self.active_project_search.as_ref() {
|
||||
let search = search.read(cx);
|
||||
search.current_mode == mode
|
||||
} else {
|
||||
false
|
||||
};
|
||||
render_search_mode_button(
|
||||
mode,
|
||||
is_active,
|
||||
move |_, this, cx| {
|
||||
this.activate_search_mode(mode, cx);
|
||||
},
|
||||
cx,
|
||||
let query_column = Flex::column()
|
||||
.with_spacing(theme.search.search_row_spacing)
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::for_style(icon_style.icon)
|
||||
.contained()
|
||||
.with_style(icon_style.container),
|
||||
)
|
||||
.with_child(ChildView::new(&search.query_editor, cx).flex(1., true))
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(filter_button)
|
||||
.with_children(case_sensitive)
|
||||
.with_children(whole_word)
|
||||
.flex(1., false)
|
||||
.constrained()
|
||||
.contained(),
|
||||
)
|
||||
.align_children_center()
|
||||
.contained()
|
||||
.with_style(query_container_style)
|
||||
.constrained()
|
||||
.with_min_width(theme.search.editor.min_width)
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., false),
|
||||
)
|
||||
};
|
||||
let is_active = search.active_match_index.is_some();
|
||||
let semantic_index = SemanticIndex::enabled(cx)
|
||||
.then(|| search_button_for_mode(SearchMode::Semantic, cx));
|
||||
.with_children(search.filters_enabled.then(|| {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
ChildView::new(&search.included_files_editor, cx)
|
||||
.contained()
|
||||
.with_style(include_container_style)
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
ChildView::new(&search.excluded_files_editor, cx)
|
||||
.contained()
|
||||
.with_style(exclude_container_style)
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., true),
|
||||
)
|
||||
.constrained()
|
||||
.with_min_width(theme.search.editor.min_width)
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.flex(1., false)
|
||||
}))
|
||||
.flex(1., false);
|
||||
|
||||
let mode_column =
|
||||
Flex::row()
|
||||
.with_child(search_button_for_mode(
|
||||
SearchMode::Text,
|
||||
Some(Side::Left),
|
||||
cx,
|
||||
))
|
||||
.with_child(search_button_for_mode(
|
||||
SearchMode::Regex,
|
||||
if is_semantic_available {
|
||||
None
|
||||
} else {
|
||||
Some(Side::Right)
|
||||
},
|
||||
cx,
|
||||
))
|
||||
.with_children(is_semantic_available.then(|| {
|
||||
search_button_for_mode(SearchMode::Semantic, Some(Side::Right), cx)
|
||||
}))
|
||||
.contained()
|
||||
.with_style(theme.search.modes_container);
|
||||
|
||||
let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| {
|
||||
render_nav_button(
|
||||
label,
|
||||
@ -1582,43 +1594,17 @@ impl View for ProjectSearchBar {
|
||||
};
|
||||
|
||||
let nav_column = Flex::row()
|
||||
.with_child(Flex::row().with_children(matches))
|
||||
.with_child(nav_button_for_direction("<", Direction::Prev, cx))
|
||||
.with_child(nav_button_for_direction(">", Direction::Next, cx))
|
||||
.with_child(Flex::row().with_children(matches))
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height);
|
||||
|
||||
let mode_column = Flex::row()
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(search_button_for_mode(SearchMode::Text, cx))
|
||||
.with_children(semantic_index)
|
||||
.with_child(search_button_for_mode(SearchMode::Regex, cx))
|
||||
.contained()
|
||||
.with_style(theme.search.modes_container),
|
||||
)
|
||||
.with_child(super::search_bar::render_close_button(
|
||||
"Dismiss Project Search",
|
||||
&theme.search,
|
||||
cx,
|
||||
|_, this, cx| {
|
||||
if let Some(search) = this.active_project_search.as_mut() {
|
||||
search.update(cx, |_, cx| cx.emit(ViewEvent::Dismiss))
|
||||
}
|
||||
},
|
||||
None,
|
||||
))
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.aligned()
|
||||
.right()
|
||||
.top()
|
||||
.flex_float();
|
||||
|
||||
Flex::row()
|
||||
.with_child(editor_column)
|
||||
.with_child(nav_column)
|
||||
.with_child(query_column)
|
||||
.with_child(mode_column)
|
||||
.with_child(nav_column)
|
||||
.contained()
|
||||
.with_style(theme.search.container)
|
||||
.into_any_named("project search")
|
||||
@ -1647,7 +1633,7 @@ impl ToolbarItemView for ProjectSearchBar {
|
||||
self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
|
||||
self.active_project_search = Some(search);
|
||||
ToolbarItemLocation::PrimaryLeft {
|
||||
flex: Some((1., false)),
|
||||
flex: Some((1., true)),
|
||||
}
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
@ -1655,13 +1641,12 @@ impl ToolbarItemView for ProjectSearchBar {
|
||||
}
|
||||
|
||||
fn row_count(&self, cx: &ViewContext<Self>) -> usize {
|
||||
self.active_project_search
|
||||
.as_ref()
|
||||
.map(|search| {
|
||||
let offset = search.read(cx).filters_enabled as usize;
|
||||
2 + offset
|
||||
})
|
||||
.unwrap_or_else(|| 2)
|
||||
if let Some(search) = self.active_project_search.as_ref() {
|
||||
if search.read(cx).filters_enabled {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,34 +13,6 @@ use crate::{
|
||||
SelectNextMatch, SelectPrevMatch,
|
||||
};
|
||||
|
||||
pub(super) fn render_close_button<V: View>(
|
||||
tooltip: &'static str,
|
||||
theme: &theme::Search,
|
||||
cx: &mut ViewContext<V>,
|
||||
on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
dismiss_action: Option<Box<dyn Action>>,
|
||||
) -> AnyElement<V> {
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
|
||||
enum CloseButton {}
|
||||
MouseEventHandler::new::<CloseButton, _>(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.constrained()
|
||||
.with_height(theme.search_bar_row_height)
|
||||
})
|
||||
.on_click(MouseButton::Left, on_click)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_tooltip::<CloseButton>(0, tooltip.to_string(), dismiss_action, tooltip_style, cx)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
pub(super) fn render_nav_button<V: View>(
|
||||
icon: &'static str,
|
||||
direction: Direction,
|
||||
@ -111,6 +83,7 @@ pub(super) fn render_nav_button<V: View>(
|
||||
|
||||
pub(crate) fn render_search_mode_button<V: View>(
|
||||
mode: SearchMode,
|
||||
side: Option<Side>,
|
||||
is_active: bool,
|
||||
on_click: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
cx: &mut ViewContext<V>,
|
||||
@ -119,41 +92,41 @@ pub(crate) fn render_search_mode_button<V: View>(
|
||||
enum SearchModeButton {}
|
||||
MouseEventHandler::new::<SearchModeButton, _>(mode.region_id(), cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let mut style = theme
|
||||
let style = theme
|
||||
.search
|
||||
.mode_button
|
||||
.in_state(is_active)
|
||||
.style_for(state)
|
||||
.clone();
|
||||
style.container.border.left = mode.border_left();
|
||||
style.container.border.right = mode.border_right();
|
||||
|
||||
let label = Label::new(mode.label(), style.text.clone())
|
||||
.aligned()
|
||||
.contained();
|
||||
let mut container_style = style.container.clone();
|
||||
if let Some(button_side) = mode.button_side() {
|
||||
let mut container_style = style.container;
|
||||
if let Some(button_side) = side {
|
||||
if button_side == Side::Left {
|
||||
container_style.border.left = true;
|
||||
container_style.corner_radii = CornerRadii {
|
||||
bottom_right: 0.,
|
||||
top_right: 0.,
|
||||
..container_style.corner_radii
|
||||
};
|
||||
label.with_style(container_style)
|
||||
} else {
|
||||
container_style.border.left = false;
|
||||
container_style.corner_radii = CornerRadii {
|
||||
bottom_left: 0.,
|
||||
top_left: 0.,
|
||||
..container_style.corner_radii
|
||||
};
|
||||
label.with_style(container_style)
|
||||
}
|
||||
} else {
|
||||
container_style.border.left = false;
|
||||
container_style.corner_radii = CornerRadii::default();
|
||||
label.with_style(container_style)
|
||||
}
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
|
||||
Label::new(mode.label(), style.text)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(container_style)
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
})
|
||||
.on_click(MouseButton::Left, on_click)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
|
@ -437,11 +437,11 @@ pub struct Search {
|
||||
pub match_index: ContainedText,
|
||||
pub major_results_status: TextStyle,
|
||||
pub minor_results_status: TextStyle,
|
||||
pub dismiss_button: Interactive<IconButton>,
|
||||
pub editor_icon: IconStyle,
|
||||
pub mode_button: Toggleable<Interactive<ContainedText>>,
|
||||
pub nav_button: Toggleable<Interactive<ContainedLabel>>,
|
||||
pub search_bar_row_height: f32,
|
||||
pub search_row_spacing: f32,
|
||||
pub option_button_height: f32,
|
||||
pub modes_container: ContainerStyle,
|
||||
}
|
||||
|
@ -81,10 +81,7 @@ impl View for Toolbar {
|
||||
|
||||
ToolbarItemLocation::PrimaryLeft { flex } => {
|
||||
primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
||||
let left_item = ChildView::new(item.as_any(), cx)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_right(spacing);
|
||||
let left_item = ChildView::new(item.as_any(), cx).aligned();
|
||||
if let Some((flex, expanded)) = flex {
|
||||
primary_left_items.push(left_item.flex(flex, expanded).into_any());
|
||||
} else {
|
||||
@ -94,11 +91,7 @@ impl View for Toolbar {
|
||||
|
||||
ToolbarItemLocation::PrimaryRight { flex } => {
|
||||
primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
||||
let right_item = ChildView::new(item.as_any(), cx)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_left(spacing)
|
||||
.flex_float();
|
||||
let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
|
||||
if let Some((flex, expanded)) = flex {
|
||||
primary_right_items.push(right_item.flex(flex, expanded).into_any());
|
||||
} else {
|
||||
@ -120,7 +113,7 @@ impl View for Toolbar {
|
||||
let container_style = theme.container;
|
||||
let height = theme.height * primary_items_row_count as f32;
|
||||
|
||||
let mut primary_items = Flex::row();
|
||||
let mut primary_items = Flex::row().with_spacing(spacing);
|
||||
primary_items.extend(primary_left_items);
|
||||
primary_items.extend(primary_right_items);
|
||||
|
||||
|
@ -1,78 +0,0 @@
|
||||
import { Interactive, interactive, toggleable, Toggleable } from "../element"
|
||||
import { TextStyle, background, text } from "../style_tree/components"
|
||||
import { useTheme } from "../theme"
|
||||
import { Button } from "./button"
|
||||
|
||||
type LabelButtonStyle = {
|
||||
corder_radius: number
|
||||
background: string | null
|
||||
padding: {
|
||||
top: number
|
||||
bottom: number
|
||||
left: number
|
||||
right: number
|
||||
},
|
||||
margin: Button.Options['margin']
|
||||
button_height: number
|
||||
} & TextStyle
|
||||
|
||||
/** Styles an Interactive<ContainedText> */
|
||||
export function label_button_style(
|
||||
options: Partial<Button.Options> = {
|
||||
variant: Button.variant.Default,
|
||||
shape: Button.shape.Rectangle,
|
||||
states: {
|
||||
hovered: true,
|
||||
pressed: true
|
||||
}
|
||||
}
|
||||
): Interactive<LabelButtonStyle> {
|
||||
const theme = useTheme()
|
||||
|
||||
const base = Button.button_base(options)
|
||||
const layer = options.layer ?? theme.middle
|
||||
const color = options.color ?? "base"
|
||||
|
||||
const default_state = {
|
||||
...base,
|
||||
...text(layer ?? theme.lowest, "sans", color),
|
||||
font_size: Button.FONT_SIZE,
|
||||
}
|
||||
|
||||
return interactive({
|
||||
base: default_state,
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, options.background ?? color, "hovered")
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, options.background ?? color, "pressed")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Styles an Toggleable<Interactive<ContainedText>> */
|
||||
export function toggle_label_button_style(
|
||||
options: Partial<Button.ToggleableOptions> = {
|
||||
variant: Button.variant.Default,
|
||||
shape: Button.shape.Rectangle,
|
||||
states: {
|
||||
hovered: true,
|
||||
pressed: true
|
||||
}
|
||||
}
|
||||
): Toggleable<Interactive<LabelButtonStyle>> {
|
||||
const activeOptions = {
|
||||
...options,
|
||||
color: options.active_color || options.color,
|
||||
background: options.active_background || options.background
|
||||
}
|
||||
|
||||
return toggleable({
|
||||
state: {
|
||||
inactive: label_button_style(options),
|
||||
active: label_button_style(activeOptions),
|
||||
},
|
||||
})
|
||||
}
|
34
styles/src/component/margin.ts
Normal file
34
styles/src/component/margin.ts
Normal file
@ -0,0 +1,34 @@
|
||||
type MarginOptions = {
|
||||
all?: number
|
||||
left?: number
|
||||
right?: number
|
||||
top?: number
|
||||
bottom?: number
|
||||
}
|
||||
|
||||
export type MarginStyle = {
|
||||
top: number
|
||||
bottom: number
|
||||
left: number
|
||||
right: number
|
||||
}
|
||||
|
||||
export const margin_style = (options: MarginOptions): MarginStyle => {
|
||||
const { all, top, bottom, left, right } = options
|
||||
|
||||
if (all !== undefined) return {
|
||||
top: all,
|
||||
bottom: all,
|
||||
left: all,
|
||||
right: all
|
||||
}
|
||||
|
||||
if (top === undefined && bottom === undefined && left === undefined && right === undefined) throw new Error("Margin must have at least one value")
|
||||
|
||||
return {
|
||||
top: top || 0,
|
||||
bottom: bottom || 0,
|
||||
left: left || 0,
|
||||
right: right || 0
|
||||
}
|
||||
}
|
34
styles/src/component/padding.ts
Normal file
34
styles/src/component/padding.ts
Normal file
@ -0,0 +1,34 @@
|
||||
type PaddingOptions = {
|
||||
all?: number
|
||||
left?: number
|
||||
right?: number
|
||||
top?: number
|
||||
bottom?: number
|
||||
}
|
||||
|
||||
export type PaddingStyle = {
|
||||
top: number
|
||||
bottom: number
|
||||
left: number
|
||||
right: number
|
||||
}
|
||||
|
||||
export const padding_style = (options: PaddingOptions): PaddingStyle => {
|
||||
const { all, top, bottom, left, right } = options
|
||||
|
||||
if (all !== undefined) return {
|
||||
top: all,
|
||||
bottom: all,
|
||||
left: all,
|
||||
right: all
|
||||
}
|
||||
|
||||
if (top === undefined && bottom === undefined && left === undefined && right === undefined) throw new Error("Padding must have at least one value")
|
||||
|
||||
return {
|
||||
top: top || 0,
|
||||
bottom: bottom || 0,
|
||||
left: left || 0,
|
||||
right: right || 0
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ interface TextButtonOptions {
|
||||
variant?: Button.Variant
|
||||
color?: keyof Theme["lowest"]
|
||||
margin?: Partial<Margin>
|
||||
disabled?: boolean
|
||||
text_properties?: TextProperties
|
||||
}
|
||||
|
||||
@ -29,6 +30,7 @@ export function text_button({
|
||||
color,
|
||||
layer,
|
||||
margin,
|
||||
disabled,
|
||||
text_properties,
|
||||
}: TextButtonOptions = {}) {
|
||||
const theme = useTheme()
|
||||
@ -65,13 +67,17 @@ export function text_button({
|
||||
state: {
|
||||
default: {
|
||||
background: background_color,
|
||||
color: foreground(layer ?? theme.lowest, color),
|
||||
color:
|
||||
disabled
|
||||
? foreground(layer ?? theme.lowest, "disabled")
|
||||
: foreground(layer ?? theme.lowest, color),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer ?? theme.lowest, color, "hovered"),
|
||||
color: foreground(layer ?? theme.lowest, color, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
hovered:
|
||||
disabled ? {} : {
|
||||
background: background(layer ?? theme.lowest, color, "hovered"),
|
||||
color: foreground(layer ?? theme.lowest, color, "hovered"),
|
||||
},
|
||||
clicked: disabled ? {} : {
|
||||
background: background(layer ?? theme.lowest, color, "pressed"),
|
||||
color: foreground(layer ?? theme.lowest, color, "pressed"),
|
||||
},
|
||||
|
@ -2,9 +2,23 @@ import { with_opacity } from "../theme/color"
|
||||
import { background, border, foreground, text } from "./components"
|
||||
import { interactive, toggleable } from "../element"
|
||||
import { useTheme } from "../theme"
|
||||
import { text_button } from "../component/text_button"
|
||||
|
||||
const search_results = () => {
|
||||
const theme = useTheme()
|
||||
|
||||
return {
|
||||
// TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive
|
||||
match_background: with_opacity(
|
||||
foreground(theme.highest, "accent"),
|
||||
0.4
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export default function search(): any {
|
||||
const theme = useTheme()
|
||||
const SEARCH_ROW_SPACING = 12
|
||||
|
||||
// Search input
|
||||
const editor = {
|
||||
@ -34,12 +48,8 @@ export default function search(): any {
|
||||
}
|
||||
|
||||
return {
|
||||
padding: { top: 16, bottom: 16, left: 16, right: 16 },
|
||||
// TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive
|
||||
match_background: with_opacity(
|
||||
foreground(theme.highest, "accent"),
|
||||
0.4
|
||||
),
|
||||
padding: { top: 4, bottom: 4 },
|
||||
|
||||
option_button: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
@ -153,47 +163,13 @@ export default function search(): any {
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Search tool buttons
|
||||
// HACK: This is not how disabled elements should be created
|
||||
// Disabled elements should use a disabled state of an interactive element, not a toggleable element with the inactive state being disabled
|
||||
action_button: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...text(theme.highest, "mono", "disabled"),
|
||||
background: background(theme.highest, "disabled"),
|
||||
corner_radius: 6,
|
||||
border: border(theme.highest, "disabled"),
|
||||
padding: {
|
||||
// bottom: 2,
|
||||
left: 10,
|
||||
right: 10,
|
||||
// top: 2,
|
||||
},
|
||||
margin: {
|
||||
right: 9,
|
||||
}
|
||||
},
|
||||
state: {
|
||||
hovered: {}
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: interactive({
|
||||
base: {
|
||||
...text(theme.highest, "mono", "on"),
|
||||
background: background(theme.highest, "on"),
|
||||
border: border(theme.highest, "on"),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(theme.highest, "mono", "on", "hovered"),
|
||||
background: background(theme.highest, "on", "hovered"),
|
||||
border: border(theme.highest, "on", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(theme.highest, "mono", "on", "pressed"),
|
||||
background: background(theme.highest, "on", "pressed"),
|
||||
border: border(theme.highest, "on", "pressed"),
|
||||
},
|
||||
},
|
||||
})
|
||||
inactive: text_button({ variant: "ghost", layer: theme.highest, disabled: true, margin: { right: SEARCH_ROW_SPACING }, text_properties: { size: "sm" } }),
|
||||
active: text_button({ variant: "ghost", layer: theme.highest, margin: { right: SEARCH_ROW_SPACING }, text_properties: { size: "sm" } })
|
||||
}
|
||||
}),
|
||||
editor,
|
||||
@ -207,15 +183,15 @@ export default function search(): any {
|
||||
border: border(theme.highest, "negative"),
|
||||
},
|
||||
match_index: {
|
||||
...text(theme.highest, "mono", "variant"),
|
||||
...text(theme.highest, "mono", { size: "sm" }),
|
||||
padding: {
|
||||
left: 9,
|
||||
right: SEARCH_ROW_SPACING,
|
||||
},
|
||||
},
|
||||
option_button_group: {
|
||||
padding: {
|
||||
left: 12,
|
||||
right: 12,
|
||||
left: SEARCH_ROW_SPACING,
|
||||
right: SEARCH_ROW_SPACING,
|
||||
},
|
||||
},
|
||||
include_exclude_inputs: {
|
||||
@ -232,52 +208,26 @@ export default function search(): any {
|
||||
...text(theme.highest, "mono", "variant"),
|
||||
size: 13,
|
||||
},
|
||||
dismiss_button: interactive({
|
||||
base: {
|
||||
color: foreground(theme.highest, "variant"),
|
||||
icon_width: 14,
|
||||
button_width: 32,
|
||||
corner_radius: 6,
|
||||
padding: {
|
||||
// // top: 10,
|
||||
// bottom: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
},
|
||||
|
||||
background: background(theme.highest, "variant"),
|
||||
|
||||
border: border(theme.highest, "on"),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(theme.highest, "hovered"),
|
||||
background: background(theme.highest, "variant", "hovered")
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(theme.highest, "pressed"),
|
||||
background: background(theme.highest, "variant", "pressed")
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Input Icon
|
||||
editor_icon: {
|
||||
icon: {
|
||||
color: foreground(theme.highest, "variant"),
|
||||
asset: "icons/magnifying_glass_12.svg",
|
||||
color: foreground(theme.highest, "disabled"),
|
||||
asset: "icons/magnifying_glass.svg",
|
||||
dimensions: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
width: 14,
|
||||
height: 14,
|
||||
}
|
||||
},
|
||||
container: {
|
||||
margin: { right: 6 },
|
||||
padding: { left: 2, right: 2 },
|
||||
margin: { right: 4 },
|
||||
padding: { left: 1, right: 1 },
|
||||
}
|
||||
},
|
||||
// Toggle group buttons - Text | Regex | Semantic
|
||||
mode_button: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...text(theme.highest, "mono", "variant"),
|
||||
...text(theme.highest, "mono", "variant", { size: "sm" }),
|
||||
background: background(theme.highest, "variant"),
|
||||
|
||||
border: {
|
||||
@ -285,21 +235,24 @@ export default function search(): any {
|
||||
left: false,
|
||||
right: false
|
||||
},
|
||||
|
||||
margin: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
},
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
left: 12,
|
||||
right: 12,
|
||||
},
|
||||
corner_radius: 6,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(theme.highest, "mono", "variant", "hovered"),
|
||||
...text(theme.highest, "mono", "variant", "hovered", { size: "sm" }),
|
||||
background: background(theme.highest, "variant", "hovered"),
|
||||
border: border(theme.highest, "on", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(theme.highest, "mono", "variant", "pressed"),
|
||||
...text(theme.highest, "mono", "variant", "pressed", { size: "sm" }),
|
||||
background: background(theme.highest, "variant", "pressed"),
|
||||
border: border(theme.highest, "on", "pressed"),
|
||||
},
|
||||
@ -308,20 +261,23 @@ export default function search(): any {
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
...text(theme.highest, "mono", "on"),
|
||||
...text(theme.highest, "mono", "on", { size: "sm" }),
|
||||
background: background(theme.highest, "on")
|
||||
},
|
||||
hovered: {
|
||||
...text(theme.highest, "mono", "on", "hovered"),
|
||||
...text(theme.highest, "mono", "on", "hovered", { size: "sm" }),
|
||||
background: background(theme.highest, "on", "hovered")
|
||||
},
|
||||
clicked: {
|
||||
...text(theme.highest, "mono", "on", "pressed"),
|
||||
...text(theme.highest, "mono", "on", "pressed", { size: "sm" }),
|
||||
background: background(theme.highest, "on", "pressed")
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Next/Previous Match buttons
|
||||
// HACK: This is not how disabled elements should be created
|
||||
// Disabled elements should use a disabled state of an interactive element, not a toggleable element with the inactive state being disabled
|
||||
nav_button: toggleable({
|
||||
state: {
|
||||
inactive: interactive({
|
||||
@ -334,7 +290,10 @@ export default function search(): any {
|
||||
left: false,
|
||||
right: false,
|
||||
},
|
||||
|
||||
margin: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
},
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
@ -354,7 +313,10 @@ export default function search(): any {
|
||||
left: false,
|
||||
right: false,
|
||||
},
|
||||
|
||||
margin: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
},
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
@ -375,13 +337,10 @@ export default function search(): any {
|
||||
})
|
||||
}
|
||||
}),
|
||||
search_bar_row_height: 32,
|
||||
search_bar_row_height: 34,
|
||||
search_row_spacing: 8,
|
||||
option_button_height: 22,
|
||||
modes_container: {
|
||||
margin: {
|
||||
right: 9
|
||||
}
|
||||
}
|
||||
|
||||
modes_container: {},
|
||||
...search_results()
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ export default function workspace(): any {
|
||||
status_bar: statusBar(),
|
||||
titlebar: titlebar(),
|
||||
toolbar: {
|
||||
height: 34,
|
||||
height: 42,
|
||||
background: background(theme.highest),
|
||||
border: border(theme.highest, { bottom: true }),
|
||||
item_spacing: 8,
|
||||
@ -138,7 +138,7 @@ export default function workspace(): any {
|
||||
variant: "ghost",
|
||||
active_color: "accent",
|
||||
}),
|
||||
padding: { left: 8, right: 8, top: 4, bottom: 4 },
|
||||
padding: { left: 8, right: 8 },
|
||||
},
|
||||
breadcrumb_height: 24,
|
||||
breadcrumbs: interactive({
|
||||
|
@ -21,8 +21,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"skipLibCheck": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"baseUrl": "."
|
||||
"useUnknownInCatchVariables": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
Loading…
Reference in New Issue
Block a user