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:
Max Brunsfeld 2023-08-29 12:53:32 -07:00 committed by GitHub
commit e7ba5a1edb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 319 additions and 461 deletions

View File

@ -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),
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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,
}
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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,
}

View File

@ -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);

View File

@ -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&lt;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&lt;Interactive&lt;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),
},
})
}

View 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
}
}

View 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
}
}

View File

@ -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"),
},

View File

@ -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()
}
}

View File

@ -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({

View File

@ -21,8 +21,7 @@
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"useUnknownInCatchVariables": false,
"baseUrl": "."
"useUnknownInCatchVariables": false
},
"exclude": [
"node_modules"