mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: mouse button and mouse move events
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
19
src/App.zig
19
src/App.zig
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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 };
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user