macos: mouse button and mouse move events

This commit is contained in:
Mitchell Hashimoto
2023-02-19 09:42:56 -08:00
parent e92d90b8d5
commit 1659f52175
6 changed files with 149 additions and 7 deletions

View File

@ -40,6 +40,17 @@ typedef struct {
double scale_factor; double scale_factor;
} ghostty_surface_config_s; } ghostty_surface_config_s;
typedef enum {
GHOSTTY_MOUSE_RELEASE,
GHOSTTY_MOUSE_PRESS,
} ghostty_input_mouse_state_e;
typedef enum {
GHOSTTY_MOUSE_LEFT = 1,
GHOSTTY_MOUSE_RIGHT,
GHOSTTY_MOUSE_MIDDLE,
} ghostty_input_mouse_button_e;
typedef enum { typedef enum {
GHOSTTY_MODS_NONE = 0, GHOSTTY_MODS_NONE = 0,
GHOSTTY_MODS_SHIFT = 1 << 0, GHOSTTY_MODS_SHIFT = 1 << 0,
@ -218,6 +229,8 @@ void ghostty_surface_set_focus(ghostty_surface_t, bool);
void ghostty_surface_set_size(ghostty_surface_t, uint32_t, uint32_t); void ghostty_surface_set_size(ghostty_surface_t, uint32_t, uint32_t);
void ghostty_surface_key(ghostty_surface_t, ghostty_input_action_e, ghostty_input_key_e, ghostty_input_mods_e); void ghostty_surface_key(ghostty_surface_t, ghostty_input_action_e, ghostty_input_key_e, ghostty_input_mods_e);
void ghostty_surface_char(ghostty_surface_t, uint32_t); void ghostty_surface_char(ghostty_surface_t, uint32_t);
void ghostty_surface_mouse_button(ghostty_surface_t, ghostty_input_mouse_state_e, ghostty_input_mouse_button_e, ghostty_input_mods_e);
void ghostty_surface_mouse_pos(ghostty_surface_t, double, double);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -221,8 +221,10 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
self.error = AppError.surfaceCreateError self.error = AppError.surfaceCreateError
return return
} }
self.surface = surface; self.surface = surface;
// Setup our tracking area so we get mouse moved events
updateTrackingAreas()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -230,6 +232,8 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
} }
deinit { deinit {
trackingAreas.forEach { removeTrackingArea($0) }
guard let surface = self.surface else { return } guard let surface = self.surface else { return }
ghostty_surface_free(surface) ghostty_surface_free(surface)
} }
@ -249,6 +253,27 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
} }
} }
override func updateTrackingAreas() {
// To update our tracking area we just recreate it all.
trackingAreas.forEach { removeTrackingArea($0) }
// This tracking area is across the entire frame to notify us of mouse movements.
addTrackingArea(NSTrackingArea(
rect: frame,
options: [
.mouseEnteredAndExited,
.mouseMoved,
.inVisibleRect,
// It is possible this is incorrect when we have splits. This will make
// mouse events only happen while the terminal is focused. Is that what
// we want?
.activeWhenFirstResponder,
],
owner: self,
userInfo: nil))
}
override func viewDidChangeBackingProperties() { override func viewDidChangeBackingProperties() {
guard let surface = self.surface else { return } guard let surface = self.surface else { return }
@ -268,7 +293,40 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
} }
override func mouseDown(with event: NSEvent) { override func mouseDown(with event: NSEvent) {
print("Mouse down: \(event)") guard let surface = self.surface else { return }
let mods = Self.translateFlags(event.modifierFlags)
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_LEFT, mods)
}
override func mouseUp(with event: NSEvent) {
guard let surface = self.surface else { return }
let mods = Self.translateFlags(event.modifierFlags)
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, mods)
}
override func rightMouseDown(with event: NSEvent) {
guard let surface = self.surface else { return }
let mods = Self.translateFlags(event.modifierFlags)
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_RIGHT, mods)
}
override func rightMouseUp(with event: NSEvent) {
guard let surface = self.surface else { return }
let mods = Self.translateFlags(event.modifierFlags)
ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_RIGHT, mods)
}
override func mouseMoved(with event: NSEvent) {
guard let surface = self.surface else { return }
// Convert window position to view position. Note (0, 0) is bottom left.
let pos = self.convert(event.locationInWindow, from: nil)
ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y)
}
override func mouseDragged(with event: NSEvent) {
self.mouseMoved(with: event)
} }
override func keyDown(with event: NSEvent) { override func keyDown(with event: NSEvent) {

View File

@ -439,4 +439,23 @@ pub const CAPI = struct {
export fn ghostty_surface_char(win: *Window, codepoint: u32) void { export fn ghostty_surface_char(win: *Window, codepoint: u32) void {
win.window.charCallback(codepoint); win.window.charCallback(codepoint);
} }
/// Tell the surface that it needs to schedule a render
export fn ghostty_surface_mouse_button(
win: *Window,
action: input.MouseButtonState,
button: input.MouseButton,
mods: c_int,
) void {
win.window.mouseButtonCallback(
action,
button,
@bitCast(input.Mods, @truncate(u8, @bitCast(c_uint, mods))),
);
}
/// Update the mouse position within the view.
export fn ghostty_surface_mouse_pos(win: *Window, x: f64, y: f64) void {
win.window.cursorPosCallback(x, y);
}
}; };

View File

@ -1292,7 +1292,7 @@ pub fn mouseButtonCallback(
} }
// Always record our latest mouse state // Always record our latest mouse state
self.mouse.click_state[@enumToInt(button)] = action; self.mouse.click_state[@intCast(usize, @enumToInt(button))] = action;
self.mouse.mods = @bitCast(input.Mods, mods); self.mouse.mods = @bitCast(input.Mods, mods);
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();

