feat: Adding Swipe to Navigate for macOS

This commit is contained in:
Seivan 2025-08-20 11:58:54 +02:00
parent 858ab9cc23
commit 7eafc4573e
No known key found for this signature in database
3 changed files with 61 additions and 30 deletions

View file

@ -534,6 +534,23 @@ impl InputEvent for FileDropEvent {
}
impl MouseEvent for FileDropEvent {}
/// A swipe direction on macOS.
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
#[allow(missing_docs)]
pub enum SwipeDirection {
Left,
Right,
Up,
Down,
}
/// Recognizing touch pad gesture, such as swipe.
/// For now only swipe is supported.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum TouchPadGestureEvent {
/// A swipe gesture was performed.
Swipe(SwipeDirection),
}
/// An enum corresponding to all kinds of platform input events.
#[derive(Clone, Debug)]
pub enum PlatformInput {
@ -555,6 +572,8 @@ pub enum PlatformInput {
ScrollWheel(ScrollWheelEvent),
/// Files were dragged and dropped onto the window.
FileDrop(FileDropEvent),
/// Touch pad gesture was performed.
TouchPad(TouchPadGestureEvent),
}
impl PlatformInput {
@ -569,6 +588,7 @@ impl PlatformInput {
PlatformInput::MouseExited(event) => Some(event),
PlatformInput::ScrollWheel(event) => Some(event),
PlatformInput::FileDrop(event) => Some(event),
PlatformInput::TouchPad(_) => None,
}
}
@ -583,6 +603,13 @@ impl PlatformInput {
PlatformInput::MouseExited(_) => None,
PlatformInput::ScrollWheel(_) => None,
PlatformInput::FileDrop(_) => None,
PlatformInput::TouchPad(_) => None,
}
}
pub(crate) fn touchpad_event(&self) -> Option<&TouchPadGestureEvent> {
match self {
PlatformInput::TouchPad(event) => Some(event),
_ => None,
}
}
}

View file

@ -1,7 +1,7 @@
use crate::{
Capslock, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels,
PlatformInput, ScrollDelta, ScrollWheelEvent, TouchPhase,
MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
Pixels, PlatformInput, ScrollDelta, ScrollWheelEvent, SwipeDirection, TouchPadGestureEvent, TouchPhase,
platform::mac::{
LMGetKbdType, NSStringExt, TISCopyCurrentKeyboardLayoutInputSource,
TISGetInputSourceProperty, UCKeyTranslate, kTISPropertyUnicodeKeyLayoutData,
@ -186,33 +186,14 @@ impl PlatformInput {
})
})
}
// Some mice (like Logitech MX Master) send navigation buttons as swipe events
NSEventType::NSEventTypeSwipe => {
let navigation_direction = match native_event.phase() {
NSEventPhase::NSEventPhaseEnded => match native_event.deltaX() {
x if x > 0.0 => Some(NavigationDirection::Back),
x if x < 0.0 => Some(NavigationDirection::Forward),
_ => return None,
},
_ => return None,
};
match navigation_direction {
Some(direction) => window_height.map(|window_height| {
Self::MouseDown(MouseDownEvent {
button: MouseButton::Navigate(direction),
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
click_count: 1,
first_mouse: false,
})
}),
_ => None,
}
NSEventType::NSEventTypeSwipe => match (native_event.deltaX() as i32, native_event.deltaY() as i32) {
(1, 0) => Some(SwipeDirection::Left),
(-1, 0) => Some(SwipeDirection::Right),
(0, 1) => Some(SwipeDirection::Up),
(0, -1) => Some(SwipeDirection::Down),
_ => None,
}
.map(|direction| Self::TouchPad(TouchPadGestureEvent::Swipe(direction))),
NSEventType::NSScrollWheel => window_height.map(|window_height| {
let phase = match native_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {

View file

@ -12,8 +12,8 @@ use crate::{
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptButton, PromptLevel, Quad,
Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size,
StrikethroughStyle, Style, SubscriberSet, Subscription, TabHandles, TaffyLayoutEngine, Task,
TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle,
StrikethroughStyle, Style, SubscriberSet, Subscription, SwipeDirection, TabHandles, TaffyLayoutEngine, Task, TextStyle,
TextStyleRefinement, TouchPadGestureEvent, TransformationMatrix, Underline, UnderlineStyle,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
transparent_black,
@ -3554,12 +3554,15 @@ impl Window {
}
},
PlatformInput::KeyDown(_) | PlatformInput::KeyUp(_) => event,
PlatformInput::TouchPad(TouchPadGestureEvent::Swipe(_)) => event,
};
if let Some(any_mouse_event) = event.mouse_event() {
self.dispatch_mouse_event(any_mouse_event, cx);
} else if let Some(any_key_event) = event.keyboard_event() {
self.dispatch_key_event(any_key_event, cx);
} else if let Some(TouchPadGestureEvent::Swipe(direction)) = event.touchpad_event() {
self.dispatch_swipe_event(direction, cx);
}
DispatchEventResult {
@ -3621,6 +3624,26 @@ impl Window {
}
}
fn dispatch_swipe_event(&mut self, direction: &SwipeDirection, cx: &mut App) {
// Eventually we want to read from a config what to do, while forward/back is default.
let action_name = match direction {
SwipeDirection::Right => Some("pane::GoForward"),
SwipeDirection::Left => Some("pane::GoBack"),
_ => None,
};
if let Ok(_) = action_name
.ok_or_else(|| crate::ActionBuildError::NotFound {
name: format!("swipe::{:?}", action_name),
})
.and_then(|action_name| cx.build_action(action_name, None))
.map(|action| {
self.dispatch_action(action, cx);
direction
})
{}
}
fn dispatch_key_event(&mut self, event: &dyn Any, cx: &mut App) {
if self.invalidator.is_dirty() {
self.draw(cx).clear();