From 6faeb9ba40ad91cb3ab390ca9297b2a814b02303 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 12 Jun 2024 06:39:17 -0400 Subject: [PATCH] core: mouse pressure state and callbacks --- include/ghostty.h | 1 + .../Sources/Ghostty/SurfaceView_AppKit.swift | 7 ++++ src/Surface.zig | 39 +++++++++++++++++++ src/apprt/embedded.zig | 30 ++++++++++++++ src/input/mouse.zig | 16 ++++++++ 5 files changed, 93 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index 6638caaa6..d49d02386 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -535,6 +535,7 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t, double, double, ghostty_input_scroll_mods_t); +void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double); void ghostty_surface_ime_point(ghostty_surface_t, double*, double*); void ghostty_surface_request_close(ghostty_surface_t); void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e); diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 23f0492d2..2a32fe6ce 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -578,6 +578,13 @@ extension Ghostty { } override func pressureChange(with event: NSEvent) { + guard let surface = self.surface else { return } + + // Notify Ghostty first. We do this because this will let Ghostty handle + // state setup that we'll need for later pressure handling (such as + // QuickLook) + ghostty_surface_mouse_pressure(surface, UInt32(event.stage), Double(event.pressure)) + // Pressure stage 2 is force click. We only want to execute this on the // initial transition to stage 2, and not for any repeated events. guard self.prevPressureStage < 2 else { return } diff --git a/src/Surface.zig b/src/Surface.zig index 2227e1f37..625e1ddb0 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -173,6 +173,10 @@ const Mouse = struct { /// The last x/y sent for mouse reports. event_point: ?terminal.point.Coordinate = null, + /// The pressure stage for the mouse. This should always be none if + /// the mouse is not pressed. + pressure_stage: input.MousePressureStage = .none, + /// Pending scroll amounts for high-precision scrolls pending_scroll_x: f64 = 0, pending_scroll_y: f64 = 0, @@ -2492,6 +2496,41 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { return true; } +pub fn mousePressureCallback( + self: *Surface, + stage: input.MousePressureStage, + pressure: f64, +) !void { + // We don't currently use the pressure value for anything. In the + // future, we could report this to applications using new mouse + // events or utilize it for some custom UI. + _ = pressure; + + // If the pressure stage is the same as what we already have do nothing + if (self.mouse.pressure_stage == stage) return; + + // Update our pressure stage. + self.mouse.pressure_stage = stage; + + // If our left mouse button is pressed and we're entering a deep + // click then we want to start a selection. We treat this as a + // word selection since that is typical macOS behavior. + const left_idx = @intFromEnum(input.MouseButton.left); + if (self.mouse.click_state[left_idx] == .press and + stage == .deep) + select: { + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); + + // This should always be set in this state but we don't want + // to handle state inconsistency here. + const pin = self.mouse.left_click_pin orelse break :select; + const sel = self.io.terminal.screen.selectWord(pin.*) orelse break :select; + try self.setSelection(sel); + try self.queueRender(); + } +} + pub fn cursorPosCallback( self: *Surface, pos: apprt.CursorPos, diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 57fc26aea..6fbd8ef14 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -716,6 +716,17 @@ pub const Surface = struct { }; } + pub fn mousePressureCallback( + self: *Surface, + stage: input.MousePressureStage, + pressure: f64, + ) void { + self.core_surface.mousePressureCallback(stage, pressure) catch |err| { + log.err("error in mouse pressure callback err={}", .{err}); + return; + }; + } + pub fn scrollCallback( self: *Surface, xoff: f64, @@ -1648,6 +1659,25 @@ pub const CAPI = struct { ); } + export fn ghostty_surface_mouse_pressure( + surface: *Surface, + stage_raw: u32, + pressure: f64, + ) void { + const stage = std.meta.intToEnum( + input.MousePressureStage, + stage_raw, + ) catch { + log.warn( + "invalid mouse pressure stage value={}", + .{stage_raw}, + ); + return; + }; + + surface.mousePressureCallback(stage, pressure); + } + export fn ghostty_surface_ime_point(surface: *Surface, x: *f64, y: *f64) void { const pos = surface.core_surface.imePoint(); x.* = pos.x; diff --git a/src/input/mouse.zig b/src/input/mouse.zig index 326e87e81..7fb3cfe89 100644 --- a/src/input/mouse.zig +++ b/src/input/mouse.zig @@ -63,6 +63,22 @@ pub const MouseMomentum = enum(u3) { may_begin = 6, }; +/// The pressure stage of a pressure-sensitive input device. +/// +/// This currently only supports the stages that macOS supports. +pub const MousePressureStage = enum(u2) { + /// The input device is unpressed. + none = 0, + + /// The input device is pressed a normal amount. On macOS trackpads, + /// this is after a "click". + normal = 1, + + /// The input device is pressed a deep amount. On macOS trackpads, + /// this is after a "force click". + deep = 2, +}; + /// The bitmask for mods for scroll events. pub const ScrollMods = packed struct(u8) { /// True if this is a high-precision scroll event. For example, Apple