View File

@ -58,6 +58,7 @@ pub const Window = struct {
core_win: *CoreWindow, core_win: *CoreWindow,
content_scale: apprt.ContentScale, content_scale: apprt.ContentScale,
size: apprt.WindowSize, size: apprt.WindowSize,
cursor_pos: apprt.CursorPos,
opts: Options, opts: Options,
pub const Options = extern struct { pub const Options = extern struct {
@ -82,6 +83,7 @@ pub const Window = struct {
.y = @floatCast(f32, opts.scale_factor), .y = @floatCast(f32, opts.scale_factor),
}, },
.size = .{ .width = 800, .height = 600 }, .size = .{ .width = 800, .height = 600 },
.cursor_pos = .{ .x = 0, .y = 0 },
.opts = opts, .opts = opts,
}; };
} }
@ -130,6 +132,10 @@ pub const Window = struct {
return false; return false;
} }
pub fn getCursorPos(self: *const Window) !apprt.CursorPos {
return self.cursor_pos;
}
pub fn refresh(self: *Window) void { pub fn refresh(self: *Window) void {
self.core_win.refreshCallback() catch |err| { self.core_win.refreshCallback() catch |err| {
log.err("error in refresh callback err={}", .{err}); log.err("error in refresh callback err={}", .{err});
@ -157,6 +163,37 @@ pub const Window = struct {
}; };
} }
pub fn mouseButtonCallback(
self: *const Window,
action: input.MouseButtonState,
button: input.MouseButton,
mods: input.Mods,
) void {
self.core_win.mouseButtonCallback(action, button, mods) catch |err| {
log.err("error in mouse button callback err={}", .{err});
return;
};
}
pub fn cursorPosCallback(self: *Window, x: f64, y: f64) void {
// Convert our unscaled x/y to scaled.
self.cursor_pos = self.core_win.window.cursorPosToPixels(.{
.x = @floatCast(f32, x),
.y = @floatCast(f32, y),
}) catch |err| {
log.err(
"error converting cursor pos to scaled pixels in cursor pos callback err={}",
.{err},
);
return;
};
self.core_win.cursorPosCallback(self.cursor_pos) catch |err| {
log.err("error in cursor pos callback err={}", .{err});
return;
};
}
pub fn keyCallback( pub fn keyCallback(
self: *const Window, self: *const Window,
action: input.Action, action: input.Action,
@ -184,4 +221,11 @@ pub const Window = struct {
return; return;
}; };
} }
/// The cursor position from the host directly is in screen coordinates but
/// all our interface works in pixels.
fn cursorPosToPixels(self: *const Window, pos: apprt.CursorPos) !apprt.CursorPos {
const scale = try self.getContentScale();
return .{ .x = pos.x * scale.x, .y = pos.y * scale.y };
}
}; };

View File

@ -1,7 +1,11 @@
/// The state of a mouse button. /// The state of a mouse button.
pub const MouseButtonState = enum(u1) { ///
release = 0, /// This is backed by a c_int so we can use this as-is for our embedding API.
press = 1, ///
/// IMPORTANT: Any changes here update include/ghostty.h
pub const MouseButtonState = enum(c_int) {
release,
press,
}; };
/// Possible mouse buttons. We only track up to 11 because thats the maximum /// Possible mouse buttons. We only track up to 11 because thats the maximum
@ -10,7 +14,11 @@ pub const MouseButtonState = enum(u1) {
/// ///
/// Its a bit silly to name numbers like this but given its a restricted /// Its a bit silly to name numbers like this but given its a restricted
/// set, it feels better than passing around raw numeric literals. /// set, it feels better than passing around raw numeric literals.
pub const MouseButton = enum(u4) { ///
/// This is backed by a c_int so we can use this as-is for our embedding API.
///
/// IMPORTANT: Any changes here update include/ghostty.h
pub const MouseButton = enum(c_int) {
const Self = @This(); const Self = @This();
/// The maximum value in this enum. This can be used to create a densely /// The maximum value in this enum. This can be used to create a densely