Merge branch 'main' into project_search_design

Piotr Osiewicz created

Change summary

Cargo.lock                                         |   3 
assets/fonts/plex/IBMPlexSans-Bold.ttf             |   0 
assets/fonts/plex/IBMPlexSans-Italic.ttf           |   0 
assets/fonts/plex/IBMPlexSans-Regular.ttf          |   0 
assets/fonts/plex/LICENSE.txt                      |  93 +++++++++
crates/ai/src/assistant.rs                         |  22 -
crates/collab_ui/src/collab_titlebar_item.rs       |  14 
crates/collab_ui/src/contact_list.rs               |  10 
crates/copilot_button/src/copilot_button.rs        |   2 
crates/diagnostics/src/items.rs                    |   2 
crates/editor/src/editor.rs                        |   2 
crates/editor/src/element.rs                       |  26 +-
crates/editor/src/hover_popover.rs                 |   2 
crates/feedback/src/deploy_feedback_button.rs      |   2 
crates/feedback/src/submit_feedback_button.rs      |   2 
crates/gpui/examples/corner_radii.rs               | 155 ++++++++++++++++
crates/gpui/src/app/window.rs                      |  13 
crates/gpui/src/elements.rs                        |   8 
crates/gpui/src/elements/container.rs              |  22 +
crates/gpui/src/elements/image.rs                  |   2 
crates/gpui/src/elements/tooltip.rs                |   9 
crates/gpui/src/platform/mac/renderer.rs           |  23 +
crates/gpui/src/platform/mac/shaders/shaders.h     |  15 +
crates/gpui/src/platform/mac/shaders/shaders.metal |  64 +++++-
crates/gpui/src/platform/mac/window.rs             |   5 
crates/gpui/src/scene.rs                           |  68 ++++++
crates/gpui/src/scene/mouse_region.rs              |   2 
crates/gpui_macros/Cargo.toml                      |   2 
crates/gpui_macros/src/gpui_macros.rs              |   8 
crates/gpui_macros/tests/test.rs                   |  14 +
crates/search/src/project_search.rs                |   2 
crates/search/src/search_bar.rs                    |   8 
crates/terminal_view/src/terminal_element.rs       |   4 
crates/terminal_view/src/terminal_panel.rs         |   5 
crates/workspace/src/pane.rs                       |   8 
crates/workspace/src/pane/dragged_item_receiver.rs |   2 
crates/workspace/src/workspace.rs                  |   6 
crates/zed/Cargo.toml                              |   2 
crates/zed/src/main.rs                             |   4 
crates/zed/src/zed.rs                              |  11 
styles/src/common.ts                               |   1 
41 files changed, 528 insertions(+), 115 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3172,6 +3172,7 @@ dependencies = [
 name = "gpui_macros"
 version = "0.1.0"
 dependencies = [
+ "gpui",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
@@ -9860,7 +9861,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.99.0"
+version = "0.100.0"
 dependencies = [
  "activity_indicator",
  "ai",

assets/fonts/plex/LICENSE.txt 🔗

@@ -0,0 +1,93 @@
+Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"

+

+This Font Software is licensed under the SIL Open Font License, Version 1.1.

+

+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL

+

+

+-----------------------------------------------------------

+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

+-----------------------------------------------------------

+

+PREAMBLE

+The goals of the Open Font License (OFL) are to stimulate worldwide

+development of collaborative font projects, to support the font creation

+efforts of academic and linguistic communities, and to provide a free and

+open framework in which fonts may be shared and improved in partnership

+with others.

+

+The OFL allows the licensed fonts to be used, studied, modified and

+redistributed freely as long as they are not sold by themselves. The

+fonts, including any derivative works, can be bundled, embedded, 

+redistributed and/or sold with any software provided that any reserved

+names are not used by derivative works. The fonts and derivatives,

+however, cannot be released under any other type of license. The

+requirement for fonts to remain under this license does not apply

+to any document created using the fonts or their derivatives.

+

+DEFINITIONS

+"Font Software" refers to the set of files released by the Copyright

+Holder(s) under this license and clearly marked as such. This may

+include source files, build scripts and documentation.

+

+"Reserved Font Name" refers to any names specified as such after the

+copyright statement(s).

+

+"Original Version" refers to the collection of Font Software components as

+distributed by the Copyright Holder(s).

+

+"Modified Version" refers to any derivative made by adding to, deleting,

+or substituting -- in part or in whole -- any of the components of the

+Original Version, by changing formats or by porting the Font Software to a

+new environment.

+

+"Author" refers to any designer, engineer, programmer, technical

+writer or other person who contributed to the Font Software.

+

+PERMISSION & CONDITIONS

+Permission is hereby granted, free of charge, to any person obtaining

+a copy of the Font Software, to use, study, copy, merge, embed, modify,

+redistribute, and sell modified and unmodified copies of the Font

+Software, subject to the following conditions:

+

+1) Neither the Font Software nor any of its individual components,

+in Original or Modified Versions, may be sold by itself.

+

+2) Original or Modified Versions of the Font Software may be bundled,

+redistributed and/or sold with any software, provided that each copy

+contains the above copyright notice and this license. These can be

+included either as stand-alone text files, human-readable headers or

+in the appropriate machine-readable metadata fields within text or

+binary files as long as those fields can be easily viewed by the user.

+

+3) No Modified Version of the Font Software may use the Reserved Font

+Name(s) unless explicit written permission is granted by the corresponding

+Copyright Holder. This restriction only applies to the primary font name as

+presented to the users.

+

+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font

+Software shall not be used to promote, endorse or advertise any

+Modified Version, except to acknowledge the contribution(s) of the

+Copyright Holder(s) and the Author(s) or with their explicit written

+permission.

+

+5) The Font Software, modified or unmodified, in part or in whole,

