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;
} 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 {
GHOSTTY_MODS_NONE = 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_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_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
}

View File

@ -221,8 +221,10 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
self.error = AppError.surfaceCreateError
return
}
self.surface = surface;
// Setup our tracking area so we get mouse moved events
updateTrackingAreas()
}
required init?(coder: NSCoder) {
@ -230,6 +232,8 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
}
deinit {
trackingAreas.forEach { removeTrackingArea($0) }
guard let surface = self.surface else { return }
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() {
guard let surface = self.surface else { return }
@ -268,7 +293,40 @@ class TerminalSurfaceView_Real: NSView, NSTextInputClient, ObservableObject {
}
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) {

View File

@ -439,4 +439,23 @@ pub const CAPI = struct {
export fn ghostty_surface_char(win: *Window, codepoint: u32) void {
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
self.mouse.click_state[@enumToInt(button)] = action;
self.mouse.click_state[@intCast(usize, @enumToInt(button))] = action;
self.mouse.mods = @bitCast(input.Mods, mods);
self.renderer_state.mutex.lock();

View File

@ -58,6 +58,7 @@ pub const Window = struct {
core_win: *CoreWindow,
content_scale: apprt.ContentScale,
size: apprt.WindowSize,
cursor_pos: apprt.CursorPos,
opts: Options,
pub const Options = extern struct {
@ -82,6 +83,7 @@ pub const Window = struct {
.y = @floatCast(f32, opts.scale_factor),
},
.size = .{ .width = 800, .height = 600 },
.cursor_pos = .{ .x = 0, .y = 0 },
.opts = opts,
};
}
@ -130,6 +132,10 @@ pub const Window = struct {
return false;
}
pub fn getCursorPos(self: *const Window) !apprt.CursorPos {
return self.cursor_pos;
}
pub fn refresh(self: *Window) void {
self.core_win.refreshCallback() catch |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(
self: *const Window,
action: input.Action,
@ -184,4 +221,11 @@ pub const Window = struct {
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.
pub const MouseButtonState = enum(u1) {
release = 0,
press = 1,
///
/// 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 MouseButtonState = enum(c_int) {
release,
press,
};
/// 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
/// 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();
/// The maximum value in this enum. This can be used to create a densely