From 65861ce580f95a346f73e4b4193f021c5a7e5b88 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 29 Aug 2023 12:53:32 -0700 Subject: [PATCH] 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. --- crates/gpui/src/elements/flex.rs | 28 +-- .../quick_action_bar/src/quick_action_bar.rs | 5 +- crates/search/src/buffer_search.rs | 77 +++--- crates/search/src/mode.rs | 41 +--- crates/search/src/project_search.rs | 227 ++++++++---------- crates/search/src/search_bar.rs | 55 ++--- crates/theme/src/theme.rs | 2 +- crates/workspace/src/toolbar.rs | 13 +- styles/src/component/label_button.ts | 78 ------ styles/src/component/margin.ts | 34 +++ styles/src/component/padding.ts | 34 +++ styles/src/component/text_button.ts | 18 +- styles/src/style_tree/search.ts | 161 +++++-------- styles/src/style_tree/workspace.ts | 4 +- styles/tsconfig.json | 3 +- 15 files changed, 319 insertions(+), 461 deletions(-) delete mode 100644 styles/src/component/label_button.ts create mode 100644 styles/src/component/margin.ts create mode 100644 styles/src/component/padding.ts diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index d43a152a64..80dfb0625c 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -88,8 +88,7 @@ impl Flex { cx: &mut LayoutContext, ) { 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::() { if let Some((flex, expanded)) = metadata.flex { if expanded != layout_expanded { @@ -101,10 +100,6 @@ impl Flex { } 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 Element for Flex { cx: &mut LayoutContext, ) -> (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::(); contains_float |= metadata.map_or(false, |metadata| metadata.float); @@ -168,12 +162,7 @@ impl Element for Flex { ), }; 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 Element for Flex { } } - 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::() { if metadata.float { @@ -372,11 +360,9 @@ impl Element for Flex { 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), } } diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index 3055399c13..b1ec23fee8 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -150,9 +150,10 @@ impl ToolbarItemView for QuickActionBar { cx.notify(); } })); + ToolbarItemLocation::PrimaryRight { flex: None } + } else { + ToolbarItemLocation::Hidden } - - ToolbarItemLocation::PrimaryRight { flex: None } } None => { self.active_item = None; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 4078cb572d..dcc9b76900 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -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| { + let search_button_for_mode = |mode, side, cx: &mut ViewContext| { 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) -> usize { - 2 + 1 } } diff --git a/crates/search/src/mode.rs b/crates/search/src/mode.rs index 2c180be761..8afc2bd3f4 100644 --- a/crates/search/src/mode.rs +++ b/crates/search/src/mode.rs @@ -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 { - 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, } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 52247b9bc0..e05887f9fe 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -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, @@ -1420,8 +1420,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| { crate::search_bar::render_option_button_icon( self.is_option_enabled(option, cx), @@ -1447,28 +1452,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| { + 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); @@ -1486,50 +1486,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!( @@ -1544,25 +1500,81 @@ impl View for ProjectSearchBar { .aligned() }); - let search_button_for_mode = |mode, cx: &mut ViewContext| { - 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| { render_nav_button( label, @@ -1578,43 +1590,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") @@ -1637,7 +1623,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 @@ -1645,13 +1631,12 @@ impl ToolbarItemView for ProjectSearchBar { } fn row_count(&self, cx: &ViewContext) -> 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 } } diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 7d3c5261ea..d1a5a0380a 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -13,34 +13,6 @@ use crate::{ SelectNextMatch, SelectPrevMatch, }; -pub(super) fn render_close_button( - tooltip: &'static str, - theme: &theme::Search, - cx: &mut ViewContext, - on_click: impl Fn(MouseClick, &mut V, &mut EventContext) + 'static, - dismiss_action: Option>, -) -> AnyElement { - let tooltip_style = theme::current(cx).tooltip.clone(); - - enum CloseButton {} - MouseEventHandler::new::(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::(0, tooltip.to_string(), dismiss_action, tooltip_style, cx) - .into_any() -} - pub(super) fn render_nav_button( icon: &'static str, direction: Direction, @@ -111,6 +83,7 @@ pub(super) fn render_nav_button( pub(crate) fn render_search_mode_button( mode: SearchMode, + side: Option, is_active: bool, on_click: impl Fn(MouseClick, &mut V, &mut EventContext) + 'static, cx: &mut ViewContext, @@ -119,41 +92,41 @@ pub(crate) fn render_search_mode_button( enum SearchModeButton {} MouseEventHandler::new::(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) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 0f34963708..b82bf3e3a5 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -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, pub editor_icon: IconStyle, pub mode_button: Toggleable>, pub nav_button: Toggleable>, pub search_bar_row_height: f32, + pub search_row_spacing: f32, pub option_button_height: f32, pub modes_container: ContainerStyle, } diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 72c879d6d4..c3f4bb9723 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -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); diff --git a/styles/src/component/label_button.ts b/styles/src/component/label_button.ts deleted file mode 100644 index 3f1c54a7f6..0000000000 --- a/styles/src/component/label_button.ts +++ /dev/null @@ -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 = { - variant: Button.variant.Default, - shape: Button.shape.Rectangle, - states: { - hovered: true, - pressed: true - } - } -): Interactive { - 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 = { - variant: Button.variant.Default, - shape: Button.shape.Rectangle, - states: { - hovered: true, - pressed: true - } - } -): Toggleable> { - 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), - }, - }) -} diff --git a/styles/src/component/margin.ts b/styles/src/component/margin.ts new file mode 100644 index 0000000000..f6262405f0 --- /dev/null +++ b/styles/src/component/margin.ts @@ -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 + } +} diff --git a/styles/src/component/padding.ts b/styles/src/component/padding.ts new file mode 100644 index 0000000000..96792bf766 --- /dev/null +++ b/styles/src/component/padding.ts @@ -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 + } +} diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index b911cd5b77..ead017a803 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -17,6 +17,7 @@ interface TextButtonOptions { variant?: Button.Variant color?: keyof Theme["lowest"] margin?: Partial + 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"), }, diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts index 4493634a8e..c37a4e4b9a 100644 --- a/styles/src/style_tree/search.ts +++ b/styles/src/style_tree/search.ts @@ -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() } } diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts index ecfb572f7e..43a6cec585 100644 --- a/styles/src/style_tree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -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({ diff --git a/styles/tsconfig.json b/styles/tsconfig.json index 281bd74b21..c7eaa50eed 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -21,8 +21,7 @@ "experimentalDecorators": true, "strictPropertyInitialization": false, "skipLibCheck": true, - "useUnknownInCatchVariables": false, - "baseUrl": "." + "useUnknownInCatchVariables": false }, "exclude": [ "node_modules"