+must be distributed entirely under this license, and must not be

+distributed under any other license. The requirement for fonts to

+remain under this license does not apply to any document created

+using the Font Software.

+

+TERMINATION

+This license becomes null and void if any of the above conditions are

+not met.

+

+DISCLAIMER

+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF

+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT

+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE

+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL

+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM

+OTHER DEALINGS IN THE FONT SOFTWARE.

crates/ai/src/assistant.rs 🔗

@@ -362,7 +362,7 @@ impl AssistantPanel {
                 this.set_active_editor_index(this.prev_active_editor_index, cx);
             }
         })
-        .with_tooltip::<History>(1, "History".into(), None, tooltip_style, cx)
+        .with_tooltip::<History>(1, "History", None, tooltip_style, cx)
     }
 
     fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement<Self>> {
@@ -394,7 +394,7 @@ impl AssistantPanel {
         })
         .with_tooltip::<Split>(
             1,
-            "Split Message".into(),
+            "Split Message",
             Some(Box::new(Split)),
             tooltip_style,
             cx,
@@ -416,13 +416,7 @@ impl AssistantPanel {
                 active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
             }
         })
-        .with_tooltip::<Assist>(
-            1,
-            "Assist".into(),
-            Some(Box::new(Assist)),
-            tooltip_style,
-            cx,
-        )
+        .with_tooltip::<Assist>(1, "Assist", Some(Box::new(Assist)), tooltip_style, cx)
     }
 
     fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
@@ -446,7 +440,7 @@ impl AssistantPanel {
         })
         .with_tooltip::<QuoteSelection>(
             1,
-            "Quote Selection".into(),
+            "Quote Selection",
             Some(Box::new(QuoteSelection)),
             tooltip_style,
             cx,
@@ -468,7 +462,7 @@ impl AssistantPanel {
         })
         .with_tooltip::<NewConversation>(
             1,
-            "New Conversation".into(),
+            "New Conversation",
             Some(Box::new(NewConversation)),
             tooltip_style,
             cx,
@@ -498,11 +492,7 @@ impl AssistantPanel {
         })
         .with_tooltip::<ToggleZoom>(
             0,
-            if self.zoomed {
-                "Zoom Out".into()
-            } else {
-                "Zoom In".into()
-            },
+            if self.zoomed { "Zoom Out" } else { "Zoom In" },
             Some(Box::new(ToggleZoom)),
             tooltip_style,
             cx,

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -238,7 +238,7 @@ impl CollabTitlebarItem {
                             .left()
                             .with_tooltip::<RecentProjectsTooltip>(
                                 0,
-                                "Recent projects".into(),
+                                "Recent projects",
                                 Some(Box::new(recent_projects::OpenRecent)),
                                 theme.tooltip.clone(),
                                 cx,
@@ -282,7 +282,7 @@ impl CollabTitlebarItem {
                                             .left()
                                             .with_tooltip::<BranchPopoverTooltip>(
                                                 0,
-                                                "Recent branches".into(),
+                                                "Recent branches",
                                                 Some(Box::new(ToggleVcsMenu)),
                                                 theme.tooltip.clone(),
                                                 cx,
@@ -582,7 +582,7 @@ impl CollabTitlebarItem {
                 })
                 .with_tooltip::<ToggleContactsMenu>(
                     0,
-                    "Show contacts menu".into(),
+                    "Show contacts menu",
                     Some(Box::new(ToggleContactsMenu)),
                     theme.tooltip.clone(),
                     cx,
@@ -633,7 +633,7 @@ impl CollabTitlebarItem {
         })
         .with_tooltip::<ToggleScreenSharing>(
             0,
-            tooltip.into(),
+            tooltip,
             Some(Box::new(ToggleScreenSharing)),
             theme.tooltip.clone(),
             cx,
@@ -686,7 +686,7 @@ impl CollabTitlebarItem {
         })
         .with_tooltip::<ToggleMute>(
             0,
-            tooltip.into(),
+            tooltip,
             Some(Box::new(ToggleMute)),
             theme.tooltip.clone(),
             cx,
@@ -734,7 +734,7 @@ impl CollabTitlebarItem {
         })
         .with_tooltip::<ToggleDeafen>(
             0,
-            tooltip.into(),
+            tooltip,
             Some(Box::new(ToggleDeafen)),
             theme.tooltip.clone(),
             cx,
@@ -768,7 +768,7 @@ impl CollabTitlebarItem {
         })
         .with_tooltip::<LeaveCall>(
             0,
-            tooltip.into(),
+            tooltip,
             Some(Box::new(LeaveCall)),
             theme.tooltip.clone(),
             cx,

crates/collab_ui/src/contact_list.rs 🔗

@@ -837,7 +837,7 @@ impl ContactList {
                                 ),
                                 background: Some(tree_branch.color),
                                 border: gpui::Border::default(),
-                                corner_radius: 0.,
+                                corner_radii: Default::default(),
                             });
                             scene.push_quad(gpui::Quad {
                                 bounds: RectF::from_points(
@@ -846,7 +846,7 @@ impl ContactList {
                                 ),
                                 background: Some(tree_branch.color),
                                 border: gpui::Border::default(),
-                                corner_radius: 0.,
+                                corner_radii: Default::default(),
                             });
                         }))
                         .constrained()
@@ -934,7 +934,7 @@ impl ContactList {
                                     ),
                                     background: Some(tree_branch.color),
                                     border: gpui::Border::default(),
-                                    corner_radius: 0.,
+                                    corner_radii: Default::default(),
                                 });
                                 scene.push_quad(gpui::Quad {
                                     bounds: RectF::from_points(
@@ -943,7 +943,7 @@ impl ContactList {
                                     ),
                                     background: Some(tree_branch.color),
                                     border: gpui::Border::default(),
-                                    corner_radius: 0.,
+                                    corner_radii: Default::default(),
                                 });
                             }))
                             .constrained()
