macos: move mousePos and mousScroll to Ghostty.Surface
parent
4445a9c637
commit
bc134016f7
|
|
@ -269,6 +269,40 @@ extension Ghostty.Input {
|
||||||
self.mods = Mods(cMods: mods)
|
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
|
// 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
|
// MARK: Ghostty.Input.Mods
|
||||||
|
|
||||||
extension Ghostty.Input {
|
extension Ghostty.Input {
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,38 @@ extension Ghostty {
|
||||||
event.mods.cMods)
|
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.
|
/// Perform a keybinding action.
|
||||||
///
|
///
|
||||||
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
/// 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) {
|
override func mouseEntered(with event: NSEvent) {
|
||||||
super.mouseEntered(with: event)
|
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
|
// On mouse enter we need to reset our cursor position. This is
|
||||||
// super important because we set it to -1/-1 on mouseExit and
|
// super important because we set it to -1/-1 on mouseExit and
|
||||||
// lots of mouse logic (i.e. whether to send mouse reports) depend
|
// lots of mouse logic (i.e. whether to send mouse reports) depend
|
||||||
// on the position being in the viewport if it is.
|
// on the position being in the viewport if it is.
|
||||||
let pos = self.convert(event.locationInWindow, from: nil)
|
let pos = self.convert(event.locationInWindow, from: nil)
|
||||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
let mouseEvent = Ghostty.Input.MousePosEvent(
|
||||||
ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y, mods)
|
x: pos.x,
|
||||||
|
y: frame.height - pos.y,
|
||||||
|
mods: .init(nsFlags: event.modifierFlags)
|
||||||
|
)
|
||||||
|
surfaceModel.sendMousePos(mouseEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseExited(with event: NSEvent) {
|
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
|
// 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
|
// 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
|
// Negative values indicate cursor has left the viewport
|
||||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
let mouseEvent = Ghostty.Input.MousePosEvent(
|
||||||
ghostty_surface_mouse_pos(surface, -1, -1, mods)
|
x: -1,
|
||||||
|
y: -1,
|
||||||
|
mods: .init(nsFlags: event.modifierFlags)
|
||||||
|
)
|
||||||
|
surfaceModel.sendMousePos(mouseEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseMoved(with event: NSEvent) {
|
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.
|
// Convert window position to view position. Note (0, 0) is bottom left.
|
||||||
let pos = self.convert(event.locationInWindow, from: nil)
|
let pos = self.convert(event.locationInWindow, from: nil)
|
||||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
let mouseEvent = Ghostty.Input.MousePosEvent(
|
||||||
ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y, mods)
|
x: pos.x,
|
||||||
|
y: frame.height - pos.y,
|
||||||
|
mods: .init(nsFlags: event.modifierFlags)
|
||||||
|
)
|
||||||
|
surfaceModel.sendMousePos(mouseEvent)
|
||||||
|
|
||||||
// Handle focus-follows-mouse
|
// Handle focus-follows-mouse
|
||||||
if let window,
|
if let window,
|
||||||
|
|
@ -866,16 +878,13 @@ extension Ghostty {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func scrollWheel(with event: NSEvent) {
|
override func scrollWheel(with event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
guard let surfaceModel else { return }
|
||||||
|
|
||||||
// Builds up the "input.ScrollMods" bitmask
|
|
||||||
var mods: Int32 = 0
|
|
||||||
|
|
||||||
var x = event.scrollingDeltaX
|
var x = event.scrollingDeltaX
|
||||||
var y = event.scrollingDeltaY
|
var y = event.scrollingDeltaY
|
||||||
if event.hasPreciseScrollingDeltas {
|
let precision = event.hasPreciseScrollingDeltas
|
||||||
mods = 1
|
|
||||||
|
if precision {
|
||||||
// We do a 2x speed multiplier. This is subjective, it "feels" better to me.
|
// We do a 2x speed multiplier. This is subjective, it "feels" better to me.
|
||||||
x *= 2;
|
x *= 2;
|
||||||
y *= 2;
|
y *= 2;
|
||||||
|
|
@ -883,29 +892,12 @@ extension Ghostty {
|
||||||
// TODO(mitchellh): do we have to scale the x/y here by window scale factor?
|
// TODO(mitchellh): do we have to scale the x/y here by window scale factor?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine our momentum value
|
let scrollEvent = Ghostty.Input.MouseScrollEvent(
|
||||||
var momentum: ghostty_input_mouse_momentum_e = GHOSTTY_MOUSE_MOMENTUM_NONE
|
x: x,
|
||||||
switch (event.momentumPhase) {
|
y: y,
|
||||||
case .began:
|
mods: .init(precision: precision, momentum: .init(event.momentumPhase))
|
||||||
momentum = GHOSTTY_MOUSE_MOMENTUM_BEGAN
|
)
|
||||||
case .stationary:
|
surfaceModel.sendMouseScroll(scrollEvent)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func pressureChange(with event: NSEvent) {
|
override func pressureChange(with event: NSEvent) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue