Permanent fix to repeat MouseRegion Tag failure in Workspace

K Simmons and Mikayla Maki created

Polish tab bar buttons

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>

Change summary

crates/activity_indicator/src/activity_indicator.rs |   2 
crates/auto_update/src/update_notification.rs       |   4 
crates/chat_panel/src/chat_panel.rs                 |   2 
crates/contacts_panel/src/contacts_panel.rs         |  72 +-
crates/contacts_panel/src/notifications.rs          |   4 
crates/context_menu/src/context_menu.rs             |  24 
crates/diagnostics/src/items.rs                     |   4 
crates/drag_and_drop/src/drag_and_drop.rs           |   5 
crates/editor/src/editor.rs                         |   8 
crates/editor/src/element.rs                        |   2 
crates/editor/src/hover_popover.rs                  |   4 
crates/editor/src/mouse_context_menu.rs             |   6 
crates/gpui/src/app.rs                              |  13 
crates/gpui/src/elements.rs                         |   7 
crates/gpui/src/elements/event_handler.rs           | 179 --------
crates/gpui/src/elements/mouse_event_handler.rs     |  35 
crates/gpui/src/elements/overlay.rs                 | 153 +++++-
crates/gpui/src/elements/tooltip.rs                 |  53 +-
crates/gpui/src/presenter.rs                        |  10 
crates/gpui/src/scene.rs                            |  32 +
crates/gpui/src/scene/mouse_region.rs               |  42 +
crates/gpui/src/views/select.rs                     |  30 
crates/picker/src/picker.rs                         |   2 
crates/project_panel/src/project_panel.rs           |  10 
crates/search/src/buffer_search.rs                  |   4 
crates/search/src/project_search.rs                 |   6 
crates/terminal/src/terminal_view.rs                |   7 
crates/workspace/src/dock.rs                        |  20 
crates/workspace/src/pane.rs                        | 305 ++++++++------
crates/workspace/src/sidebar.rs                     |   4 
crates/workspace/src/toolbar.rs                     |   2 
crates/workspace/src/workspace.rs                   |  14 
crates/zed/src/feedback.rs                          |   2 
33 files changed, 532 insertions(+), 535 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -278,7 +278,7 @@ impl View for ActivityIndicator {
     fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
         let (icon, message, action) = self.content_to_render(cx);
 
-        let mut element = MouseEventHandler::new::<Self, _, _>(0, cx, |state, cx| {
+        let mut element = MouseEventHandler::<Self>::new(0, cx, |state, cx| {
             let theme = &cx
                 .global::<Settings>()
                 .theme

crates/auto_update/src/update_notification.rs 🔗

@@ -29,7 +29,7 @@ impl View for UpdateNotification {
         let theme = cx.global::<Settings>().theme.clone();
         let theme = &theme.update_notification;
 
-        MouseEventHandler::new::<ViewReleaseNotes, _, _>(0, cx, |state, cx| {
+        MouseEventHandler::<ViewReleaseNotes>::new(0, cx, |state, cx| {
             Flex::column()
                 .with_child(
                     Flex::row()
@@ -47,7 +47,7 @@ impl View for UpdateNotification {
                             .boxed(),
                         )
                         .with_child(
-                            MouseEventHandler::new::<Cancel, _, _>(0, cx, |state, _| {
+                            MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
                                 let style = theme.dismiss_button.style_for(state, false);
                                 Svg::new("icons/x_mark_thin_8.svg")
                                     .with_color(style.color)

crates/chat_panel/src/chat_panel.rs 🔗

@@ -308,7 +308,7 @@ impl ChatPanel {
         enum SignInPromptLabel {}
 
         Align::new(
-            MouseEventHandler::new::<SignInPromptLabel, _, _>(0, cx, |mouse_state, _| {
+            MouseEventHandler::<SignInPromptLabel>::new(0, cx, |mouse_state, _| {
                 Label::new(
                     "Sign in to use chat".to_string(),
                     if mouse_state.hovered {

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -276,7 +276,7 @@ impl ContactsPanel {
             Section::Offline => "Offline",
         };
         let icon_size = theme.section_icon_size;
-        MouseEventHandler::new::<Header, _, _>(section as usize, cx, |_, _| {
+        MouseEventHandler::<Header>::new(section as usize, cx, |_, _| {
             Flex::row()
                 .with_child(
                     Svg::new(if is_collapsed {
@@ -375,7 +375,7 @@ impl ContactsPanel {
         let baseline_offset =
             row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
 
-        MouseEventHandler::new::<JoinProject, _, _>(project_id as usize, cx, |mouse_state, cx| {
+        MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, cx| {
             let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
             let row = theme.project_row.style_for(mouse_state, is_selected);
 
@@ -424,7 +424,7 @@ impl ContactsPanel {
                                 return None;
                             }
 
-                            let button = MouseEventHandler::new::<ToggleProjectOnline, _, _>(
+                            let button = MouseEventHandler::<ToggleProjectOnline>::new(
                                 project_id as usize,
                                 cx,
                                 |state, _| {
@@ -529,7 +529,7 @@ impl ContactsPanel {
         enum ToggleOnline {}
 
         let project_id = project_handle.id();
-        MouseEventHandler::new::<LocalProject, _, _>(project_id, cx, |state, cx| {
+        MouseEventHandler::<LocalProject>::new(project_id, cx, |state, cx| {
             let row = theme.project_row.style_for(state, is_selected);
             let mut worktree_root_names = String::new();
             let project = if let Some(project) = project_handle.upgrade(cx.deref_mut()) {
@@ -548,7 +548,7 @@ impl ContactsPanel {
             Flex::row()
                 .with_child({
                     let button =
-                        MouseEventHandler::new::<ToggleOnline, _, _>(project_id, cx, |state, _| {
+                        MouseEventHandler::<ToggleOnline>::new(project_id, cx, |state, _| {
                             let mut style = *theme.private_button.style_for(state, false);
                             if is_going_online {
                                 style.color = theme.disabled_button.color;
@@ -636,7 +636,7 @@ impl ContactsPanel {
 
         if is_incoming {
             row.add_children([
-                MouseEventHandler::new::<Decline, _, _>(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Decline>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -658,7 +658,7 @@ impl ContactsPanel {
                 .contained()
                 .with_margin_right(button_spacing)
                 .boxed(),
-                MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Accept>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -680,7 +680,7 @@ impl ContactsPanel {
             ]);
         } else {
             row.add_child(
-                MouseEventHandler::new::<Cancel, _, _>(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Cancel>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1071,7 +1071,7 @@ impl View for ContactsPanel {
                                 .boxed(),
                         )
                         .with_child(
-                            MouseEventHandler::new::<AddContact, _, _>(0, cx, |_, _| {
+                            MouseEventHandler::<AddContact>::new(0, cx, |_, _| {
                                 Svg::new("icons/user_plus_16.svg")
                                     .with_color(theme.add_contact_button.color)
                                     .constrained()
@@ -1102,35 +1102,31 @@ impl View for ContactsPanel {
 
                             if info.count > 0 {
                                 Some(
-                                    MouseEventHandler::new::<InviteLink, _, _>(
-                                        0,
-                                        cx,
-                                        |state, cx| {
-                                            let style =
-                                                theme.invite_row.style_for(state, false).clone();
-
-                                            let copied =
-                                                cx.read_from_clipboard().map_or(false, |item| {
-                                                    item.text().as_str() == info.url.as_ref()
-                                                });
-
-                                            Label::new(
-                                                format!(
-                                                    "{} invite link ({} left)",
-                                                    if copied { "Copied" } else { "Copy" },
-                                                    info.count
-                                                ),
-                                                style.label.clone(),
-                                            )
-                                            .aligned()
-                                            .left()
-                                            .constrained()
-                                            .with_height(theme.row_height)
-                                            .contained()
-                                            .with_style(style.container)
-                                            .boxed()
-                                        },
-                                    )
+                                    MouseEventHandler::<InviteLink>::new(0, cx, |state, cx| {
+                                        let style =
+                                            theme.invite_row.style_for(state, false).clone();
+
+                                        let copied =
+                                            cx.read_from_clipboard().map_or(false, |item| {
+                                                item.text().as_str() == info.url.as_ref()
+                                            });
+
+                                        Label::new(
+                                            format!(
+                                                "{} invite link ({} left)",
+                                                if copied { "Copied" } else { "Copy" },
+                                                info.count
+                                            ),
+                                            style.label.clone(),
+                                        )
+                                        .aligned()
+                                        .left()
+                                        .constrained()
+                                        .with_height(theme.row_height)
+                                        .contained()
+                                        .with_style(style.container)
+                                        .boxed()
+                                    })
                                     .with_cursor_style(CursorStyle::PointingHand)
                                     .on_click(MouseButton::Left, move |_, cx| {
                                         cx.write_to_clipboard(ClipboardItem::new(

crates/contacts_panel/src/notifications.rs 🔗

@@ -52,7 +52,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                     .boxed(),
                 )
                 .with_child(
-                    MouseEventHandler::new::<Dismiss, _, _>(user.id as usize, cx, |state, _| {
+                    MouseEventHandler::<Dismiss>::new(user.id as usize, cx, |state, _| {
                         render_icon_button(
                             theme.dismiss_button.style_for(state, false),
                             "icons/x_mark_thin_8.svg",
@@ -90,7 +90,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                 Flex::row()
                     .with_children(buttons.into_iter().enumerate().map(
                         |(ix, (message, action))| {
-                            MouseEventHandler::new::<Button, _, _>(ix, cx, |state, _| {
+                            MouseEventHandler::<Button>::new(ix, cx, |state, _| {
                                 let button = theme.button.style_for(state, false);
                                 Label::new(message.to_string(), button.text.clone())
                                     .contained()

crates/context_menu/src/context_menu.rs 🔗

@@ -22,7 +22,6 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(ContextMenu::cancel);
 }
 
-//
 pub enum ContextMenuItem {
     Item {
         label: String,
@@ -57,7 +56,8 @@ impl ContextMenuItem {
 
 pub struct ContextMenu {
     show_count: usize,
-    position: Vector2F,
+    anchor_position: Vector2F,
+    anchor_corner: AnchorCorner,
     items: Vec<ContextMenuItem>,
     selected_index: Option<usize>,
     visible: bool,
@@ -100,9 +100,10 @@ impl View for ContextMenu {
             .boxed();
 
         Overlay::new(expanded_menu)
-            .hoverable(true)
-            .fit_mode(OverlayFitMode::SnapToWindow)
-            .with_abs_position(self.position)
+            .with_hoverable(true)
+            .with_fit_mode(OverlayFitMode::SnapToWindow)
+            .with_anchor_position(self.anchor_position)
+            .with_anchor_corner(self.anchor_corner)
             .boxed()
     }
 
@@ -115,7 +116,8 @@ impl ContextMenu {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
         Self {
             show_count: 0,
-            position: Default::default(),
+            anchor_position: Default::default(),
+            anchor_corner: AnchorCorner::TopLeft,
             items: Default::default(),
             selected_index: Default::default(),
             visible: Default::default(),
@@ -226,14 +228,16 @@ impl ContextMenu {
 
     pub fn show(
         &mut self,
-        position: Vector2F,
+        anchor_position: Vector2F,
+        anchor_corner: AnchorCorner,
         items: impl IntoIterator<Item = ContextMenuItem>,
         cx: &mut ViewContext<Self>,
     ) {
         let mut items = items.into_iter().peekable();
         if items.peek().is_some() {
             self.items = items.collect();
-            self.position = position;
+            self.anchor_position = anchor_position;
+            self.anchor_corner = anchor_corner;
             self.visible = true;
             self.show_count += 1;
             if !cx.is_self_focused() {
@@ -310,13 +314,13 @@ impl ContextMenu {
         enum Menu {}
         enum MenuItem {}
         let style = cx.global::<Settings>().theme.context_menu.clone();
-        MouseEventHandler::new::<Menu, _, _>(0, cx, |_, cx| {
+        MouseEventHandler::<Menu>::new(0, cx, |_, cx| {
             Flex::column()
                 .with_children(self.items.iter().enumerate().map(|(ix, item)| {
                     match item {
                         ContextMenuItem::Item { label, action } => {
                             let action = action.boxed_clone();
-                            MouseEventHandler::new::<MenuItem, _, _>(ix, cx, |state, _| {
+                            MouseEventHandler::<MenuItem>::new(ix, cx, |state, _| {
                                 let style =
                                     style.item.style_for(state, Some(ix) == self.selected_index);
 

crates/diagnostics/src/items.rs 🔗

@@ -89,7 +89,7 @@ impl View for DiagnosticIndicator {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let in_progress = !self.in_progress_checks.is_empty();
         let mut element = Flex::row().with_child(
-            MouseEventHandler::new::<Summary, _, _>(0, cx, |state, cx| {
+            MouseEventHandler::<Summary>::new(0, cx, |state, cx| {
                 let style = cx
                     .global::<Settings>()
                     .theme
@@ -190,7 +190,7 @@ impl View for DiagnosticIndicator {
         } else if let Some(diagnostic) = &self.current_diagnostic {
             let message_style = style.diagnostic_message.clone();
             element.add_child(
-                MouseEventHandler::new::<Message, _, _>(1, cx, |state, _| {
+                MouseEventHandler::<Message>::new(1, cx, |state, _| {
                     Label::new(
                         diagnostic.message.split('\n').next().unwrap().to_string(),
                         message_style.style_for(state, false).text.clone(),

crates/drag_and_drop/src/drag_and_drop.rs 🔗

@@ -114,8 +114,9 @@ impl<V: View> DragAndDrop<V> {
 
                 let position = position + region_offset;
 
+                enum DraggedElementHandler {}
                 Some(
-                    MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
+                    MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
                         Container::new(render(payload, cx))
                             .with_margin_left(position.x())
                             .with_margin_top(position.y())
@@ -174,7 +175,7 @@ pub trait Draggable {
         Self: Sized;
 }
 
-impl Draggable for MouseEventHandler {
+impl<Tag> Draggable for MouseEventHandler<Tag> {
     fn as_draggable<V: View, P: Any>(
         self,
         payload: P,

crates/editor/src/editor.rs 🔗

@@ -682,7 +682,7 @@ impl CompletionsMenu {
                     let completion = &completions[mat.candidate_id];
                     let item_ix = start_ix + ix;
                     items.push(
-                        MouseEventHandler::new::<CompletionTag, _, _>(
+                        MouseEventHandler::<CompletionTag>::new(
                             mat.candidate_id,
                             cx,
                             |state, _| {
@@ -830,7 +830,7 @@ impl CodeActionsMenu {
                 for (ix, action) in actions[range].iter().enumerate() {
                     let item_ix = start_ix + ix;
                     items.push(
-                        MouseEventHandler::new::<ActionTag, _, _>(item_ix, cx, |state, _| {
+                        MouseEventHandler::<ActionTag>::new(item_ix, cx, |state, _| {
                             let item_style = if item_ix == selected_item {
                                 style.autocomplete.selected_item
                             } else if state.hovered {
@@ -2735,7 +2735,7 @@ impl Editor {
         if self.available_code_actions.is_some() {
             enum Tag {}
             Some(
-                MouseEventHandler::new::<Tag, _, _>(0, cx, |_, _| {
+                MouseEventHandler::<Tag>::new(0, cx, |_, _| {
                     Svg::new("icons/bolt_8.svg")
                         .with_color(style.code_actions.indicator)
                         .boxed()
@@ -7100,7 +7100,7 @@ mod tests {
     fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
         cx.set_global(Settings::test(cx));
         use workspace::Item;
-        let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(false, cx));
+        let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
         let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
 
         cx.add_view(&pane, |cx| {

crates/editor/src/element.rs 🔗

@@ -1138,7 +1138,7 @@ impl EditorElement {
 
                         enum JumpIcon {}
                         cx.render(&editor, |_, cx| {
-                            MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
+                            MouseEventHandler::<JumpIcon>::new(*key, cx, |state, _| {
                                 let style = style.jump_icon.style_for(state, false);
                                 Svg::new("icons/arrow_up_right_8.svg")
                                     .with_color(style.color)

crates/editor/src/hover_popover.rs 🔗

@@ -312,7 +312,7 @@ pub struct InfoPopover {
 
 impl InfoPopover {
     pub fn render(&self, style: &EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
-        MouseEventHandler::new::<InfoPopover, _, _>(0, cx, |_, cx| {
+        MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| {
             let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx);
             flex.extend(self.contents.iter().map(|content| {
                 let project = self.project.read(cx);
@@ -383,7 +383,7 @@ impl DiagnosticPopover {
 
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
-        MouseEventHandler::new::<DiagnosticPopover, _, _>(0, cx, |_, _| {
+        MouseEventHandler::<DiagnosticPopover>::new(0, cx, |_, _| {
             Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style)
                 .with_soft_wrap(true)
                 .contained()

crates/editor/src/mouse_context_menu.rs 🔗

@@ -1,5 +1,8 @@
 use context_menu::ContextMenuItem;
-use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext};
+use gpui::{
+    elements::AnchorCorner, geometry::vector::Vector2F, impl_internal_actions, MutableAppContext,
+    ViewContext,
+};
 
 use crate::{
     DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
@@ -46,6 +49,7 @@ pub fn deploy_context_menu(
     editor.mouse_context_menu.update(cx, |menu, cx| {
         menu.show(
             position,
+            AnchorCorner::TopLeft,
             vec![
                 ContextMenuItem::item("Rename Symbol", Rename),
                 ContextMenuItem::item("Go To Definition", GoToDefinition),

crates/gpui/src/app.rs 🔗

@@ -4076,10 +4076,7 @@ impl<'a, V: View> RenderContext<'a, V> {
     }
 
     pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
-        let region_id = MouseRegionId {
-            view_id: self.view_id,
-            discriminant: (TypeId::of::<Tag>(), region_id),
-        };
+        let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
         MouseState {
             hovered: self.hovered_region_ids.contains(&region_id),
             clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
@@ -6032,12 +6029,12 @@ mod tests {
         }
 
         impl super::View for View {
-            fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+            fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+                enum Handler {}
                 let mouse_down_count = self.mouse_down_count.clone();
-                EventHandler::new(Empty::new().boxed())
-                    .on_mouse_down(move |_| {
+                MouseEventHandler::<Handler>::new(0, cx, |_, _| Empty::new().boxed())
+                    .on_down(MouseButton::Left, move |_, _| {
                         mouse_down_count.fetch_add(1, SeqCst);
-                        true
                     })
                     .boxed()
             }

crates/gpui/src/elements.rs 🔗

@@ -3,7 +3,6 @@ mod canvas;
 mod constrained_box;
 mod container;
 mod empty;
-mod event_handler;
 mod expanded;
 mod flex;
 mod hook;
@@ -21,9 +20,9 @@ mod uniform_list;
 
 use self::expanded::Expanded;
 pub use self::{
-    align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*,
-    hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
-    stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
+    align::*, canvas::*, constrained_box::*, container::*, empty::*, flex::*, hook::*, image::*,
+    keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, stack::*, svg::*,
+    text::*, tooltip::*, uniform_list::*,
 };
 pub use crate::presenter::ChildView;
 use crate::{

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

@@ -1,179 +0,0 @@
-use crate::{
-    geometry::vector::Vector2F, presenter::MeasurementContext, scene::HandlerSet, CursorRegion,
-    DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton,
-    MouseButtonEvent, MouseRegion, NavigationDirection, PaintContext, SizeConstraint,
-};
-use pathfinder_geometry::rect::RectF;
-use serde_json::json;
-use std::{any::TypeId, ops::Range};
-
-pub struct EventHandler {
-    child: ElementBox,
-    capture_all: Option<(TypeId, usize)>,
-    mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
-    right_mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
-    navigate_mouse_down: Option<Box<dyn FnMut(NavigationDirection, &mut EventContext) -> bool>>,
-}
-
-impl EventHandler {
-    pub fn new(child: ElementBox) -> Self {
-        Self {
-            child,
-            capture_all: None,
-            mouse_down: None,
-            right_mouse_down: None,
-            navigate_mouse_down: None,
-        }
-    }
-
-    pub fn on_mouse_down<F>(mut self, callback: F) -> Self
-    where
-        F: 'static + FnMut(&mut EventContext) -> bool,
-    {
-        self.mouse_down = Some(Box::new(callback));
-        self
-    }
-
-    pub fn on_right_mouse_down<F>(mut self, callback: F) -> Self
-    where
-        F: 'static + FnMut(&mut EventContext) -> bool,
-    {
-        self.right_mouse_down = Some(Box::new(callback));
-        self
-    }
-
-    pub fn on_navigate_mouse_down<F>(mut self, callback: F) -> Self
-    where
-        F: 'static + FnMut(NavigationDirection, &mut EventContext) -> bool,
-    {
-        self.navigate_mouse_down = Some(Box::new(callback));
-        self
-    }
-
-    pub fn capture_all<T: 'static>(mut self, id: usize) -> Self {
-        self.capture_all = Some((TypeId::of::<T>(), id));
-        self
-    }
-}
-
-impl Element for EventHandler {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        cx: &mut LayoutContext,
-    ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, cx);
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
-    ) -> Self::PaintState {
-        if let Some(discriminant) = self.capture_all {
-            cx.scene.push_stacking_context(None);
-            cx.scene.push_cursor_region(CursorRegion {
-                bounds: visible_bounds,
-                style: Default::default(),
-            });
-            cx.scene.push_mouse_region(MouseRegion {
-                view_id: cx.current_view_id(),
-                discriminant,
-                bounds: visible_bounds,
-                handlers: HandlerSet::capture_all(),
-                hoverable: true,
-            });
-            cx.scene.pop_stacking_context();
-        }
-        self.child.paint(bounds.origin(), visible_bounds, cx);
-    }
-
-    fn dispatch_event(
-        &mut self,
-        event: &Event,
-        _: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        _: &mut Self::PaintState,
-        cx: &mut EventContext,
-    ) -> bool {
-        if self.capture_all.is_some() {
-            return true;
-        }
-
-        if self.child.dispatch_event(event, cx) {
-            true
-        } else {
-            match event {
-                Event::MouseDown(MouseButtonEvent {
-                    button: MouseButton::Left,
-                    position,
-                    ..
-                }) => {
-                    if let Some(callback) = self.mouse_down.as_mut() {
-                        if visible_bounds.contains_point(*position) {
-                            return callback(cx);
-                        }
-                    }
-                    false
-                }
-                Event::MouseDown(MouseButtonEvent {
-                    button: MouseButton::Right,
-                    position,
-                    ..
-                }) => {
-                    if let Some(callback) = self.right_mouse_down.as_mut() {
-                        if visible_bounds.contains_point(*position) {
-                            return callback(cx);
-                        }
-                    }
-                    false
-                }
-                Event::MouseDown(MouseButtonEvent {
-                    button: MouseButton::Navigate(direction),
-                    position,
-                    ..
-                }) => {
-                    if let Some(callback) = self.navigate_mouse_down.as_mut() {
-                        if visible_bounds.contains_point(*position) {
-                            return callback(*direction, cx);
-                        }
-                    }
-                    false
-                }
-                _ => false,
-            }
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        cx: &MeasurementContext,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        cx: &DebugContext,
-    ) -> serde_json::Value {
-        json!({
-            "type": "EventHandler",
-            "child": self.child.debug(cx),
-        })
-    }
-}

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

@@ -13,31 +13,32 @@ use crate::{
     MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
 };
 use serde_json::json;
-use std::{any::TypeId, ops::Range};
+use std::{marker::PhantomData, ops::Range};
 
-pub struct MouseEventHandler {
+pub struct MouseEventHandler<Tag: 'static> {
     child: ElementBox,
-    discriminant: (TypeId, usize),
+    region_id: usize,
     cursor_style: Option<CursorStyle>,
     handlers: HandlerSet,
     hoverable: bool,
     padding: Padding,
+    _tag: PhantomData<Tag>,
 }
 
-impl MouseEventHandler {
-    pub fn new<Tag, V, F>(id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
+impl<Tag> MouseEventHandler<Tag> {
+    pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
     where
-        Tag: 'static,
         V: View,
         F: FnOnce(MouseState, &mut RenderContext<V>) -> ElementBox,
     {
         Self {
-            child: render_child(cx.mouse_state::<Tag>(id), cx),
+            child: render_child(cx.mouse_state::<Tag>(region_id), cx),
+            region_id,
             cursor_style: None,
-            discriminant: (TypeId::of::<Tag>(), id),
             handlers: Default::default(),
             hoverable: true,
             padding: Default::default(),
+            _tag: PhantomData,
         }
     }
 
@@ -140,7 +141,7 @@ impl MouseEventHandler {
     }
 }
 
-impl Element for MouseEventHandler {
+impl<Tag> Element for MouseEventHandler<Tag> {
     type LayoutState = ();
     type PaintState = ();
 
@@ -167,13 +168,15 @@ impl Element for MouseEventHandler {
             });
         }
 
-        cx.scene.push_mouse_region(MouseRegion {
-            view_id: cx.current_view_id(),
-            discriminant: self.discriminant,
-            bounds: hit_bounds,
-            handlers: self.handlers.clone(),
-            hoverable: self.hoverable,
-        });
+        cx.scene.push_mouse_region(
+            MouseRegion::from_handlers::<Tag>(
+                cx.current_view_id(),
+                self.region_id,
+                hit_bounds,
+                self.handlers.clone(),
+            )
+            .with_hoverable(self.hoverable),
+        );
 
         self.child.paint(bounds.origin(), visible_bounds, cx);
     }

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

@@ -1,49 +1,98 @@
-use std::{any::TypeId, ops::Range};
+use std::ops::Range;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
     presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
+    Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
     PaintContext, SizeConstraint,
 };
 use serde_json::json;
 
 pub struct Overlay {
     child: ElementBox,
-    abs_position: Option<Vector2F>,
+    anchor_position: Option<Vector2F>,
     fit_mode: OverlayFitMode,
+    anchor_corner: AnchorCorner,
     hoverable: bool,
 }
 
 #[derive(Copy, Clone)]
 pub enum OverlayFitMode {
     SnapToWindow,
-    FlipAlignment,
+    SwitchAnchor,
     None,
 }
 
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum AnchorCorner {
+    TopLeft,
+    TopRight,
+    BottomLeft,
+    BottomRight,
+}
+
+impl AnchorCorner {
+    fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
+        match self {
+            Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
+            Self::TopRight => RectF::from_points(
+                anchor_position - Vector2F::new(size.x(), 0.),
+                anchor_position + Vector2F::new(0., size.y()),
+            ),
+            Self::BottomLeft => RectF::from_points(
+                anchor_position - Vector2F::new(0., size.y()),
+                anchor_position + Vector2F::new(size.x(), 0.),
+            ),
+            Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
+        }
+    }
+
+    fn switch_axis(self, axis: Axis) -> Self {
+        match axis {
+            Axis::Vertical => match self {
+                AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
+                AnchorCorner::TopRight => AnchorCorner::BottomRight,
+                AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
+                AnchorCorner::BottomRight => AnchorCorner::TopRight,
+            },
+            Axis::Horizontal => match self {
+                AnchorCorner::TopLeft => AnchorCorner::TopRight,
+                AnchorCorner::TopRight => AnchorCorner::TopLeft,
+                AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
+                AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
+            },
+        }
+    }
+}
+
 impl Overlay {
     pub fn new(child: ElementBox) -> Self {
         Self {
             child,
-            abs_position: None,
+            anchor_position: None,
             fit_mode: OverlayFitMode::None,
+            anchor_corner: AnchorCorner::TopLeft,
             hoverable: false,
         }
     }
 
-    pub fn with_abs_position(mut self, position: Vector2F) -> Self {
-        self.abs_position = Some(position);
+    pub fn with_anchor_position(mut self, position: Vector2F) -> Self {
+        self.anchor_position = Some(position);
+        self
+    }
+
+    pub fn with_anchor_corner(mut self, anchor_corner: AnchorCorner) -> Self {
+        self.anchor_corner = anchor_corner;
         self
     }
 
-    pub fn fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
+    pub fn with_fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
         self.fit_mode = fit_mode;
         self
     }
 
-    pub fn hoverable(mut self, hoverable: bool) -> Self {
+    pub fn with_hoverable(mut self, hoverable: bool) -> Self {
         self.hoverable = hoverable;
         self
     }
@@ -58,7 +107,7 @@ impl Element for Overlay {
         constraint: SizeConstraint,
         cx: &mut LayoutContext,
     ) -> (Vector2F, Self::LayoutState) {
-        let constraint = if self.abs_position.is_some() {
+        let constraint = if self.anchor_position.is_some() {
             SizeConstraint::new(Vector2F::zero(), cx.window_size)
         } else {
             constraint
@@ -74,48 +123,76 @@ impl Element for Overlay {
         size: &mut Self::LayoutState,
         cx: &mut PaintContext,
     ) {
-        let mut bounds = RectF::new(self.abs_position.unwrap_or_else(|| bounds.origin()), *size);
-        cx.scene.push_stacking_context(None);
-
-        if self.hoverable {
-            enum OverlayHoverCapture {}
-            cx.scene.push_mouse_region(MouseRegion {
-                view_id: cx.current_view_id(),
-                bounds,
-                discriminant: (TypeId::of::<OverlayHoverCapture>(), cx.current_view_id()),
-                handlers: Default::default(),
-                hoverable: true,
-            });
-        }
+        let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
+        let mut bounds = self.anchor_corner.get_bounds(anchor_position, *size);
 
         match self.fit_mode {
             OverlayFitMode::SnapToWindow => {
-                // Snap the right edge of the overlay to the right edge of the window if
-                // its horizontal bounds overflow.
-                if bounds.lower_right().x() > cx.window_size.x() {
-                    bounds.set_origin_x((cx.window_size.x() - bounds.width()).max(0.));
+                // Snap the horizontal edges of the overlay to the horizontal edges of the window if
+                // its horizontal bounds overflow
+                if bounds.max_x() > cx.window_size.x() {
+                    let mut lower_right = bounds.lower_right();
+                    lower_right.set_x(cx.window_size.x());
+                    bounds = RectF::from_points(lower_right - *size, lower_right);
+                } else if bounds.min_x() < 0. {
+                    let mut upper_left = bounds.origin();
+                    upper_left.set_x(0.);
+                    bounds = RectF::from_points(upper_left, upper_left + *size);
                 }
 
-                // Snap the bottom edge of the overlay to the bottom edge of the window if
+                // Snap the vertical edges of the overlay to the vertical edges of the window if
                 // its vertical bounds overflow.
-                if bounds.lower_right().y() > cx.window_size.y() {
-                    bounds.set_origin_y((cx.window_size.y() - bounds.height()).max(0.));
+                if bounds.max_y() > cx.window_size.y() {
+                    let mut lower_right = bounds.lower_right();
+                    lower_right.set_y(cx.window_size.y());
+                    bounds = RectF::from_points(lower_right - *size, lower_right);
+                } else if bounds.min_y() < 0. {
+                    let mut upper_left = bounds.origin();
+                    upper_left.set_y(0.);
+                    bounds = RectF::from_points(upper_left, upper_left + *size);
                 }
             }
-            OverlayFitMode::FlipAlignment => {
-                // Right-align overlay if its horizontal bounds overflow.
-                if bounds.lower_right().x() > cx.window_size.x() {
-                    bounds.set_origin_x(bounds.origin_x() - bounds.width());
+            OverlayFitMode::SwitchAnchor => {
+                let mut anchor_corner = self.anchor_corner;
+
+                if bounds.max_x() > cx.window_size.x() {
+                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
+                }
+
+                if bounds.max_y() > cx.window_size.y() {
+                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
+                }
+
+                if bounds.min_x() < 0. {
+                    anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
                 }
 
-                // Bottom-align overlay if its vertical bounds overflow.
-                if bounds.lower_right().y() > cx.window_size.y() {
-                    bounds.set_origin_y(bounds.origin_y() - bounds.height());
+                if bounds.min_y() < 0. {
+                    anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
+                }
+
+                // Update bounds if needed
+                if anchor_corner != self.anchor_corner {
+                    bounds = anchor_corner.get_bounds(anchor_position, *size)
                 }
             }
             OverlayFitMode::None => {}
         }
 
+        cx.scene.push_stacking_context(None);
+
+        if self.hoverable {
+            enum OverlayHoverCapture {}
+            cx.scene.push_mouse_region(
+                MouseRegion::new::<OverlayHoverCapture>(
+                    cx.current_view_id(),
+                    cx.current_view_id(),
+                    bounds,
+                )
+                .with_hoverable(true),
+            );
+        }
+
         self.child.paint(bounds.origin(), bounds, cx);
         cx.scene.pop_stacking_context();
     }
@@ -153,7 +230,7 @@ impl Element for Overlay {
     ) -> serde_json::Value {
         json!({
             "type": "Overlay",
-            "abs_position": self.abs_position.to_json(),
+            "abs_position": self.anchor_position.to_json(),
             "child": self.child.debug(cx),
         })
     }

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

@@ -84,42 +84,41 @@ impl Tooltip {
                         })
                         .boxed(),
                 )
-                .fit_mode(OverlayFitMode::FlipAlignment)
-                .with_abs_position(state.position.get())
+                .with_fit_mode(OverlayFitMode::SwitchAnchor)
+                .with_anchor_position(state.position.get())
                 .boxed(),
             )
         } else {
             None
         };
-        let child =
-            MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| child)
-                .on_hover(move |e, cx| {
-                    let position = e.position;
-                    let window_id = cx.window_id();
-                    if let Some(view_id) = cx.view_id() {
-                        if e.started {
-                            if !state.visible.get() {
-                                state.position.set(position);
+        let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child)
+            .on_hover(move |e, cx| {
+                let position = e.position;
+                let window_id = cx.window_id();
+                if let Some(view_id) = cx.view_id() {
+                    if e.started {
+                        if !state.visible.get() {
+                            state.position.set(position);
 
-                                let mut debounce = state.debounce.borrow_mut();
-                                if debounce.is_none() {
-                                    *debounce = Some(cx.spawn({
-                                        let state = state.clone();
-                                        |mut cx| async move {
-                                            cx.background().timer(DEBOUNCE_TIMEOUT).await;
-                                            state.visible.set(true);
-                                            cx.update(|cx| cx.notify_view(window_id, view_id));
-                                        }
-                                    }));
-                                }
+                            let mut debounce = state.debounce.borrow_mut();
+                            if debounce.is_none() {
+                                *debounce = Some(cx.spawn({
+                                    let state = state.clone();
+                                    |mut cx| async move {
+                                        cx.background().timer(DEBOUNCE_TIMEOUT).await;
+                                        state.visible.set(true);
+                                        cx.update(|cx| cx.notify_view(window_id, view_id));
+                                    }
+                                }));
                             }
-                        } else {
-                            state.visible.set(false);
-                            state.debounce.take();
                         }
+                    } else {
+                        state.visible.set(false);
+                        state.debounce.take();
                     }
-                })
-                .boxed();
+                }
+            })
+            .boxed();
         Self {
             child,
             tooltip,

crates/gpui/src/presenter.rs 🔗

@@ -372,13 +372,13 @@ impl Presenter {
                                 //Ensure that hover entrance events aren't sent twice
                                 if self.hovered_region_ids.insert(region.id()) {
                                     valid_regions.push(region.clone());
-                                    invalidated_views.insert(region.view_id);
+                                    invalidated_views.insert(region.id().view_id());
                                 }
                             } else {
                                 // Ensure that hover exit events aren't sent twice
                                 if self.hovered_region_ids.remove(&region.id()) {
                                     valid_regions.push(region.clone());
-                                    invalidated_views.insert(region.view_id);
+                                    invalidated_views.insert(region.id().view_id());
                                 }
                             }
                         }
@@ -452,8 +452,10 @@ impl Presenter {
 
                     if let Some(callback) = valid_region.handlers.get(&region_event.handler_key()) {
                         event_cx.handled = true;
-                        event_cx.invalidated_views.insert(valid_region.view_id);
-                        event_cx.with_current_view(valid_region.view_id, {
+                        event_cx
+                            .invalidated_views
+                            .insert(valid_region.id().view_id());
+                        event_cx.with_current_view(valid_region.id().view_id(), {
                             let region_event = region_event.clone();
                             |cx| {
                                 callback(region_event, cx);

crates/gpui/src/scene.rs 🔗

@@ -1,6 +1,7 @@
 mod mouse_region;
 mod mouse_region_event;
 
+use collections::HashSet;
 use serde::Deserialize;
 use serde_json::json;
 use std::{borrow::Cow, sync::Arc};
@@ -20,6 +21,8 @@ pub struct Scene {
     scale_factor: f32,
     stacking_contexts: Vec<StackingContext>,
     active_stacking_context_stack: Vec<usize>,
+    #[cfg(debug_assertions)]
+    mouse_region_ids: HashSet<MouseRegionId>,
 }
 
 struct StackingContext {
@@ -177,6 +180,8 @@ impl Scene {
             scale_factor,
             stacking_contexts: vec![stacking_context],
             active_stacking_context_stack: vec![0],
+            #[cfg(debug_assertions)]
+            mouse_region_ids: Default::default(),
         }
     }
 
@@ -241,7 +246,23 @@ impl Scene {
 
     pub fn push_mouse_region(&mut self, region: MouseRegion) {
         if can_draw(region.bounds) {
-            self.active_layer().push_mouse_region(region);
+            // Ensure that Regions cannot be added to a scene with the same region id.
+            let region_id;
+            #[cfg(debug_assertions)]
+            {
+                region_id = region.id();
+            }
+
+            if self.active_layer().push_mouse_region(region) {
+                #[cfg(debug_assertions)]
+                {
+                    if !self.mouse_region_ids.insert(region_id) {
+                        let tag_name = region_id.tag_type_name();
+                        panic!("Same MouseRegionId: {region_id:?} inserted multiple times to the same scene. \
+                            Will cause problems! Look for MouseRegion that uses Tag: {tag_name}");
+                    }
+                }
+            }
         }
     }
 
@@ -364,15 +385,17 @@ impl Layer {
         }
     }
 
-    fn push_mouse_region(&mut self, region: MouseRegion) {
+    fn push_mouse_region(&mut self, region: MouseRegion) -> bool {
         if let Some(bounds) = region
             .bounds
             .intersection(self.clip_bounds.unwrap_or(region.bounds))
         {
             if can_draw(bounds) {
                 self.mouse_regions.push(region);
+                return true;
             }
         }
+        false
     }
 
     fn push_underline(&mut self, underline: Underline) {
@@ -537,10 +560,7 @@ impl ToJson for Border {
 
 impl MouseRegion {
     pub fn id(&self) -> MouseRegionId {
-        MouseRegionId {
-            view_id: self.view_id,
-            discriminant: self.discriminant,
-        }
+        self.id
     }
 }
 

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

@@ -1,4 +1,4 @@
-use std::{any::TypeId, mem::Discriminant, rc::Rc};
+use std::{any::TypeId, fmt::Debug, mem::Discriminant, rc::Rc};
 
 use collections::HashMap;
 
@@ -16,8 +16,7 @@ use super::{
 
 #[derive(Clone)]
 pub struct MouseRegion {
-    pub view_id: usize,
-    pub discriminant: (TypeId, usize),
+    pub id: MouseRegionId,
     pub bounds: RectF,
     pub handlers: HandlerSet,
     pub hoverable: bool,
@@ -43,8 +42,13 @@ impl MouseRegion {
         handlers: HandlerSet,
     ) -> Self {
         Self {
-            view_id,
-            discriminant: (TypeId::of::<Tag>(), region_id),
+            id: MouseRegionId {
+                view_id,
+                tag: TypeId::of::<Tag>(),
+                region_id,
+                #[cfg(debug_assertions)]
+                tag_type_name: std::any::type_name::<Tag>(),
+            },
             bounds,
             handlers,
             hoverable: true,
@@ -137,8 +141,32 @@ impl MouseRegion {
 
 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
 pub struct MouseRegionId {
-    pub view_id: usize,
-    pub discriminant: (TypeId, usize),
+    view_id: usize,
+    tag: TypeId,
+    region_id: usize,
+    #[cfg(debug_assertions)]
+    tag_type_name: &'static str,
+}
+
+impl MouseRegionId {
+    pub(crate) fn new<Tag: 'static>(view_id: usize, region_id: usize) -> Self {
+        MouseRegionId {
+            view_id,
+            region_id,
+            tag: TypeId::of::<Tag>(),
+            #[cfg(debug_assertions)]
+            tag_type_name: std::any::type_name::<Tag>(),
+        }
+    }
+
+    pub fn view_id(&self) -> usize {
+        self.view_id
+    }
+
+    #[cfg(debug_assertions)]
+    pub fn tag_type_name(&self) -> &'static str {
+        self.tag_type_name
+    }
 }
 
 #[derive(Clone, Default)]

crates/gpui/src/views/select.rs 🔗

@@ -109,7 +109,7 @@ impl View for Select {
             Default::default()
         };
         let mut result = Flex::column().with_child(
-            MouseEventHandler::new::<Header, _, _>(self.handle.id(), cx, |mouse_state, cx| {
+            MouseEventHandler::<Header>::new(self.handle.id(), cx, |mouse_state, cx| {
                 Container::new((self.render_item)(
                     self.selected_item_ix,
                     ItemType::Header,
@@ -137,22 +137,18 @@ impl View for Select {
                                     let selected_item_ix = this.selected_item_ix;
                                     range.end = range.end.min(this.item_count);
                                     items.extend(range.map(|ix| {
-                                        MouseEventHandler::new::<Item, _, _>(
-                                            ix,
-                                            cx,
-                                            |mouse_state, cx| {
-                                                (this.render_item)(
-                                                    ix,
-                                                    if ix == selected_item_ix {
-                                                        ItemType::Selected
-                                                    } else {
-                                                        ItemType::Unselected
-                                                    },
-                                                    mouse_state.hovered,
-                                                    cx,
-                                                )
-                                            },
-                                        )
+                                        MouseEventHandler::<Item>::new(ix, cx, |mouse_state, cx| {
+                                            (this.render_item)(
+                                                ix,
+                                                if ix == selected_item_ix {
+                                                    ItemType::Selected
+                                                } else {
+                                                    ItemType::Unselected
+                                                },
+                                                mouse_state.hovered,
+                                                cx,
+                                            )
+                                        })
                                         .on_click(MouseButton::Left, move |_, cx| {
                                             cx.dispatch_action(SelectItem(ix))
                                         })

crates/picker/src/picker.rs 🔗

@@ -85,7 +85,7 @@ impl<D: PickerDelegate> View for Picker<D> {
                             let selected_ix = delegate.read(cx).selected_index();
                             range.end = cmp::min(range.end, delegate.read(cx).match_count());
                             items.extend(range.map(move |ix| {
-                                MouseEventHandler::new::<D, _, _>(ix, cx, |state, cx| {
+                                MouseEventHandler::<D>::new(ix, cx, |state, cx| {
                                     delegate
                                         .read(cx)
                                         .render_match(ix, state, ix == selected_ix, cx)

crates/project_panel/src/project_panel.rs 🔗

@@ -5,8 +5,8 @@ use gpui::{
     actions,
     anyhow::{anyhow, Result},
     elements::{
-        ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement,
-        ScrollTarget, Stack, Svg, UniformList, UniformListState,
+        AnchorCorner, ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler,
+        ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
     },
     geometry::vector::Vector2F,
     impl_internal_actions, keymap,
@@ -302,7 +302,7 @@ impl ProjectPanel {
         }
 
         self.context_menu.update(cx, |menu, cx| {
-            menu.show(action.position, menu_entries, cx);
+            menu.show(action.position, AnchorCorner::TopLeft, menu_entries, cx);
         });
 
         cx.notify();
@@ -1012,7 +1012,7 @@ impl ProjectPanel {
     ) -> ElementBox {
         let kind = details.kind;
         let show_editor = details.is_editing && !details.is_processing;
-        MouseEventHandler::new::<Self, _, _>(entry_id.to_usize(), cx, |state, _| {
+        MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, _| {
             let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
             let mut style = theme.entry.style_for(state, details.is_selected).clone();
             if details.is_ignored {
@@ -1107,7 +1107,7 @@ impl View for ProjectPanel {
         let last_worktree_root_id = self.last_worktree_root_id;
         Stack::new()
             .with_child(
-                MouseEventHandler::new::<Tag, _, _>(0, cx, |_, cx| {
+                MouseEventHandler::<Tag>::new(0, cx, |_, cx| {
                     UniformList::new(
                         self.list.clone(),
                         self.visible_entries

crates/search/src/buffer_search.rs 🔗

@@ -319,7 +319,7 @@ impl BufferSearchBar {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let is_active = self.is_search_option_enabled(option);
         Some(
-            MouseEventHandler::new::<Self, _, _>(option as usize, cx, |state, cx| {
+            MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
                 let style = &cx
                     .global::<Settings>()
                     .theme
@@ -367,7 +367,7 @@ impl BufferSearchBar {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
         enum NavButton {}
-        MouseEventHandler::new::<NavButton, _, _>(direction as usize, cx, |state, cx| {
+        MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
             let style = &cx
                 .global::<Settings>()
                 .theme

crates/search/src/project_search.rs 🔗

@@ -176,7 +176,7 @@ impl View for ProjectSearchView {
             } else {
                 "No results"
             };
-            MouseEventHandler::new::<Status, _, _>(0, cx, |_, _| {
+            MouseEventHandler::<Status>::new(0, cx, |_, _| {
                 Label::new(text.to_string(), theme.search.results_status.clone())
                     .aligned()
                     .contained()
@@ -723,7 +723,7 @@ impl ProjectSearchBar {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
         enum NavButton {}
-        MouseEventHandler::new::<NavButton, _, _>(direction as usize, cx, |state, cx| {
+        MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
             let style = &cx
                 .global::<Settings>()
                 .theme
@@ -758,7 +758,7 @@ impl ProjectSearchBar {
     ) -> ElementBox {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let is_active = self.is_option_enabled(option, cx);
-        MouseEventHandler::new::<Self, _, _>(option as usize, cx, |state, cx| {
+        MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
             let style = &cx
                 .global::<Settings>()
                 .theme

crates/terminal/src/terminal_view.rs 🔗

@@ -4,7 +4,7 @@ use alacritty_terminal::{index::Point, term::TermMode};
 use context_menu::{ContextMenu, ContextMenuItem};
 use gpui::{
     actions,
-    elements::{ChildView, ParentElement, Stack},
+    elements::{AnchorCorner, ChildView, ParentElement, Stack},
     geometry::vector::Vector2F,
     impl_internal_actions,
     keymap::Keystroke,
@@ -139,8 +139,9 @@ impl TerminalView {
             ContextMenuItem::item("Close Terminal", pane::CloseActiveItem),
         ];
 
-        self.context_menu
-            .update(cx, |menu, cx| menu.show(action.position, menu_entries, cx));
+        self.context_menu.update(cx, |menu, cx| {
+            menu.show(action.position, AnchorCorner::TopLeft, menu_entries, cx)
+        });
 
         cx.notify();
     }

crates/workspace/src/dock.rs 🔗

@@ -37,6 +37,12 @@ impl Default for DockPosition {
 }
 
 impl DockPosition {
+    fn anchor(&self) -> DockAnchor {
+        match self {
+            DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor,
+        }
+    }
+
     fn toggle(self) -> Self {
         match self {
             DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
@@ -70,7 +76,8 @@ pub struct Dock {
 
 impl Dock {
     pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
-        let pane = cx.add_view(|cx| Pane::new(true, cx));
+        let anchor = cx.global::<Settings>().default_dock_anchor;
+        let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx));
         let pane_id = pane.id();
         cx.subscribe(&pane, move |workspace, _, event, cx| {
             workspace.handle_pane_event(pane_id, event, cx);
@@ -79,7 +86,7 @@ impl Dock {
 
         Self {
             pane,
-            position: DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor),
+            position: DockPosition::Hidden(anchor),
             default_item_factory,
         }
     }
@@ -98,6 +105,11 @@ impl Dock {
         cx: &mut ViewContext<Workspace>,
     ) {
         workspace.dock.position = new_position;
+        // Tell the pane about the new anchor position
+        workspace.dock.pane.update(cx, |pane, cx| {
+            pane.set_docked(Some(new_position.anchor()), cx)
+        });
+
         let now_visible = workspace.dock.visible_pane().is_some();
         if now_visible {
             // Ensure that the pane has at least one item or construct a default item to put in it
@@ -164,7 +176,7 @@ impl Dock {
                     .boxed()
                 }
                 DockAnchor::Expanded => Container::new(
-                    MouseEventHandler::new::<Dock, _, _>(0, cx, |_state, _cx| {
+                    MouseEventHandler::<Dock>::new(0, cx, |_state, _cx| {
                         Container::new(ChildView::new(self.pane.clone()).boxed())
                             .with_style(style.maximized)
                             .boxed()
@@ -205,7 +217,7 @@ impl View for ToggleDockButton {
             .map(|workspace| workspace.read(cx).dock.position.visible().is_some())
             .unwrap_or(false);
 
-        MouseEventHandler::new::<Self, _, _>(0, cx, |state, cx| {
+        MouseEventHandler::<Self>::new(0, cx, |state, cx| {
             let theme = &cx
                 .global::<Settings>()
                 .theme

crates/workspace/src/pane.rs 🔗

@@ -1,7 +1,8 @@
 use super::{ItemHandle, SplitDirection};
 use crate::{
-    dock::MoveDock, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle,
-    Workspace,
+    dock::{MoveDock, ToggleDock},
+    toolbar::Toolbar,
+    Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
@@ -18,7 +19,7 @@ use gpui::{
     },
     impl_actions, impl_internal_actions,
     platform::{CursorStyle, NavigationDirection},
-    AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
+    Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
     ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
     ViewContext, ViewHandle, WeakViewHandle,
 };
@@ -204,8 +205,8 @@ pub struct Pane {
     autoscroll: bool,
     nav_history: Rc<RefCell<NavHistory>>,
     toolbar: ViewHandle<Toolbar>,
-    context_menu: ViewHandle<ContextMenu>,
-    is_dock: bool,
+    tab_bar_context_menu: ViewHandle<ContextMenu>,
+    docked: Option<DockAnchor>,
 }
 
 pub struct ItemNavHistory {
@@ -255,7 +256,7 @@ pub enum ReorderBehavior {
 }
 
 impl Pane {
-    pub fn new(is_dock: bool, cx: &mut ViewContext<Self>) -> Self {
+    pub fn new(docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) -> Self {
         let handle = cx.weak_handle();
         let context_menu = cx.add_view(ContextMenu::new);
         Self {
@@ -273,8 +274,8 @@ impl Pane {
                 pane: handle.clone(),
             })),
             toolbar: cx.add_view(|_| Toolbar::new(handle)),
-            context_menu,
-            is_dock,
+            tab_bar_context_menu: context_menu,
+            docked,
         }
     }
 
@@ -283,6 +284,11 @@ impl Pane {
         cx.notify();
     }
 
+    pub fn set_docked(&mut self, docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) {
+        self.docked = docked;
+        cx.notify();
+    }
+
     pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
         ItemNavHistory {
             history: self.nav_history.clone(),
@@ -983,9 +989,10 @@ impl Pane {
     }
 
     fn deploy_split_menu(&mut self, action: &DeploySplitMenu, cx: &mut ViewContext<Self>) {
-        self.context_menu.update(cx, |menu, cx| {
+        self.tab_bar_context_menu.update(cx, |menu, cx| {
             menu.show(
                 action.position,
+                AnchorCorner::TopRight,
                 vec![
                     ContextMenuItem::item("Split Right", SplitRight),
                     ContextMenuItem::item("Split Left", SplitLeft),
@@ -998,9 +1005,10 @@ impl Pane {
     }
 
     fn deploy_dock_menu(&mut self, action: &DeployDockMenu, cx: &mut ViewContext<Self>) {
-        self.context_menu.update(cx, |menu, cx| {
+        self.tab_bar_context_menu.update(cx, |menu, cx| {
             menu.show(
                 action.position,
+                AnchorCorner::TopRight,
                 vec![
                     ContextMenuItem::item("Move Dock Right", MoveDock(DockAnchor::Right)),
                     ContextMenuItem::item("Move Dock Bottom", MoveDock(DockAnchor::Bottom)),
@@ -1012,9 +1020,10 @@ impl Pane {
     }
 
     fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext<Self>) {
-        self.context_menu.update(cx, |menu, cx| {
+        self.tab_bar_context_menu.update(cx, |menu, cx| {
             menu.show(
                 action.position,
+                AnchorCorner::TopRight,
                 vec![
                     ContextMenuItem::item("New File", NewFile),
                     ContextMenuItem::item("New Terminal", NewTerminal),
@@ -1047,7 +1056,7 @@ impl Pane {
         enum Tab {}
         enum Filler {}
         let pane = cx.handle();
-        MouseEventHandler::new::<Tabs, _, _>(0, cx, |_, cx| {
+        MouseEventHandler::<Tabs>::new(0, cx, |_, cx| {
             let autoscroll = if mem::take(&mut self.autoscroll) {
                 Some(self.active_item_index)
             } else {
@@ -1068,7 +1077,7 @@ impl Pane {
                 let tab_active = ix == self.active_item_index;
 
                 row.add_child({
-                    MouseEventHandler::new::<Tab, _, _>(ix, cx, {
+                    MouseEventHandler::<Tab>::new(ix, cx, {
                         let item = item.clone();
                         let pane = pane.clone();
                         let detail = detail.clone();
@@ -1143,7 +1152,7 @@ impl Pane {
             // the filler
             let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
             row.add_child(
-                MouseEventHandler::new::<Filler, _, _>(0, cx, |mouse_state, cx| {
+                MouseEventHandler::<Filler>::new(0, cx, |mouse_state, cx| {
                     let mut filler = Empty::new()
                         .contained()
                         .with_style(filler_style.container)
@@ -1265,17 +1274,13 @@ impl Pane {
                         let item_id = item.id();
                         enum TabCloseButton {}
                         let icon = Svg::new("icons/x_mark_thin_8.svg");
-                        MouseEventHandler::new::<TabCloseButton, _, _>(
-                            item_id,
-                            cx,
-                            |mouse_state, _| {
-                                if mouse_state.hovered {
-                                    icon.with_color(tab_style.icon_close_active).boxed()
-                                } else {
-                                    icon.with_color(tab_style.icon_close).boxed()
-                                }
-                            },
-                        )
+                        MouseEventHandler::<TabCloseButton>::new(item_id, cx, |mouse_state, _| {
+                            if mouse_state.hovered {
+                                icon.with_color(tab_style.icon_close_active).boxed()
+                            } else {
+                                icon.with_color(tab_style.icon_close).boxed()
+                            }
+                        })
                         .with_padding(Padding::uniform(4.))
                         .with_cursor_style(CursorStyle::PointingHand)
                         .on_click(MouseButton::Left, {
@@ -1351,132 +1356,132 @@ impl View for Pane {
     }
 
     fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        enum SplitIcon {}
-
         let this = cx.handle();
 
-        let is_dock = self.is_dock;
+        enum MouseNavigationHandler {}
 
         Stack::new()
             .with_child(
-                EventHandler::new(if let Some(active_item) = self.active_item() {
-                    Flex::column()
-                        .with_child({
-                            let mut tab_row = Flex::row()
-                                .with_child(self.render_tab_bar(cx).flex(1., true).named("tabs"));
-
-                            if self.is_active {
-                                tab_row.add_children([
-                                    MouseEventHandler::new::<SplitIcon, _, _>(
-                                        0,
-                                        cx,
-                                        |mouse_state, cx| {
-                                            let theme =
-                                                &cx.global::<Settings>().theme.workspace.tab_bar;
-                                            let style =
-                                                theme.pane_button.style_for(mouse_state, false);
-                                            Svg::new("icons/plus_12.svg")
-                                                .with_color(style.color)
-                                                .constrained()
-                                                .with_width(style.icon_width)
-                                                .aligned()
-                                                .contained()
-                                                .with_style(style.container)
-                                                .constrained()
-                                                .with_width(style.button_width)
-                                                .with_height(style.button_width)
-                                                .aligned()
-                                                .boxed()
-                                        },
+                MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| {
+                    if let Some(active_item) = self.active_item() {
+                        Flex::column()
+                            .with_child({
+                                let mut tab_row = Flex::row().with_child(
+                                    self.render_tab_bar(cx).flex(1., true).named("tabs"),
+                                );
+
+                                if self.is_active {
+                                    tab_row.add_child(
+                                        Flex::row()
+                                            // New menu
+                                            .with_child(tab_bar_button(
+                                                0,
+                                                "icons/plus_12.svg",
+                                                cx,
+                                                |position| DeployNewMenu { position },
+                                            ))
+                                            .with_child(
+                                                self.docked
+                                                    .map(|anchor| {
+                                                        // Add the dock menu button if this pane is a dock
+                                                        let dock_icon = match anchor {
+                                                            DockAnchor::Right => {
+                                                                "icons/dock_right_12.svg"
+                                                            }
+                                                            DockAnchor::Bottom => {
+                                                                "icons/dock_bottom_12.svg"
+                                                            }
+                                                            DockAnchor::Expanded => {
+                                                                "icons/dock_modal_12.svg"
+                                                            }
+                                                        };
+
+                                                        tab_bar_button(
+                                                            2,
+                                                            dock_icon,
+                                                            cx,
+                                                            |position| DeployDockMenu { position },
+                                                        )
+                                                    })
+                                                    .unwrap_or_else(|| {
+                                                        // Add the split menu if this pane is not a dock
+                                                        tab_bar_button(
+                                                            1,
+                                                            "icons/split_12.svg",
+                                                            cx,
+                                                            |position| DeployNewMenu { position },
+                                                        )
+                                                    }),
+                                            )
+                                            // Add the close dock button if this pane is a dock
+                                            .with_children(self.docked.map(|_| {
+                                                tab_bar_button(3, "icons/x_mark_12.svg", cx, |_| {
+                                                    ToggleDock
+                                                })
+                                            }))
+                                            .contained()
+                                            .with_style(
+                                                cx.global::<Settings>()
+                                                    .theme
+                                                    .workspace
+                                                    .tab_bar
+                                                    .pane_button
+                                                    .default
+                                                    .container,
+                                            )
+                                            .boxed(),
                                     )
-                                    .with_cursor_style(CursorStyle::PointingHand)
-                                    .on_down(MouseButton::Left, |e, cx| {
-                                        cx.dispatch_action(DeployNewMenu {
-                                            position: e.region.lower_right(),
-                                        });
-                                    })
-                                    .boxed(),
-                                    MouseEventHandler::new::<SplitIcon, _, _>(
-                                        1,
-                                        cx,
-                                        |mouse_state, cx| {
-                                            let theme =
-                                                &cx.global::<Settings>().theme.workspace.tab_bar;
-                                            let style =
-                                                theme.pane_button.style_for(mouse_state, false);
-                                            Svg::new("icons/split_12.svg")
-                                                .with_color(style.color)
-                                                .constrained()
-                                                .with_width(style.icon_width)
-                                                .aligned()
-                                                .contained()
-                                                .with_style(style.container)
-                                                .constrained()
-                                                .with_width(style.button_width)
-                                                .with_height(style.button_width)
-                                                .aligned()
-                                                .boxed()
-                                        },
+                                }
+
+                                tab_row
+                                    .constrained()
+                                    .with_height(
+                                        cx.global::<Settings>().theme.workspace.tab_bar.height,
                                     )
-                                    .with_cursor_style(CursorStyle::PointingHand)
-                                    .on_down(MouseButton::Left, move |e, cx| {
-                                        if is_dock {
-                                            cx.dispatch_action(DeployDockMenu {
-                                                position: e.region.lower_right(),
-                                            });
-                                        } else {
-                                            cx.dispatch_action(DeploySplitMenu {
-                                                position: e.region.lower_right(),
-                                            });
-                                        }
-                                    })
-                                    .boxed(),
-                                ])
-                            }
+                                    .named("tab bar")
+                            })
+                            .with_child(ChildView::new(&self.toolbar).boxed())
+                            .with_child(ChildView::new(active_item).flex(1., true).boxed())
+                            .boxed()
+                    } else {
+                        enum EmptyPane {}
+                        let theme = cx.global::<Settings>().theme.clone();
 
-                            tab_row
-                                .constrained()
-                                .with_height(cx.global::<Settings>().theme.workspace.tab_bar.height)
-                                .named("tab bar")
+                        MouseEventHandler::<EmptyPane>::new(0, cx, |_, _| {
+                            Empty::new()
+                                .contained()
+                                .with_background_color(theme.workspace.background)
+                                .boxed()
+                        })
+                        .on_down(MouseButton::Left, |_, cx| {
+                            cx.focus_parent_view();
+                        })
+                        .on_up(MouseButton::Left, {
+                            let pane = this.clone();
+                            move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, 0, cx)
                         })
-                        .with_child(ChildView::new(&self.toolbar).boxed())
-                        .with_child(ChildView::new(active_item).flex(1., true).boxed())
                         .boxed()
-                } else {
-                    enum EmptyPane {}
-                    let theme = cx.global::<Settings>().theme.clone();
-
-                    MouseEventHandler::new::<EmptyPane, _, _>(0, cx, |_, _| {
-                        Empty::new()
-                            .contained()
-                            .with_background_color(theme.workspace.background)
-                            .boxed()
-                    })
-                    .on_down(MouseButton::Left, |_, cx| {
-                        cx.focus_parent_view();
-                    })
-                    .on_up(MouseButton::Left, {
-                        let pane = this.clone();
-                        move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, 0, cx)
-                    })
-                    .boxed()
+                    }
                 })
-                .on_navigate_mouse_down(move |direction, cx| {
+                .on_down(MouseButton::Navigate(NavigationDirection::Back), {
                     let this = this.clone();
-                    match direction {
-                        NavigationDirection::Back => {
-                            cx.dispatch_action(GoBack { pane: Some(this) })
-                        }
-                        NavigationDirection::Forward => {
-                            cx.dispatch_action(GoForward { pane: Some(this) })
-                        }
+                    move |_, cx| {
+                        cx.dispatch_action(GoBack {
+                            pane: Some(this.clone()),
+                        });
+                    }
+                })
+                .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
+                    let this = this.clone();
+                    move |_, cx| {
+                        cx.dispatch_action(GoForward {
+                            pane: Some(this.clone()),
+                        })
                     }
-
-                    true
                 })
                 .boxed(),
             )
-            .with_child(ChildView::new(&self.context_menu).boxed())
+            .with_child(ChildView::new(&self.tab_bar_context_menu).boxed())
             .named("pane")
     }
 
@@ -1498,6 +1503,36 @@ impl View for Pane {
     }
 }
 
+fn tab_bar_button<A: Action>(
+    index: usize,
+    icon: &'static str,
+    cx: &mut RenderContext<Pane>,
+    action_builder: impl 'static + Fn(Vector2F) -> A,
+) -> ElementBox {
+    enum TabBarButton {}
+
+    MouseEventHandler::<TabBarButton>::new(index, cx, |mouse_state, cx| {
+        let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
+        let style = theme.pane_button.style_for(mouse_state, false);
+        Svg::new(icon)
+            .with_color(style.color)
+            .constrained()
+            .with_width(style.icon_width)
+            .aligned()
+            .contained()
+            .constrained()
+            .with_width(style.button_width)
+            .with_height(style.button_width)
+            .aligned()
+            .boxed()
+    })
+    .with_cursor_style(CursorStyle::PointingHand)
+    .on_click(MouseButton::Left, move |e, cx| {
+        cx.dispatch_action(action_builder(e.region.lower_right()));
+    })
+    .boxed()
+}
+
 impl ItemNavHistory {
     pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut MutableAppContext) {
         self.history.borrow_mut().push(data, self.item.clone(), cx);

crates/workspace/src/sidebar.rs 🔗

@@ -176,7 +176,7 @@ impl Sidebar {
         let actual_width = self.actual_width.clone();
         let custom_width = self.custom_width.clone();
         let side = self.side;
-        MouseEventHandler::new::<Self, _, _>(side as usize, cx, |_, _| {
+        MouseEventHandler::<Self>::new(side as usize, cx, |_, _| {
             Empty::new()
                 .contained()
                 .with_style(theme.workspace.sidebar_resize_handle)
@@ -291,7 +291,7 @@ impl View for SidebarButtons {
                         side,
                         item_index: ix,
                     };
-                    MouseEventHandler::new::<Self, _, _>(ix, cx, move |state, cx| {
+                    MouseEventHandler::<Self>::new(ix, cx, move |state, cx| {
                         let is_active = is_open && ix == active_ix;
                         let style = item_style.style_for(state, is_active);
                         Stack::new()

crates/workspace/src/toolbar.rs 🔗

@@ -166,7 +166,7 @@ fn nav_button<A: Action + Clone>(
     action_name: &str,
     cx: &mut RenderContext<Toolbar>,
 ) -> ElementBox {
-    MouseEventHandler::new::<A, _, _>(0, cx, |state, _| {
+    MouseEventHandler::<A>::new(0, cx, |state, _| {
         let style = if enabled {
             style.style_for(state, false)
         } else {

crates/workspace/src/workspace.rs 🔗

@@ -955,7 +955,7 @@ impl Workspace {
         })
         .detach();
 
-        let center_pane = cx.add_view(|cx| Pane::new(false, cx));
+        let center_pane = cx.add_view(|cx| Pane::new(None, cx));
         let pane_id = center_pane.id();
         cx.subscribe(&center_pane, move |this, _, event, cx| {
             this.handle_pane_event(pane_id, event, cx)
@@ -1542,7 +1542,7 @@ impl Workspace {
     }
 
     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
-        let pane = cx.add_view(|cx| Pane::new(false, cx));
+        let pane = cx.add_view(|cx| Pane::new(None, cx));
         let pane_id = pane.id();
         cx.subscribe(&pane, move |this, _, event, cx| {
             this.handle_pane_event(pane_id, event, cx)
@@ -1999,8 +1999,9 @@ impl Workspace {
             theme.workspace.titlebar.container
         };
 
+        enum TitleBar {}
         ConstrainedBox::new(
-            MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
+            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
                 Container::new(
                     Stack::new()
                         .with_child(
@@ -2129,7 +2130,7 @@ impl Workspace {
             None
         } else {
             Some(
-                MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
+                MouseEventHandler::<Authenticate>::new(0, cx, |state, _| {
                     let style = theme
                         .workspace
                         .titlebar
@@ -2189,7 +2190,7 @@ impl Workspace {
             .boxed();
 
         if let Some((peer_id, peer_github_login)) = peer {
-            MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
+            MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| content)
                 .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, move |_, cx| {
                     cx.dispatch_action(ToggleFollow(peer_id))
@@ -2215,7 +2216,7 @@ impl Workspace {
         if self.project.read(cx).is_read_only() {
             enum DisconnectedOverlay {}
             Some(
-                MouseEventHandler::new::<DisconnectedOverlay, _, _>(0, cx, |_, cx| {
+                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme;
                     Label::new(
                         "Your connection to the remote project has been lost.".to_string(),
@@ -2226,6 +2227,7 @@ impl Workspace {
                     .with_style(theme.workspace.disconnected_overlay.container)
                     .boxed()
                 })
+                .with_cursor_style(CursorStyle::Arrow)
                 .capture_all()
                 .boxed(),
             )

crates/zed/src/feedback.rs 🔗

@@ -21,7 +21,7 @@ impl View for FeedbackLink {
     }
 
     fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> gpui::ElementBox {
-        MouseEventHandler::new::<Self, _, _>(0, cx, |state, cx| {
+        MouseEventHandler::<Self>::new(0, cx, |state, cx| {
             let theme = &cx.global::<Settings>().theme;
             let theme = &theme.workspace.status_bar.feedback;
             Text::new(