@@ -1345,7 +1345,7 @@ impl View for ContactList {
                         })
                         .with_tooltip::<AddContact>(
                             0,
-                            "Search for new contact".into(),
+                            "Search for new contact",
                             None,
                             theme.tooltip.clone(),
                             cx,

crates/diagnostics/src/items.rs 🔗

@@ -173,7 +173,7 @@ impl View for DiagnosticIndicator {
             })
             .with_tooltip::<Summary>(
                 0,
-                "Project Diagnostics".to_string(),
+                "Project Diagnostics",
                 Some(Box::new(crate::Deploy)),
                 tooltip_style,
                 cx,

crates/editor/src/editor.rs 🔗

@@ -8685,7 +8685,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
         // We really need to rethink this ID system...
         .with_tooltip::<BlockContextToolip>(
             cx.block_id,
-            "Copy diagnostic message".to_string(),
+            "Copy diagnostic message",
             None,
             tooltip_style,
             cx,

crates/editor/src/element.rs 🔗

@@ -488,13 +488,13 @@ impl EditorElement {
             bounds: gutter_bounds,
             background: Some(self.style.gutter_background),
             border: Border::new(0., Color::transparent_black()),
-            corner_radius: 0.,
+            corner_radii: Default::default(),
         });
         scene.push_quad(Quad {
             bounds: text_bounds,
             background: Some(self.style.background),
             border: Border::new(0., Color::transparent_black()),
-            corner_radius: 0.,
+            corner_radii: Default::default(),
         });
 
         if let EditorMode::Full = layout.mode {
@@ -522,7 +522,7 @@ impl EditorElement {
                         bounds: RectF::new(origin, size),
                         background: Some(self.style.active_line_background),
                         border: Border::default(),
-                        corner_radius: 0.,
+                        corner_radii: Default::default(),
                     });
                 }
             }
@@ -542,7 +542,7 @@ impl EditorElement {
                     bounds: RectF::new(origin, size),
                     background: Some(self.style.highlighted_line_background),
                     border: Border::default(),
-                    corner_radius: 0.,
+                    corner_radii: Default::default(),
                 });
             }
 
@@ -572,7 +572,7 @@ impl EditorElement {
                     ),
                     background: Some(color),
                     border: Border::new(0., Color::transparent_black()),
-                    corner_radius: 0.,
+                    corner_radii: Default::default(),
                 });
             }
         }
@@ -673,7 +673,7 @@ impl EditorElement {
                         bounds: highlight_bounds,
                         background: Some(diff_style.modified),
                         border: Border::new(0., Color::transparent_black()),
-                        corner_radius: 1. * line_height,
+                        corner_radii: (1. * line_height).into(),
                     });
 
                     continue;
@@ -706,7 +706,7 @@ impl EditorElement {
                         bounds: highlight_bounds,
                         background: Some(diff_style.deleted),
                         border: Border::new(0., Color::transparent_black()),
-                        corner_radius: 1. * line_height,
+                        corner_radii: (1. * line_height).into(),
                     });
 
                     continue;
@@ -728,7 +728,7 @@ impl EditorElement {
                 bounds: highlight_bounds,
                 background: Some(color),
                 border: Border::new(0., Color::transparent_black()),
-                corner_radius: diff_style.corner_radius * line_height,
+                corner_radii: (diff_style.corner_radius * line_height).into(),
             });
         }
     }
@@ -1129,7 +1129,7 @@ impl EditorElement {
                         bounds,
                         background: Some(color),
                         border,
-                        corner_radius: style.thumb.corner_radius,
+                        corner_radii: style.thumb.corner_radii.into(),
                     })
                 };
                 let background_ranges = editor
@@ -1189,7 +1189,7 @@ impl EditorElement {
                         bounds,
                         background: Some(color),
                         border,
-                        corner_radius: style.thumb.corner_radius,
+                        corner_radii: style.thumb.corner_radii.into(),
                     })
                 }
             }
@@ -1198,7 +1198,7 @@ impl EditorElement {
                 bounds: thumb_bounds,
                 border: style.thumb.border,
                 background: style.thumb.background_color,
-                corner_radius: style.thumb.corner_radius,
+                corner_radii: style.thumb.corner_radii.into(),
             });
         }
 
@@ -2725,14 +2725,14 @@ impl Cursor {
                 bounds,
                 background: None,
                 border: Border::all(1., self.color),
-                corner_radius: 0.,
+                corner_radii: Default::default(),
             });
         } else {
             scene.push_quad(Quad {
                 bounds,
                 background: Some(self.color),
                 border: Default::default(),
-                corner_radius: 0.,
+                corner_radii: Default::default(),
             });
         }
 

crates/editor/src/hover_popover.rs 🔗

@@ -599,7 +599,7 @@ impl InfoPopover {
                                         bounds,
                                         background: Some(code_span_background_color),
                                         border: Default::default(),
-                                        corner_radius: 2.0,
+                                        corner_radii: (2.0).into(),
                                     });
                                 }
                             },

crates/feedback/src/submit_feedback_button.rs 🔗

