Add missing mouse button events and mouse history navigation

Keith Simmons , Max Brunsfeld , and Nathan Sobo created

Co-Authored-By: Max Brunsfeld
Co-Authored-By: Nathan Sobo

Change summary

crates/gpui/src/elements/event_handler.rs | 38 ++++++++++++
crates/gpui/src/platform/event.rs         | 24 +++++++
crates/gpui/src/platform/mac/event.rs     | 42 +++++++++++++
crates/gpui/src/platform/mac/window.rs    | 16 +++++
crates/workspace/src/pane.rs              | 76 ++++++++++++++++--------
crates/zed/src/zed.rs                     | 20 +++---
6 files changed, 179 insertions(+), 37 deletions(-)

Detailed changes

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

@@ -10,6 +10,8 @@ pub struct EventHandler {
     child: ElementBox,
     capture: Option<Box<dyn FnMut(&Event, RectF, &mut EventContext) -> bool>>,
     mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
+    right_mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
+    other_mouse_down: Option<Box<dyn FnMut(u16, &mut EventContext) -> bool>>,
 }
 
 impl EventHandler {
@@ -18,6 +20,8 @@ impl EventHandler {
             child,
             capture: None,
             mouse_down: None,
+            right_mouse_down: None,
+            other_mouse_down: None,
         }
     }
 
@@ -29,6 +33,22 @@ impl EventHandler {
         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_other_mouse_down<F>(mut self, callback: F) -> Self
+    where
+        F: 'static + FnMut(u16, &mut EventContext) -> bool,
+    {
+        self.other_mouse_down = Some(Box::new(callback));
+        self
+    }
+
     pub fn capture<F>(mut self, callback: F) -> Self
     where
         F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool,
@@ -86,7 +106,23 @@ impl Element for EventHandler {
                         }
                     }
                     false
-                }
+                },
+                Event::RightMouseDown { position, .. } => {
+                    if let Some(callback) = self.right_mouse_down.as_mut() {
+                        if bounds.contains_point(*position) {
+                            return callback(cx);
+                        }
+                    }
+                    false
+                },
+                Event::OtherMouseDown { position, button, .. } => {
+                    if let Some(callback) = self.other_mouse_down.as_mut() {
+                        if bounds.contains_point(*position) {
+                            return callback(*button, cx);
+                        }
+                    }
+                    false
+                },
                 _ => false,
             }
         }

crates/gpui/src/platform/event.rs 🔗

@@ -26,6 +26,30 @@ pub enum Event {
     LeftMouseDragged {
         position: Vector2F,
     },
+    RightMouseDown {
+        position: Vector2F,
+        ctrl: bool,
+        alt: bool,
+        shift: bool,
+        cmd: bool,
+        click_count: usize,
+    },
+    RightMouseUp {
+        position: Vector2F,
+    },
+    OtherMouseDown {
+        position: Vector2F,
+        button: u16,
+        ctrl: bool,
+        alt: bool,
+        shift: bool,
+        cmd: bool,
+        click_count: usize,
+    },
+    OtherMouseUp {
+        position: Vector2F,
+        button: u16,
+    },
     MouseMoved {
         position: Vector2F,
         left_mouse_down: bool,

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

@@ -125,6 +125,48 @@ impl Event {
                     window_height - native_event.locationInWindow().y as f32,
                 ),
             }),
