macos: move mousePos and mousScroll to Ghostty.Surface
parent
4445a9c637
commit
bc134016f7
|
|
@ -269,6 +269,40 @@ extension Ghostty.Input {
|
|||
self.mods = Mods(cMods: mods)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a mouse position/movement event with coordinates and modifier keys.
|
||||
struct MousePosEvent {
|
||||
let x: Double
|
||||
let y: Double
|
||||
let mods: Mods
|
||||
|
||||
init(
|
||||
x: Double,
|
||||
y: Double,
|
||||
mods: Mods = []
|
||||
) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.mods = mods
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a mouse scroll event with scroll deltas and modifier keys.
|
||||
struct MouseScrollEvent {
|
||||
let x: Double
|
||||
let y: Double
|
||||
let mods: ScrollMods
|
||||
|
||||
init(
|
||||
x: Double,
|
||||
y: Double,
|
||||
mods: ScrollMods = .init(rawValue: 0)
|
||||
) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.mods = mods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.MouseState
|
||||
|
|
@ -335,6 +369,92 @@ extension Ghostty.Input.MouseButton: AppEnum {
|
|||
]
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.ScrollMods
|
||||
|
||||
extension Ghostty.Input {
|
||||
/// `ghostty_input_scroll_mods_t` - Scroll event modifiers
|
||||
///
|
||||
/// This is a packed bitmask that contains precision and momentum information
|
||||
/// for scroll events, matching the Zig `ScrollMods` packed struct.
|
||||
struct ScrollMods {
|
||||
let rawValue: Int32
|
||||
|
||||
/// True if this is a high-precision scroll event (e.g., trackpad, Magic Mouse)
|
||||
var precision: Bool {
|
||||
rawValue & 0b0000_0001 != 0
|
||||
}
|
||||
|
||||
/// The momentum phase of the scroll event for inertial scrolling
|
||||
var momentum: Momentum {
|
||||
let momentumBits = (rawValue >> 1) & 0b0000_0111
|
||||
return Momentum(rawValue: UInt8(momentumBits)) ?? .none
|
||||
}
|
||||
|
||||
init(precision: Bool = false, momentum: Momentum = .none) {
|
||||
var value: Int32 = 0
|
||||
if precision {
|
||||
value |= 0b0000_0001
|
||||
}
|
||||
value |= Int32(momentum.rawValue) << 1
|
||||
self.rawValue = value
|
||||
}
|
||||
|
||||
init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
var cScrollMods: ghostty_input_scroll_mods_t {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.Momentum
|
||||
|
||||
extension Ghostty.Input {
|
||||
/// `ghostty_input_mouse_momentum_e` - Momentum phase for scroll events
|
||||
enum Momentum: UInt8, CaseIterable {
|
||||
case none = 0
|
||||
case began = 1
|
||||
case stationary = 2
|
||||
case changed = 3
|
||||
case ended = 4
|
||||
case cancelled = 5
|
||||
case mayBegin = 6
|
||||
|
||||
var cMomentum: ghostty_input_mouse_momentum_e {
|
||||
switch self {
|
||||
case .none: GHOSTTY_MOUSE_MOMENTUM_NONE
|
||||
case .began: GHOSTTY_MOUSE_MOMENTUM_BEGAN
|
||||
case .stationary: GHOSTTY_MOUSE_MOMENTUM_STATIONARY
|
||||
case .changed: GHOSTTY_MOUSE_MOMENTUM_CHANGED
|
||||
case .ended: GHOSTTY_MOUSE_MOMENTUM_ENDED
|
||||
case .cancelled: GHOSTTY_MOUSE_MOMENTUM_CANCELLED
|
||||
case .mayBegin: GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(AppKit)
|
||||
import AppKit
|
||||
|
||||
extension Ghostty.Input.Momentum {
|
||||
/// Create a Momentum from an NSEvent.Phase
|
||||
init(_ phase: NSEvent.Phase) {
|
||||
switch phase {
|
||||
case .began: self = .began
|
||||
case .stationary: self = .stationary
|
||||
case .changed: self = .changed
|
||||
case .ended: self = .ended
|
||||
case .cancelled: self = .cancelled
|
||||
case .mayBegin: self = .mayBegin
|
||||
default: self = .none
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: Ghostty.Input.Mods
|
||||
|
||||
extension Ghostty.Input {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,38 @@ extension Ghostty {
|
|||
event.mods.cMods)
|
||||
}
|
||||
|
||||
/// Send a mouse position event to the terminal.
|
||||
///
|
||||
/// This reports the current mouse position to the terminal, which may be used
|
||||
/// for mouse tracking, hover effects, or other position-dependent features.
|
||||
/// The terminal will only receive these events if mouse reporting is enabled.
|
||||
///
|
||||
/// - Parameter event: The mouse position event to send to the terminal
|
||||
@MainActor
|
||||
func sendMousePos(_ event: Input.MousePosEvent) {
|
||||
ghostty_surface_mouse_pos(
|
||||
surface,
|
||||
event.x,
|
||||
event.y,
|
||||
event.mods.cMods)
|
||||
}
|
||||
|
||||
/// Send a mouse scroll event to the terminal.
|
||||
///
|
||||
/// This sends scroll wheel input to the terminal with delta values for both
|
||||
/// horizontal and vertical scrolling, along with precision and momentum information.
|
||||
/// The terminal processes this according to its scroll handling configuration.
|
||||
///
|
||||
/// - Parameter event: The mouse scroll event to send to the terminal
|
||||
@MainActor
|
||||
func sendMouseScroll(_ event: Input.MouseScrollEvent) {
|
||||
ghostty_surface_mouse_scroll(
|
||||
surface,
|
||||
event.x,
|
||||
event.y,
|
||||
event.mods.cScrollMods)
|
||||
}
|
||||
|
||||
/// Perform a keybinding action.
|
||||
///
|
||||
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
||||
|
|
|
|||
|
|
@ -808,19 +808,23 @@ extension Ghostty {
|
|||
override func mouseEntered(with event: NSEvent) {
|
||||
super.mouseEntered(with: event)
|
||||
|
||||
guard let surface = self.surface else { return }
|
||||
guard let surfaceModel else { return }
|
||||
|
||||
// On mouse enter we need to reset our cursor position. This is
|
||||
// super important because we set it to -1/-1 on mouseExit and
|
||||
// lots of mouse logic (i.e. whether to send mouse reports) depend
|
||||
// on the position being in the viewport if it is.
|
||||
let pos = self.convert(event.locationInWindow, from: nil)
|
||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
||||
ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y, mods)
|
||||
let mouseEvent = Ghostty.Input.MousePosEvent(
|
||||
x: pos.x,
|
||||
y: frame.height - pos.y,
|
||||
mods: .init(nsFlags: event.modifierFlags)
|
||||
)
|
||||
surfaceModel.sendMousePos(mouseEvent)
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
guard let surface = self.surface else { return }
|
||||
guard let surfaceModel else { return }
|
||||
|
||||
// If the mouse is being dragged then we don't have to emit
|
||||
// this because we get mouse drag events even if we've already
|
||||
|
|
@ -830,17 +834,25 @@ extension Ghostty {
|
|||
}
|
||||
|
||||
// Negative values indicate cursor has left the viewport
|
||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
||||
ghostty_surface_mouse_pos(surface, -1, -1, mods)
|
||||
let mouseEvent = Ghostty.Input.MousePosEvent(
|
||||
x: -1,
|
||||
y: -1,
|
||||
mods: .init(nsFlags: event.modifierFlags)
|
||||
)
|
||||
surfaceModel.sendMousePos(mouseEvent)
|
||||
}
|
||||
|
||||
override func mouseMoved(with event: NSEvent) {
|
||||
guard let surface = self.surface else { return }
|
||||
guard let surfaceModel else { return }
|
||||
|
||||
// Convert window position to view position. Note (0, 0) is bottom left.
|
||||
let pos = self.convert(event.locationInWindow, from: nil)
|
||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
||||
ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y, mods)
|
||||
let mouseEvent = Ghostty.Input.MousePosEvent(
|
||||
x: pos.x,
|
||||
y: frame.height - pos.y,
|
||||
mods: .init(nsFlags: event.modifierFlags)
|
||||
)
|
||||
surfaceModel.sendMousePos(mouseEvent)
|
||||
|
||||
// Handle focus-follows-mouse
|
||||
if let window,
|
||||
|
|
@ -866,16 +878,13 @@ extension Ghostty {
|
|||
}
|
||||
|
||||
override func scrollWheel(with event: NSEvent) {
|
||||
guard let surface = self.surface else { return }
|
||||
|
||||
// Builds up the "input.ScrollMods" bitmask
|
||||
var mods: Int32 = 0
|
||||
guard let surfaceModel else { return }
|
||||
|
||||
var x = event.scrollingDeltaX
|
||||
var y = event.scrollingDeltaY
|
||||
if event.hasPreciseScrollingDeltas {
|
||||
mods = 1
|
||||
|
||||
let precision = event.hasPreciseScrollingDeltas
|
||||
|
||||
if precision {
|
||||
// We do a 2x speed multiplier. This is subjective, it "feels" better to me.
|
||||
x *= 2;
|
||||
y *= 2;
|
||||
|
|
@ -883,29 +892,12 @@ extension Ghostty {
|
|||
// TODO(mitchellh): do we have to scale the x/y here by window scale factor?
|
||||
}
|
||||
|
||||
// Determine our momentum value
|
||||
var momentum: ghostty_input_mouse_momentum_e = GHOSTTY_MOUSE_MOMENTUM_NONE
|
||||
switch (event.momentumPhase) {
|
||||
case .began:
|
||||
momentum = GHOSTTY_MOUSE_MOMENTUM_BEGAN
|
||||
case .stationary:
|
||||
momentum = GHOSTTY_MOUSE_MOMENTUM_STATIONARY
|
||||
case .changed:
|
||||
momentum = GHOSTTY_MOUSE_MOMENTUM_CHANGED
|
||||
case .ended:
|
||||
momentum = GHOSTTY_MOUSE_MOMENTUM_ENDED
|
||||
case .cancelled:
|
||||
momentum = GHOSTTY_MOUSE_MOMENTUM_CANCELLED
|
||||
case .mayBegin:
|
||||
momentum = GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// Pack our momentum value into the mods bitmask
|
||||
mods |= Int32(momentum.rawValue) << 1
|
||||
|
||||
ghostty_surface_mouse_scroll(surface, x, y, mods)
|
||||
let scrollEvent = Ghostty.Input.MouseScrollEvent(
|
||||
x: x,
|
||||
y: y,
|
||||
mods: .init(precision: precision, momentum: .init(event.momentumPhase))
|
||||
)
|
||||
surfaceModel.sendMouseScroll(scrollEvent)
|
||||
}
|
||||
|
||||
override func pressureChange(with event: NSEvent) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue