Merge pull request #563 from zed-industries/mouse-history-navigation

Add missing mouse button events and mouse history navigation
This commit is contained in:
Keith Simmons 2022-03-10 17:39:36 -08:00 committed by GitHub
commit b62daebde1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 238 additions and 32 deletions

View file

@ -3,13 +3,15 @@ use serde_json::json;
use crate::{ use crate::{
geometry::vector::Vector2F, DebugContext, Element, ElementBox, Event, EventContext, geometry::vector::Vector2F, DebugContext, Element, ElementBox, Event, EventContext,
LayoutContext, PaintContext, SizeConstraint, LayoutContext, NavigationDirection, PaintContext, SizeConstraint,
}; };
pub struct EventHandler { pub struct EventHandler {
child: ElementBox, child: ElementBox,
capture: Option<Box<dyn FnMut(&Event, RectF, &mut EventContext) -> bool>>, capture: Option<Box<dyn FnMut(&Event, RectF, &mut EventContext) -> bool>>,
mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>, 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 { impl EventHandler {
@ -18,6 +20,8 @@ impl EventHandler {
child, child,
capture: None, capture: None,
mouse_down: None, mouse_down: None,
right_mouse_down: None,
navigate_mouse_down: None,
} }
} }
@ -29,6 +33,22 @@ impl EventHandler {
self 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<F>(mut self, callback: F) -> Self pub fn capture<F>(mut self, callback: F) -> Self
where where
F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool, F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool,
@ -87,6 +107,26 @@ impl Element for EventHandler {
} }
false false
} }
Event::RightMouseDown { position, .. } => {
if let Some(callback) = self.right_mouse_down.as_mut() {
if bounds.contains_point(*position) {
return callback(cx);
}
}
false
}
Event::NavigateMouseDown {
position,
direction,
..
} => {
if let Some(callback) = self.navigate_mouse_down.as_mut() {
if bounds.contains_point(*position) {
return callback(*direction, cx);
}
}
false
}
_ => false, _ => false,
} }
} }

View file

@ -29,7 +29,7 @@ pub mod keymap;
pub mod platform; pub mod platform;
pub use gpui_macros::test; pub use gpui_macros::test;
pub use platform::FontSystem; pub use platform::FontSystem;
pub use platform::{Event, PathPromptOptions, Platform, PromptLevel}; pub use platform::{Event, NavigationDirection, PathPromptOptions, Platform, PromptLevel};
pub use presenter::{ pub use presenter::{
Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
}; };

View file

@ -19,7 +19,7 @@ use crate::{
}; };
use anyhow::Result; use anyhow::Result;
use async_task::Runnable; use async_task::Runnable;
pub use event::Event; pub use event::{Event, NavigationDirection};
use postage::oneshot; use postage::oneshot;
use std::{ use std::{
any::Any, any::Any,

View file

@ -1,5 +1,11 @@
use crate::{geometry::vector::Vector2F, keymap::Keystroke}; use crate::{geometry::vector::Vector2F, keymap::Keystroke};
#[derive(Copy, Clone, Debug)]
pub enum NavigationDirection {
Back,
Forward,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Event { pub enum Event {
KeyDown { KeyDown {
@ -26,6 +32,30 @@ pub enum Event {
LeftMouseDragged { LeftMouseDragged {
position: Vector2F, position: Vector2F,
}, },
RightMouseDown {
position: Vector2F,
ctrl: bool,
alt: bool,
shift: bool,
cmd: bool,
click_count: usize,
},
RightMouseUp {
position: Vector2F,
},
NavigateMouseDown {
position: Vector2F,
direction: NavigationDirection,
ctrl: bool,
alt: bool,
shift: bool,
cmd: bool,
click_count: usize,
},
NavigateMouseUp {
position: Vector2F,
direction: NavigationDirection,
},
MouseMoved { MouseMoved {
position: Vector2F, position: Vector2F,
left_mouse_down: bool, left_mouse_down: bool,

View file

@ -1,4 +1,8 @@
use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event}; use crate::{
geometry::vector::vec2f,
keymap::Keystroke,
platform::{Event, NavigationDirection},
};
use cocoa::{ use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventType}, appkit::{NSEvent, NSEventModifierFlags, NSEventType},
base::{id, nil, YES}, base::{id, nil, YES},
@ -125,6 +129,64 @@ impl Event {
window_height - native_event.locationInWindow().y as f32, 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 direction = match native_event.buttonNumber() {
3 => NavigationDirection::Back,
4 => NavigationDirection::Forward,
// Other mouse buttons aren't tracked currently
_ => return None,
};
let modifiers = native_event.modifierFlags();
window_height.map(|window_height| Self::NavigateMouseDown {
position: vec2f(
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
direction,
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 => {
let direction = match native_event.buttonNumber() {
3 => NavigationDirection::Back,
4 => NavigationDirection::Forward,
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| Self::NavigateMouseUp {
position: vec2f(
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
direction,
})
}
NSEventType::NSLeftMouseDragged => { NSEventType::NSLeftMouseDragged => {
window_height.map(|window_height| Self::LeftMouseDragged { window_height.map(|window_height| Self::LeftMouseDragged {
position: vec2f( position: vec2f(

View file

@ -95,6 +95,22 @@ unsafe fn build_classes() {
sel!(mouseUp:), sel!(mouseUp:),
handle_view_event as extern "C" fn(&Object, Sel, id), 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( decl.add_method(
sel!(mouseMoved:), sel!(mouseMoved:),
handle_view_event as extern "C" fn(&Object, Sel, id), handle_view_event as extern "C" fn(&Object, Sel, id),

View file

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

View file

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