+            NSEventType::NSRightMouseDown => {
+                let modifiers = native_event.modifierFlags();
+                window_height.map(|window_height| Self::RightMouseDown {
+                    position: vec2f(
+                        native_event.locationInWindow().x as f32,
+                        window_height - native_event.locationInWindow().y as f32,
+                    ),
+                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
+                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
+                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
+                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
+                    click_count: native_event.clickCount() as usize,
+                })
+            }
+            NSEventType::NSRightMouseUp => window_height.map(|window_height| Self::RightMouseUp {
+                position: vec2f(
+                    native_event.locationInWindow().x as f32,
+                    window_height - native_event.locationInWindow().y as f32,
+                ),
+            }),
+            NSEventType::NSOtherMouseDown => {
+                let modifiers = native_event.modifierFlags();
+                window_height.map(|window_height| Self::OtherMouseDown {
+                    position: vec2f(
+                        native_event.locationInWindow().x as f32,
+                        window_height - native_event.locationInWindow().y as f32,
+                    ),
+                    button: native_event.buttonNumber() as u16,
+                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
+                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
+                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
+                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
+                    click_count: native_event.clickCount() as usize,
+                })
+            }
+            NSEventType::NSOtherMouseUp => window_height.map(|window_height| Self::OtherMouseUp {
+                position: vec2f(
+                    native_event.locationInWindow().x as f32,
+                    window_height - native_event.locationInWindow().y as f32,
+                ),
+                button: native_event.buttonNumber() as u16,
+            }),
             NSEventType::NSLeftMouseDragged => {
                 window_height.map(|window_height| Self::LeftMouseDragged {
                     position: vec2f(

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

@@ -95,6 +95,22 @@ unsafe fn build_classes() {
             sel!(mouseUp:),
             handle_view_event as extern "C" fn(&Object, Sel, id),
         );
+        decl.add_method(
+            sel!(rightMouseDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(rightMouseUp:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(otherMouseDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(otherMouseUp:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
         decl.add_method(
             sel!(mouseMoved:),
             handle_view_event as extern "C" fn(&Object, Sel, id),

crates/workspace/src/pane.rs 🔗

@@ -8,7 +8,7 @@ use gpui::{
     keymap::Binding,
     platform::CursorStyle,
     AnyViewHandle, Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext,
-    ViewHandle,
+    ViewHandle, WeakViewHandle,
 };
 use postage::watch;
 use project::ProjectPath;
@@ -27,8 +27,8 @@ action!(ActivateNextItem);
 action!(CloseActiveItem);
 action!(CloseInactiveItems);
 action!(CloseItem, usize);
-action!(GoBack);
-action!(GoForward);
+action!(GoBack, Option<WeakViewHandle<Pane>>);
+action!(GoForward, Option<WeakViewHandle<Pane>>);
 
 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
@@ -54,11 +54,19 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(|pane: &mut Pane, action: &Split, cx| {
         pane.split(action.0, cx);
     });
-    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
-        Pane::go_back(workspace, cx).detach();
+    cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| {
+        Pane::go_back(
+            workspace,
+            action.0.as_ref().and_then(|weak_handle| weak_handle.upgrade(cx)),
+            cx
+        ).detach();
     });
-    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
-        Pane::go_forward(workspace, cx).detach();
+    cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| {
+        Pane::go_forward(
+            workspace,
+            action.0.as_ref().and_then(|weak_handle| weak_handle.upgrade(cx)),
+            cx
+        ).detach();
     });
 
     cx.add_bindings(vec![
@@ -70,8 +78,8 @@ pub fn init(cx: &mut MutableAppContext) {
         Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
         Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
         Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
-        Binding::new("ctrl--", GoBack, Some("Pane")),
-        Binding::new("shift-ctrl-_", GoForward, Some("Pane")),
+        Binding::new("ctrl--", GoBack(None), Some("Pane")),
+        Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")),
     ]);
 }
 
@@ -163,19 +171,19 @@ impl Pane {
         cx.emit(Event::Activate);
     }
 
-    pub fn go_back(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Task<()> {
+    pub fn go_back(workspace: &mut Workspace, pane: Option<ViewHandle<Pane>>, cx: &mut ViewContext<Workspace>) -> Task<()> {
         Self::navigate_history(
             workspace,
-            workspace.active_pane().clone(),
+            pane.unwrap_or_else(|| workspace.active_pane().clone()),
             NavigationMode::GoingBack,
             cx,
         )
     }
 
-    pub fn go_forward(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Task<()> {
+    pub fn go_forward(workspace: &mut Workspace, pane: Option<ViewHandle<Pane>>, cx: &mut ViewContext<Workspace>) -> Task<()> {
         Self::navigate_history(
             workspace,
-            workspace.active_pane().clone(),
+            pane.unwrap_or_else(|| workspace.active_pane().clone()),
             NavigationMode::GoingForward,
             cx,
         )
@@ -187,6 +195,8 @@ impl Pane {
         mode: NavigationMode,
         cx: &mut ViewContext<Workspace>,
     ) -> Task<()> {
+        workspace.activate_pane(pane.clone(), cx);
+
         let to_load = pane.update(cx, |pane, cx| {
             // Retrieve the weak item handle from the history.
             let entry = pane.nav_history.borrow_mut().pop(mode)?;
@@ -634,19 +644,33 @@ impl View for Pane {
     }
 
     fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        if let Some(active_item) = self.active_item() {
-            Flex::column()
-                .with_child(self.render_tabs(cx))
-                .with_children(
-                    self.active_toolbar()
-                        .as_ref()
-                        .map(|view| ChildView::new(view).boxed()),
-                )
-                .with_child(ChildView::new(active_item).flexible(1., true).boxed())
-                .named("pane")
-        } else {
-            Empty::new().named("pane")
-        }
+        let this = cx.handle();
+
+        EventHandler::new(
+            if let Some(active_item) = self.active_item() {
+                Flex::column()
+                    .with_child(self.render_tabs(cx))
+                    .with_children(
+                        self.active_toolbar()
+                            .as_ref()
+                            .map(|view| ChildView::new(view).boxed()),
+                    )
+                    .with_child(ChildView::new(active_item).flexible(1., true).boxed())
+                    .boxed()
+            } else {
+                Empty::new().boxed()
+            }
+        )
+        .on_other_mouse_down(move |button, cx| {
+            match button {
+                3 => cx.dispatch_action(GoBack(Some(this.clone()))),
+                4 => cx.dispatch_action(GoForward(Some(this.clone()))),
+                _ => return false,
+            };
+            true
+        })
+        .named("pane")
+
     }
 
     fn on_focus(&mut self, cx: &mut ViewContext<Self>) {

crates/zed/src/zed.rs 🔗

@@ -747,44 +747,44 @@ mod tests {
             (file3.clone(), DisplayPoint::new(15, 0))
         );
 
-        workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0))
         );
 
-        workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file2.clone(), DisplayPoint::new(0, 0))
         );
 
-        workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(10, 0))
         );
 
-        workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(0, 0))
         );
 
         // Go back one more time and ensure we don't navigate past the first item in the history.
-        workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(0, 0))
         );
 
-        workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(10, 0))
         );
 
-        workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file2.clone(), DisplayPoint::new(0, 0))
@@ -798,7 +798,7 @@ mod tests {
                 .update(cx, |pane, cx| pane.close_item(editor3.id(), cx));
             drop(editor3);
         });
-        workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0))
@@ -818,12 +818,12 @@ mod tests {
             })
             .await
             .unwrap();
-        workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(10, 0))
         );
-        workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
+        workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await;
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0))