WIP: Add ButtonSide element

Piotr Osiewicz created

Change summary

crates/search/Cargo.toml            |   4 
crates/search/src/project_search.rs | 172 +++++++++++++++++++++++++++++-
styles/src/style_tree/search.ts     |   6 
3 files changed, 165 insertions(+), 17 deletions(-)

Detailed changes

crates/search/Cargo.toml 🔗

@@ -30,11 +30,11 @@ serde_derive.workspace = true
 smallvec.workspace = true
 smol.workspace = true
 globset.workspace = true
-
+serde_json.workspace = true
 [dev-dependencies]
 client = { path = "../client", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
-serde_json.workspace = true
+
 workspace = { path = "../workspace", features = ["test-support"] }
 unindent.workspace = true

crates/search/src/project_search.rs 🔗

@@ -10,6 +10,10 @@ use editor::{
 };
 use futures::StreamExt;
 use globset::{Glob, GlobMatcher};
+use gpui::color::Color;
+use gpui::geometry::rect::RectF;
+use gpui::json::{self, ToJson};
+use gpui::SceneBuilder;
 use gpui::{
     actions,
     elements::*,
@@ -17,6 +21,7 @@ use gpui::{
     Action, AnyElement, AnyViewHandle, AppContext, Entity, ModelContext, ModelHandle, Subscription,
     Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
 };
+use gpui::{scene::Path, Border, LayoutContext};
 use menu::Confirm;
 use postage::stream::Stream;
 use project::{search::SearchQuery, Entry, Project};
@@ -958,6 +963,113 @@ impl Default for ProjectSearchBar {
         Self::new()
     }
 }
+type CreatePath = fn(RectF, Color) -> Path;
+
+pub struct ButtonSide {
+    color: Color,
+    factory: CreatePath,
+}
+
+impl ButtonSide {
+    fn new(color: Color, factory: CreatePath) -> Self {
+        Self { color, factory }
+    }
+    pub fn left(color: Color) -> Self {
+        Self::new(color, left_button_side)
+    }
+    pub fn right(color: Color) -> Self {
+        Self::new(color, right_button_side)
+    }
+}
+
+fn left_button_side(bounds: RectF, color: Color) -> Path {
+    use gpui::geometry::PathBuilder;
+    let mut path = PathBuilder::new();
+    path.reset(bounds.lower_right());
+    path.line_to(bounds.upper_right());
+    let mut middle_point = bounds.origin();
+    let distance_to_line = (middle_point.y() - bounds.lower_left().y()) / 4.;
+    middle_point.set_y(middle_point.y() - distance_to_line);
+    path.curve_to(middle_point, bounds.origin());
+    let mut target = bounds.lower_left();
+    target.set_y(target.y() + distance_to_line);
+    path.line_to(target);
+    //path.curve_to(bounds.lower_right(), bounds.upper_right());
+    path.curve_to(bounds.lower_right(), bounds.lower_left());
+    path.build(color, None)
+}
+
+fn right_button_side(bounds: RectF, color: Color) -> Path {
+    use gpui::geometry::PathBuilder;
+    let mut path = PathBuilder::new();
+    path.reset(bounds.lower_left());
+    path.line_to(bounds.origin());
+    let mut middle_point = bounds.upper_right();
+    let distance_to_line = (middle_point.y() - bounds.lower_right().y()) / 4.;
+    middle_point.set_y(middle_point.y() - distance_to_line);
+    path.curve_to(middle_point, bounds.upper_right());
+    let mut target = bounds.lower_right();
+    target.set_y(target.y() + distance_to_line);
+    path.line_to(target);
+    //path.curve_to(bounds.lower_right(), bounds.upper_right());
+    path.curve_to(bounds.lower_left(), bounds.lower_right());
+    path.build(color, None)
+}
+
+impl Element<ProjectSearchBar> for ButtonSide {
+    type LayoutState = ();
+
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: gpui::SizeConstraint,
+        _: &mut ProjectSearchBar,
+        _: &mut LayoutContext<ProjectSearchBar>,
+    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
+        (constraint.max, ())
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        _: RectF,
+        _: &mut Self::LayoutState,
+        _: &mut ProjectSearchBar,
+        _: &mut ViewContext<ProjectSearchBar>,
+    ) -> Self::PaintState {
+        scene.push_path((self.factory)(bounds, self.color));
+    }
+
+    fn rect_for_text_range(
+        &self,
+        _: Range<usize>,
+        _: RectF,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &ProjectSearchBar,
+        _: &ViewContext<ProjectSearchBar>,
+    ) -> Option<RectF> {
+        None
+    }
+
+    fn debug(
+        &self,
+        bounds: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &ProjectSearchBar,
+        _: &ViewContext<ProjectSearchBar>,
+    ) -> gpui::json::Value {
+        json::json!({
+            "type": "ButtonSide",
+            "bounds": bounds.to_json(),
+            "color": self.color.to_json(),
+        })
+    }
+}
 
 impl ProjectSearchBar {
     pub fn new() -> Self {
@@ -1276,14 +1388,32 @@ impl ProjectSearchBar {
         let option = SearchOptions::REGEX;
         MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
             let theme = theme::current(cx);
-            let style = theme
+            let mut style = theme
                 .search
                 .option_button
                 .in_state(is_active)
-                .style_for(state);
-            Label::new(icon, style.text.clone())
-                .contained()
-                .with_style(style.container)
+                .style_for(state)
+                .clone();
+            style.container.border.right = false;
+            Flex::row()
+                .with_child(
+                    Label::new(icon, style.text.clone())
+                        .contained()
+                        .with_style(style.container),
+                )
+                .with_child(
+                    ButtonSide::right(
+                        style
+                            .container
+                            .background_color
+                            .unwrap_or_else(gpui::color::Color::transparent_black),
+                    )
+                    .contained()
+                    .constrained()
+                    .with_max_width(theme.titlebar.avatar_ribbon.width / 2.)
+                    .aligned()
+                    .bottom(),
+                )
         })
         .on_click(MouseButton::Left, move |_, this, cx| {
             this.toggle_search_option(option, cx);
@@ -1347,14 +1477,32 @@ impl ProjectSearchBar {
         enum NormalSearchTag {}
         MouseEventHandler::<NormalSearchTag, _>::new(region_id, cx, |state, cx| {
             let theme = theme::current(cx);
-            let style = theme
+            let mut style = theme
                 .search
                 .option_button
                 .in_state(is_active)
-                .style_for(state);
-            Label::new("Text", style.text.clone())
-                .contained()
-                .with_style(style.container)
+                .style_for(state)
+                .clone();
+            style.container.border.left = false;
+            Flex::row()
+                .with_child(
+                    ButtonSide::left(
+                        style
+                            .container
+                            .background_color
+                            .unwrap_or_else(gpui::color::Color::transparent_black),
+                    )
+                    .contained()
+                    .constrained()
+                    .with_max_width(theme.titlebar.avatar_ribbon.width / 2.)
+                    .aligned()
+                    .bottom(),
+                )
+                .with_child(
+                    Label::new("Text", style.text.clone())
+                        .contained()
+                        .with_style(style.container),
+                )
         })
         .on_click(MouseButton::Left, move |_, this, cx| {
             if let Some(search) = this.active_project_search.as_mut() {
@@ -1592,7 +1740,9 @@ impl View for ProjectSearchBar {
                             .with_child(normal_search)
                             .with_children(semantic_index)
                             .with_child(regex_button)
-                            .flex(1., true)
+                            .constrained()
+                            .with_height(theme.workspace.toolbar.height)
+                            .contained()
                             .aligned()
                             .right(),
                     ),

styles/src/style_tree/search.ts 🔗

@@ -44,11 +44,9 @@ export default function search(): any {
                 base: {
                     ...text(theme.highest, "mono", "on"),
                     background: background(theme.highest, "on"),
-                    corner_radius: 2,
+
                     border: border(theme.highest, "on"),
-                    margin: {
-                        right: 2,
-                    },
+
                     padding: {
                         bottom: 6,
                         left: 6,