@@ -80,7 +80,7 @@ impl View for SubmitFeedbackButton {
         .with_margin_left(theme.feedback.button_margin)
         .with_tooltip::<Self>(
             0,
-            "cmd-s".into(),
+            "cmd-s",
             Some(Box::new(SubmitFeedback)),
             theme.tooltip.clone(),
             cx,

crates/gpui/examples/corner_radii.rs 🔗

@@ -0,0 +1,155 @@
+use gpui::{
+    color::Color, geometry::rect::RectF, scene::Shadow, AnyElement, App, Element, Entity, Quad,
+    View,
+};
+use log::LevelFilter;
+use pathfinder_geometry::vector::vec2f;
+use simplelog::SimpleLogger;
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    App::new(()).unwrap().run(|cx| {
+        cx.platform().activate(true);
+        cx.add_window(Default::default(), |_| CornersView);
+    });
+}
+
+struct CornersView;
+
+impl Entity for CornersView {
+    type Event = ();
+}
+
+impl View for CornersView {
+    fn ui_name() -> &'static str {
+        "CornersView"
+    }
+
+    fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<CornersView> {
+        CornersElement.into_any()
+    }
+}
+
+struct CornersElement;
+
+impl<V: View> gpui::Element<V> for CornersElement {
+    type LayoutState = ();
+
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: gpui::SizeConstraint,
+        _: &mut V,
+        _: &mut gpui::LayoutContext<V>,
+    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
+        (constraint.max, ())
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut gpui::SceneBuilder,
+        bounds: pathfinder_geometry::rect::RectF,
+        _: pathfinder_geometry::rect::RectF,
+        _: &mut Self::LayoutState,
+        _: &mut V,
+        _: &mut gpui::PaintContext<V>,
+    ) -> Self::PaintState {
+        scene.push_quad(Quad {
+            bounds,
+            background: Some(Color::white()),
+            ..Default::default()
+        });
+
+        scene.push_layer(None);
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
+            background: Some(Color::red()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                top_left: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
+            background: Some(Color::green()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                top_right: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
+            background: Some(Color::blue()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                bottom_left: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
+            background: Some(Color::yellow()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                bottom_right: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_shadow(Shadow {
+            bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
+            corner_radii: gpui::scene::CornerRadii {
+                bottom_right: 20.,
+                ..Default::default()
+            },
+            sigma: 20.0,
+            color: Color::black(),
+        });
+
+        scene.push_layer(None);
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
+            background: Some(Color::red()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                bottom_right: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.pop_layer();
+        scene.pop_layer();
+    }
+
+    fn rect_for_text_range(
+        &self,
+        _: std::ops::Range<usize>,
+        _: pathfinder_geometry::rect::RectF,
+        _: pathfinder_geometry::rect::RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &V,
+        _: &gpui::ViewContext<V>,
+    ) -> Option<pathfinder_geometry::rect::RectF> {
+        unimplemented!()
+    }
+
+    fn debug(
+        &self,
+        _: pathfinder_geometry::rect::RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &V,
+        _: &gpui::ViewContext<V>,
+    ) -> serde_json::Value {
+        unimplemented!()
+    }
+}

crates/gpui/src/app/window.rs 🔗

@@ -52,8 +52,8 @@ pub struct Window {
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
     last_mouse_moved_event: Option<Event>,
-    pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
-    pub(crate) clicked_region_ids: HashSet<MouseRegionId>,
+    pub(crate) hovered_region_ids: Vec<MouseRegionId>,
+    pub(crate) clicked_region_ids: Vec<MouseRegionId>,
     pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
     mouse_position: Vector2F,
     text_layout_cache: TextLayoutCache,
@@ -678,6 +678,7 @@ impl<'a> WindowContext<'a> {
                     let mut highest_z_index = None;
                     let mouse_position = self.window.mouse_position.clone();
                     let window = &mut *self.window;
+                    let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
                     for (region, z_index) in window.mouse_regions.iter().rev() {
                         // Allow mouse regions to appear transparent to hovers
                         if !region.hoverable {
@@ -696,7 +697,11 @@ impl<'a> WindowContext<'a> {
                         // highest_z_index is set.
                         if contains_mouse && z_index == highest_z_index.unwrap() {
                             //Ensure that hover entrance events aren't sent twice
-                            if window.hovered_region_ids.insert(region.id()) {
+                            if let Err(ix) = window.hovered_region_ids.binary_search(&region.id()) {
+                                window.hovered_region_ids.insert(ix, region.id());
+                            }
+                            // window.hovered_region_ids.insert(region.id());
+                            if !prev_hovered_regions.contains(&region.id()) {
                                 valid_regions.push(region.clone());
                                 if region.notify_on_hover {
                                     notified_views.insert(region.id().view_id());
@@ -704,7 +709,7 @@ impl<'a> WindowContext<'a> {
                             }
                         } else {
                             // Ensure that hover exit events aren't sent twice
-                            if window.hovered_region_ids.remove(&region.id()) {
+                            if prev_hovered_regions.contains(&region.id()) {
                                 valid_regions.push(region.clone());
                                 if region.notify_on_hover {
                                     notified_views.insert(region.id().view_id());

crates/gpui/src/elements.rs 🔗

@@ -170,7 +170,7 @@ pub trait Element<V: View>: 'static {
     fn with_tooltip<Tag: 'static>(
         self,
         id: usize,
-        text: String,
+        text: impl Into<Cow<'static, str>>,
         action: Option<Box<dyn Action>>,
         style: TooltipStyle,
         cx: &mut ViewContext<V>,
@@ -178,7 +178,7 @@ pub trait Element<V: View>: 'static {
     where
         Self: 'static + Sized,
     {
-        Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
+        Tooltip::new::<Tag>(id, text, action, style, self.into_any(), cx)
     }
 
     fn resizable(
@@ -201,6 +201,10 @@ pub trait Element<V: View>: 'static {
     }
 }
 
+pub trait RenderElement {
+    fn render<V: View>(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
+}
+
 trait AnyElementState<V: View> {
     fn layout(
         &mut self,

crates/gpui/src/elements/container.rs 🔗

@@ -9,7 +9,7 @@ use crate::{
     },
     json::ToJson,
     platform::CursorStyle,
-    scene::{self, Border, CursorRegion, Quad},
+    scene::{self, Border, CornerRadii, CursorRegion, Quad},
     AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
     ViewContext,
 };
@@ -30,7 +30,8 @@ pub struct ContainerStyle {
     #[serde(default)]
     pub border: Border,
     #[serde(default)]
-    pub corner_radius: f32,
+    #[serde(alias = "corner_radius")]
+    pub corner_radii: CornerRadii,
     #[serde(default)]
     pub shadow: Option<Shadow>,
     #[serde(default)]
@@ -133,7 +134,10 @@ impl<V: View> Container<V> {
     }
 
     pub fn with_corner_radius(mut self, radius: f32) -> Self {
-        self.style.corner_radius = radius;
+        self.style.corner_radii.top_left = radius;
+        self.style.corner_radii.top_right = radius;
+        self.style.corner_radii.bottom_right = radius;
+        self.style.corner_radii.bottom_left = radius;
         self
     }
 
@@ -225,7 +229,7 @@ impl<V: View> Element<V> for Container<V> {
         if let Some(shadow) = self.style.shadow.as_ref() {
             scene.push_shadow(scene::Shadow {
                 bounds: quad_bounds + shadow.offset,
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radii,
                 sigma: shadow.blur,
                 color: shadow.color,
             });
@@ -248,7 +252,7 @@ impl<V: View> Element<V> for Container<V> {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: Default::default(),
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radii.into(),
             });
 
             self.child
@@ -259,7 +263,7 @@ impl<V: View> Element<V> for Container<V> {
                 bounds: quad_bounds,
                 background: self.style.overlay_color,
                 border: self.style.border,
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radii.into(),
             });
             scene.pop_layer();
         } else {
@@ -267,7 +271,7 @@ impl<V: View> Element<V> for Container<V> {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: self.style.border,
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radii.into(),
             });
 
             let child_origin = child_origin
@@ -284,7 +288,7 @@ impl<V: View> Element<V> for Container<V> {
                     bounds: quad_bounds,
                     background: self.style.overlay_color,
                     border: Default::default(),
-                    corner_radius: 0.,
+                    corner_radii: self.style.corner_radii.into(),
                 });
                 scene.pop_layer();
             }
@@ -328,7 +332,7 @@ impl ToJson for ContainerStyle {
             "padding": self.padding.to_json(),
             "background_color": self.background_color.to_json(),
             "border": self.border.to_json(),
-            "corner_radius": self.corner_radius,
+            "corner_radius": self.corner_radii,
             "shadow": self.shadow.to_json(),
         })
     }

crates/gpui/src/elements/image.rs 🔗

@@ -103,7 +103,7 @@ impl<V: View> Element<V> for Image {
             scene.push_image(scene::Image {
                 bounds,
                 border: self.style.border,
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radius.into(),
                 grayscale: self.style.grayscale,
                 data: data.clone(),
             });

crates/gpui/src/elements/tooltip.rs 🔗

@@ -12,6 +12,7 @@ use crate::{
 use schemars::JsonSchema;
 use serde::Deserialize;
 use std::{
+    borrow::Cow,
     cell::{Cell, RefCell},
     ops::Range,
     rc::Rc,
@@ -52,9 +53,9 @@ pub struct KeystrokeStyle {
 }
 
 impl<V: View> Tooltip<V> {
-    pub fn new<Tag: 'static, T: View>(
+    pub fn new<Tag: 'static>(
         id: usize,
-        text: String,
+        text: impl Into<Cow<'static, str>>,
         action: Option<Box<dyn Action>>,
         style: TooltipStyle,
         child: AnyElement<V>,
@@ -66,6 +67,8 @@ impl<V: View> Tooltip<V> {
 
         let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
         let state = state_handle.read(cx).clone();
+        let text = text.into();
+
         let tooltip = if state.visible.get() {
             let mut collapsed_tooltip = Self::render_tooltip(
                 focused_view_id,
@@ -127,7 +130,7 @@ impl<V: View> Tooltip<V> {
 
     pub fn render_tooltip(
         focused_view_id: Option<usize>,
-        text: String,
+        text: impl Into<Cow<'static, str>>,
         style: TooltipStyle,
         action: Option<Box<dyn Action>>,
         measure: bool,

crates/gpui/src/platform/mac/renderer.rs 🔗

@@ -509,10 +509,14 @@ impl Renderer {
         };
         for (ix, shadow) in shadows.iter().enumerate() {
             let shape_bounds = shadow.bounds * scale_factor;
+            let corner_radii = shadow.corner_radii * scale_factor;
             let shader_shadow = shaders::GPUIShadow {
                 origin: shape_bounds.origin().to_float2(),
                 size: shape_bounds.size().to_float2(),
-                corner_radius: shadow.corner_radius * scale_factor,
+                corner_radius_top_left: corner_radii.top_left,
+                corner_radius_top_right: corner_radii.top_right,
+                corner_radius_bottom_right: corner_radii.bottom_right,
+                corner_radius_bottom_left: corner_radii.bottom_left,
                 sigma: shadow.sigma,
                 color: shadow.color.to_uchar4(),
             };
@@ -586,7 +590,10 @@ impl Renderer {
                 border_bottom: border_width * (quad.border.bottom as usize as f32),
                 border_left: border_width * (quad.border.left as usize as f32),
                 border_color: quad.border.color.to_uchar4(),
-                corner_radius: quad.corner_radius * scale_factor,
+                corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
+                corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
+                corner_radius_bottom_right: quad.corner_radii.bottom_right * scale_factor,
+                corner_radius_bottom_left: quad.corner_radii.bottom_left * scale_factor,
             };
             unsafe {
                 *(buffer_contents.add(ix)) = shader_quad;
@@ -738,7 +745,7 @@ impl Renderer {
         for image in images {
             let origin = image.bounds.origin() * scale_factor;
             let target_size = image.bounds.size() * scale_factor;
-            let corner_radius = image.corner_radius * scale_factor;
+            let corner_radii = image.corner_radii * scale_factor;
             let border_width = image.border.width * scale_factor;
             let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
             images_by_atlas
@@ -754,7 +761,10 @@ impl Renderer {
                     border_bottom: border_width * (image.border.bottom as usize as f32),
                     border_left: border_width * (image.border.left as usize as f32),
                     border_color: image.border.color.to_uchar4(),
-                    corner_radius,
+                    corner_radius_top_left: corner_radii.top_left,
+                    corner_radius_top_right: corner_radii.top_right,
+                    corner_radius_bottom_right: corner_radii.bottom_right,
+                    corner_radius_bottom_left: corner_radii.bottom_left,
                     grayscale: image.grayscale as u8,
                 });
         }
@@ -777,7 +787,10 @@ impl Renderer {
                         border_bottom: 0.,
                         border_left: 0.,
                         border_color: Default::default(),
-                        corner_radius: 0.,
+                        corner_radius_top_left: 0.,
+                        corner_radius_top_right: 0.,
+                        corner_radius_bottom_right: 0.,
+                        corner_radius_bottom_left: 0.,
                         grayscale: false as u8,
                     });
             } else {

crates/gpui/src/platform/mac/shaders/shaders.h 🔗

@@ -19,7 +19,10 @@ typedef struct {
   float border_bottom;
   float border_left;
   vector_uchar4 border_color;
-  float corner_radius;
+  float corner_radius_top_left;
+  float corner_radius_top_right;
+  float corner_radius_bottom_right;
+  float corner_radius_bottom_left;
 } GPUIQuad;
 
 typedef enum {
@@ -31,7 +34,10 @@ typedef enum {
 typedef struct {
   vector_float2 origin;
   vector_float2 size;
-  float corner_radius;
+  float corner_radius_top_left;
+  float corner_radius_top_right;
+  float corner_radius_bottom_right;
+  float corner_radius_bottom_left;
   float sigma;
   vector_uchar4 color;
 } GPUIShadow;
@@ -89,7 +95,10 @@ typedef struct {
   float border_bottom;
   float border_left;
   vector_uchar4 border_color;
-  float corner_radius;
+  float corner_radius_top_left;
+  float corner_radius_top_right;
+  float corner_radius_bottom_right;
+  float corner_radius_bottom_left;
   uint8_t grayscale;
 } GPUIImage;
 

crates/gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -43,7 +43,10 @@ struct QuadFragmentInput {
     float border_bottom;
     float border_left;
     float4 border_color;
-    float corner_radius;
+    float corner_radius_top_left;
+    float corner_radius_top_right;
+    float corner_radius_bottom_right;
+    float corner_radius_bottom_left;
     uchar grayscale; // only used in image shader
 };
 
@@ -51,12 +54,27 @@ float4 quad_sdf(QuadFragmentInput input) {
     float2 half_size = input.size / 2.;
     float2 center = input.origin + half_size;
     float2 center_to_point = input.position.xy - center;
-    float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius;
-    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - input.corner_radius;
+    float corner_radius;
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            corner_radius = input.corner_radius_top_left;
+        } else {
+            corner_radius = input.corner_radius_bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            corner_radius = input.corner_radius_top_right;
+        } else {
+            corner_radius = input.corner_radius_bottom_right;
+        }
+    }
+
+    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
+    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
 
     float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
     float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
-    float2 inset_size = half_size - input.corner_radius - float2(vertical_border, horizontal_border);
+    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
     float2 point_to_inset_corner = abs(center_to_point) - inset_size;
     float border_width;
     if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
@@ -110,7 +128,10 @@ vertex QuadFragmentInput quad_vertex(
         quad.border_bottom,
         quad.border_left,
         coloru_to_colorf(quad.border_color),
-        quad.corner_radius,
+        quad.corner_radius_top_left,
+        quad.corner_radius_top_right,
+        quad.corner_radius_bottom_right,
+        quad.corner_radius_bottom_left,
         0,
     };
 }
@@ -125,7 +146,10 @@ struct ShadowFragmentInput {
     float4 position [[position]];
     vector_float2 origin;
     vector_float2 size;
-    float corner_radius;
+    float corner_radius_top_left;
+    float corner_radius_top_right;
+    float corner_radius_bottom_right;
+    float corner_radius_bottom_left;
     float sigma;
     vector_uchar4 color;
 };
@@ -148,7 +172,10 @@ vertex ShadowFragmentInput shadow_vertex(
         device_position,
         shadow.origin,
         shadow.size,
-        shadow.corner_radius,
+        shadow.corner_radius_top_left,
+        shadow.corner_radius_top_right,
+        shadow.corner_radius_bottom_right,
+        shadow.corner_radius_bottom_left,
         shadow.sigma,
         shadow.color,
     };
@@ -158,10 +185,24 @@ fragment float4 shadow_fragment(
     ShadowFragmentInput input [[stage_in]]
 ) {
     float sigma = input.sigma;
-    float corner_radius = input.corner_radius;
     float2 half_size = input.size / 2.;
     float2 center = input.origin + half_size;
     float2 point = input.position.xy - center;
+    float2 center_to_point = input.position.xy - center;
+    float corner_radius;
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            corner_radius = input.corner_radius_top_left;
+        } else {
+            corner_radius = input.corner_radius_bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            corner_radius = input.corner_radius_top_right;
+        } else {
+            corner_radius = input.corner_radius_bottom_right;
+        }
+    }
 
     // The signal is only non-zero in a limited range, so don't waste samples
     float low = point.y - half_size.y;
@@ -252,7 +293,10 @@ vertex QuadFragmentInput image_vertex(
         image.border_bottom,
         image.border_left,
         coloru_to_colorf(image.border_color),
-        image.corner_radius,
+        image.corner_radius_top_left,
+        image.corner_radius_top_right,
+        image.corner_radius_bottom_right,
+        image.corner_radius_bottom_left,
         image.grayscale,
     };
 }
@@ -266,7 +310,7 @@ fragment float4 image_fragment(
     if (input.grayscale) {
         float grayscale =
             0.2126 * input.background_color.r +
-            0.7152 * input.background_color.g + 
+            0.7152 * input.background_color.g +
             0.0722 * input.background_color.b;
         input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
     }

crates/gpui/src/platform/mac/window.rs 🔗

@@ -1087,7 +1087,10 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                 button: MouseButton::Left,
                 modifiers: Modifiers { ctrl: true, .. },
                 ..
-            }) => return,
+            }) => {
+                window_state_borrow.synthetic_drag_counter += 1;
+                return;
+            }
 
             _ => None,
         };

crates/gpui/src/scene.rs 🔗

@@ -3,8 +3,10 @@ mod mouse_region;
 
 #[cfg(debug_assertions)]
 use collections::HashSet;
+use derive_more::Mul;
 use schemars::JsonSchema;
 use serde::Deserialize;
+use serde_derive::Serialize;
 use serde_json::json;
 use std::{borrow::Cow, sync::Arc};
 
@@ -65,13 +67,73 @@ pub struct Quad {
     pub bounds: RectF,
     pub background: Option<Color>,
     pub border: Border,
-    pub corner_radius: f32,
+    pub corner_radii: CornerRadii,
+}
+
+#[derive(Default, Debug, Mul, Clone, Copy, Serialize, JsonSchema)]
+pub struct CornerRadii {
+    pub top_left: f32,
+    pub top_right: f32,
+    pub bottom_right: f32,
+    pub bottom_left: f32,
+}
+
+impl<'de> Deserialize<'de> for CornerRadii {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        pub struct CornerRadiiHelper {
+            pub top_left: Option<f32>,
+            pub top_right: Option<f32>,
+            pub bottom_right: Option<f32>,
+            pub bottom_left: Option<f32>,
+        }
+
+        #[derive(Deserialize)]
+        #[serde(untagged)]
+        enum RadiusOrRadii {
+            Radius(f32),
+            Radii(CornerRadiiHelper),
+        }
+
+        let json = RadiusOrRadii::deserialize(deserializer)?;
+
+        let result = match json {
+            RadiusOrRadii::Radius(radius) => CornerRadii::from(radius),
+            RadiusOrRadii::Radii(CornerRadiiHelper {
+                top_left,
+                top_right,
+                bottom_right,
+                bottom_left,
+            }) => CornerRadii {
+                top_left: top_left.unwrap_or(0.0),
+                top_right: top_right.unwrap_or(0.0),
+                bottom_right: bottom_right.unwrap_or(0.0),
+                bottom_left: bottom_left.unwrap_or(0.0),
+            },
+        };
+
+        Ok(result)
+    }
+}
+
+impl From<f32> for CornerRadii {
+    fn from(radius: f32) -> Self {
+        Self {
+            top_left: radius,
+            top_right: radius,
+            bottom_right: radius,
+            bottom_left: radius,
+        }
+    }
 }
 
 #[derive(Debug)]
 pub struct Shadow {
     pub bounds: RectF,
-    pub corner_radius: f32,
+    pub corner_radii: CornerRadii,
     pub sigma: f32,
     pub color: Color,
 }
@@ -177,7 +239,7 @@ pub struct PathVertex {
 pub struct Image {
     pub bounds: RectF,
     pub border: Border,
-    pub corner_radius: f32,
+    pub corner_radii: CornerRadii,
     pub grayscale: bool,
     pub data: Arc<ImageData>,
 }

crates/gpui/src/scene/mouse_region.rs 🔗

@@ -177,7 +177,7 @@ impl MouseRegion {
     }
 }
 
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
 pub struct MouseRegionId {
     view_id: usize,
     tag: TypeId,

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -283,8 +283,12 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
 
     // The name of the struct/enum
     let name = input.ident;
+    let must_implement = format_ident!("{}MustImplementRenderElement", name);
 
     let expanded = quote! {
+        trait #must_implement : gpui::elements::RenderElement {}
+        impl #must_implement for #name {}
+
         impl<V: gpui::View> gpui::elements::Element<V> for #name {
             type LayoutState = gpui::elements::AnyElement<V>;
             type PaintState = ();
@@ -307,7 +311,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
                 visible_bounds: gpui::geometry::rect::RectF,
                 element: &mut gpui::elements::AnyElement<V>,
                 view: &mut V,
-                cx: &mut gpui::ViewContext<V>,
+                cx: &mut gpui::PaintContext<V>,
             ) {
                 element.paint(scene, bounds.origin(), visible_bounds, view, cx);
             }
@@ -332,7 +336,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
                 _: &(),
                 view: &V,
                 cx: &gpui::ViewContext<V>,
-            ) -> serde_json::Value {
+            ) -> gpui::serde_json::Value {
                 element.debug(view, cx)
             }
         }

crates/gpui_macros/tests/test.rs 🔗

@@ -0,0 +1,14 @@
+use gpui::{elements::RenderElement, View, ViewContext};
+use gpui_macros::Element;
+
+#[test]
+fn test_derive_render_element() {
+    #[derive(Element)]
+    struct TestElement {}
+
+    impl RenderElement for TestElement {
+        fn render<V: View>(&mut self, _: &mut V, _: &mut ViewContext<V>) -> gpui::AnyElement<V> {
+            unimplemented!()
+        }
+    }
+}

crates/search/src/project_search.rs 🔗

@@ -1438,7 +1438,7 @@ impl View for ProjectSearchBar {
                 .with_cursor_style(CursorStyle::PointingHand)
                 .with_tooltip::<Self>(
                     0,
-                    "Toggle filters".into(),
+                    "Toggle filters",
                     Some(Box::new(ToggleFilters)),
                     tooltip_style,
                     cx,

crates/search/src/search_bar.rs 🔗

@@ -65,8 +65,8 @@ pub(super) fn render_nav_button<V: View>(
     MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
         let theme = theme::current(cx);
         let mut style = theme.search.nav_button.style_for(state).clone();
-        let button_side_width = style.container.corner_radius;
-        style.container.corner_radius = 0.;
+        let button_side_width = style.container.corner_radii.top_left;
+        style.container.corner_radii = (0.).into();
         let label = Label::new(icon, style.label.clone())
             .contained()
             .with_style(style.container.clone());
@@ -137,8 +137,8 @@ pub(crate) fn render_search_mode_button<V: View>(
             .in_state(is_active)
             .style_for(state)
             .clone();
-        let side_width = style.container.corner_radius;
-        style.container.corner_radius = 0.;
+        let side_width = style.container.corner_radii.top_left;
+        style.container.corner_radii = (0.).into();
         if mode.button_side().is_some() {
             style.container.border.left = mode.border_left();
             style.container.border.right = mode.border_right();

crates/terminal_view/src/terminal_element.rs 🔗

@@ -153,7 +153,7 @@ impl LayoutRect {
             bounds: RectF::new(position, size),
             background: Some(self.color),
             border: Default::default(),
-            corner_radius: 0.,
+            corner_radii: Default::default(),
         })
     }
 }
@@ -763,7 +763,7 @@ impl Element<TerminalView> for TerminalElement {
                     bounds: RectF::new(bounds.origin(), bounds.size()),
                     background: Some(layout.background_color),
                     border: Default::default(),
-                    corner_radius: 0.,
+                    corner_radii: Default::default(),
                 });
 
                 for rect in &layout.rects {

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -72,10 +72,7 @@ impl TerminalPanel {
                         0,
                         "icons/plus_12.svg",
                         false,
-                        Some((
-                            "New Terminal".into(),
-                            Some(Box::new(workspace::NewTerminal)),
-                        )),
+                        Some(("New Terminal", Some(Box::new(workspace::NewTerminal)))),
                         cx,
                         move |_, cx| {
                             let this = this.clone();

crates/workspace/src/pane.rs 🔗

@@ -409,10 +409,10 @@ impl Pane {
                         let tooltip_label;
                         if pane.is_zoomed() {
                             icon_path = "icons/minimize_8.svg";
-                            tooltip_label = "Zoom In".into();
+                            tooltip_label = "Zoom In";
                         } else {
                             icon_path = "icons/maximize_8.svg";
-                            tooltip_label = "Zoom In".into();
+                            tooltip_label = "Zoom In";
                         }
 
                         Pane::render_tab_bar_button(
@@ -1503,7 +1503,7 @@ impl Pane {
                         bounds: square,
                         background: Some(color),
                         border: Default::default(),
-                        corner_radius: diameter / 2.,
+                        corner_radii: (diameter / 2.).into(),
                     });
                 }
             })
@@ -1583,7 +1583,7 @@ impl Pane {
         index: usize,
         icon: &'static str,
         is_active: bool,
-        tooltip: Option<(String, Option<Box<dyn Action>>)>,
+        tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
         cx: &mut ViewContext<Pane>,
         on_click: F1,
         on_down: F2,

crates/workspace/src/workspace.rs 🔗

@@ -3614,7 +3614,7 @@ fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppCo
                                 bounds,
                                 background: Some(code_span_background_color),
                                 border: Default::default(),
-                                corner_radius: 2.0,
+                                corner_radii: (2.0).into(),
                             })
                         })
                         .into_any()
@@ -4067,10 +4067,10 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
 
         // If the user cancels any save prompt, then keep the app open.
         for window in workspace_windows {
-            if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
+            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
                 workspace.prepare_to_close(true, cx)
             }) {
-                if !close.await? {
+                if !should_close.await? {
                     return Ok(());
                 }
             }

crates/zed/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.99.0"
+version = "0.100.0"
 publish = false
 
 [lib]

crates/zed/src/main.rs 🔗

@@ -654,6 +654,10 @@ fn load_embedded_fonts(app: &App) {
     let embedded_fonts = Mutex::new(Vec::new());
     smol::block_on(app.background().scoped(|scope| {
         for font_path in &font_paths {
+            if !font_path.ends_with(".ttf") {
+                continue;
+            }
+
             scope.spawn(async {
                 let font_path = &*font_path;
                 let font_bytes = Assets.load(font_path).unwrap().to_vec();

crates/zed/src/zed.rs 🔗

@@ -433,10 +433,10 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
 
         // If the user cancels any save prompt, then keep the app open.
         for window in workspace_windows {
-            if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
-                workspace.prepare_to_close(false, cx)
+            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
+                workspace.prepare_to_close(true, cx)
             }) {
-                if close.await? {
+                if !should_close.await? {
                     return Ok(());
                 }
             }
@@ -2327,6 +2327,11 @@ mod tests {
                     .unwrap()
                     .to_vec()
                     .into(),
+                Assets
+                    .load("fonts/plex/IBMPlexSans-Regular.ttf")
+                    .unwrap()
+                    .to_vec()
+                    .into(),
             ])
             .unwrap();
         let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());

styles/src/common.ts 🔗

@@ -3,6 +3,7 @@ export * from "./theme"
 export { chroma }
 
 export const font_families = {
+    ui_sans: "IBM Plex Sans",
     sans: "Zed Sans",
     mono: "Zed Mono",
 }