mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Track/cache button state and mods state
This commit is contained in:
144
src/Window.zig
144
src/Window.zig
@ -137,20 +137,26 @@ const Cursor = struct {
|
||||
|
||||
/// Mouse state for the window.
|
||||
const Mouse = struct {
|
||||
/// The current state of mouse click.
|
||||
click_state: ClickState = .none,
|
||||
/// The last tracked mouse button state by button.
|
||||
click_state: [input.MouseButton.max]input.MouseButtonState = .{.release} ** input.MouseButton.max,
|
||||
|
||||
/// The point at which the mouse click happened. This is in screen
|
||||
/// The last mods state when the last mouse button (whatever it was) was
|
||||
/// pressed or release.
|
||||
mods: input.Mods = .{},
|
||||
|
||||
/// The point at which the left mouse click happened. This is in screen
|
||||
/// coordinates so that scrolling preserves the location.
|
||||
click_point: terminal.point.ScreenPoint = .{},
|
||||
left_click_point: terminal.point.ScreenPoint = .{},
|
||||
|
||||
/// The starting xpos/ypos of the click. This is only useful initially.
|
||||
/// As soon as scrolling occurs, these are no longer accurate to calculate
|
||||
/// the screen point.
|
||||
click_xpos: f64 = 0,
|
||||
click_ypos: f64 = 0,
|
||||
/// The starting xpos/ypos of the left click. Note that if scrolling occurs,
|
||||
/// these will point to different "cells", but the xpos/ypos will stay
|
||||
/// stable during scrolling relative to the window.
|
||||
left_click_xpos: f64 = 0,
|
||||
left_click_ypos: f64 = 0,
|
||||
|
||||
const ClickState = enum { none, left };
|
||||
// /// The last
|
||||
// event_cx: usize = 0,
|
||||
// event_cy: usize = 0,
|
||||
};
|
||||
|
||||
/// Create a new window. This allocates and returns a pointer because we
|
||||
@ -761,15 +767,7 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
return;
|
||||
};
|
||||
|
||||
// NOTE: a limitation of glfw (perhaps) is that we can't detect
|
||||
// scroll buttons (mouse four/five) WITH modifier state. So we always
|
||||
// report this as a "press" followed immediately by a release with no
|
||||
// modifiers.
|
||||
win.mouseReport(if (yoff < 0) .four else .five, .press, .{}, pos) catch |err| {
|
||||
log.err("error reporting mouse event: {}", .{err});
|
||||
return;
|
||||
};
|
||||
win.mouseReport(if (yoff < 0) .four else .five, .release, .{}, pos) catch |err| {
|
||||
win.mouseReport(if (yoff < 0) .four else .five, .press, win.mouse.mods, pos) catch |err| {
|
||||
log.err("error reporting mouse event: {}", .{err});
|
||||
return;
|
||||
};
|
||||
@ -792,9 +790,9 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
|
||||
fn mouseReport(
|
||||
self: *Window,
|
||||
button: glfw.MouseButton,
|
||||
action: glfw.Action,
|
||||
mods: glfw.Mods,
|
||||
button: input.MouseButton,
|
||||
action: input.MouseButtonState,
|
||||
mods: input.Mods,
|
||||
unscaled_pos: glfw.Window.CursorPos,
|
||||
) !void {
|
||||
// TODO: posToViewport currently clamps to the window boundary,
|
||||
@ -833,7 +831,7 @@ fn mouseReport(
|
||||
if (self.terminal.modes.mouse_event == .normal) {
|
||||
if (mods.shift) acc += 4;
|
||||
if (mods.super) acc += 8;
|
||||
if (mods.control) acc += 16;
|
||||
if (mods.ctrl) acc += 16;
|
||||
}
|
||||
|
||||
break :code acc;
|
||||
@ -861,8 +859,8 @@ fn mouseReport(
|
||||
|
||||
fn mouseButtonCallback(
|
||||
window: glfw.Window,
|
||||
button: glfw.MouseButton,
|
||||
action: glfw.Action,
|
||||
glfw_button: glfw.MouseButton,
|
||||
glfw_action: glfw.Action,
|
||||
mods: glfw.Mods,
|
||||
) void {
|
||||
_ = mods;
|
||||
@ -872,6 +870,27 @@ fn mouseButtonCallback(
|
||||
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
|
||||
// Convert glfw button to input button
|
||||
const button: input.MouseButton = switch (glfw_button) {
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.middle => .middle,
|
||||
.four => .four,
|
||||
.five => .five,
|
||||
.six => .six,
|
||||
.seven => .seven,
|
||||
.eight => .eight,
|
||||
};
|
||||
const action: input.MouseButtonState = switch (glfw_action) {
|
||||
.press => .press,
|
||||
.release => .release,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// Always record our latest mouse state
|
||||
win.mouse.click_state[@enumToInt(button)] = action;
|
||||
win.mouse.mods = @bitCast(input.Mods, mods);
|
||||
|
||||
// Report mouse events if enabled
|
||||
if (win.terminal.modes.mouse_event != .none) {
|
||||
const pos = window.getCursorPos() catch |err| {
|
||||
@ -879,46 +898,36 @@ fn mouseButtonCallback(
|
||||
return;
|
||||
};
|
||||
|
||||
win.mouseReport(button, action, mods, pos) catch |err| {
|
||||
win.mouseReport(
|
||||
button,
|
||||
action,
|
||||
win.mouse.mods,
|
||||
pos,
|
||||
) catch |err| {
|
||||
log.err("error reporting mouse event: {}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (button == .left) {
|
||||
switch (action) {
|
||||
.press => {
|
||||
const pos = win.cursorPosToPixels(window.getCursorPos() catch |err| {
|
||||
log.err("error reading cursor position: {}", .{err});
|
||||
return;
|
||||
});
|
||||
// For left button clicks we always record some information for
|
||||
// selection/highlighting purposes.
|
||||
if (button == .left and action == .press) {
|
||||
const pos = win.cursorPosToPixels(window.getCursorPos() catch |err| {
|
||||
log.err("error reading cursor position: {}", .{err});
|
||||
return;
|
||||
});
|
||||
|
||||
// Store it
|
||||
const point = win.posToViewport(pos.xpos, pos.ypos);
|
||||
win.mouse.click_state = .left;
|
||||
win.mouse.click_point = point.toScreen(&win.terminal.screen);
|
||||
win.mouse.click_xpos = pos.xpos;
|
||||
win.mouse.click_ypos = pos.ypos;
|
||||
log.debug("click start state={} viewport={} screen={}", .{
|
||||
win.mouse.click_state,
|
||||
point,
|
||||
win.mouse.click_point,
|
||||
});
|
||||
// Store it
|
||||
const point = win.posToViewport(pos.xpos, pos.ypos);
|
||||
win.mouse.left_click_point = point.toScreen(&win.terminal.screen);
|
||||
win.mouse.left_click_xpos = pos.xpos;
|
||||
win.mouse.left_click_ypos = pos.ypos;
|
||||
|
||||
// Selection is always cleared
|
||||
if (win.terminal.selection != null) {
|
||||
win.terminal.selection = null;
|
||||
win.render_timer.schedule() catch |err|
|
||||
log.err("error scheduling render in mouseButtinCallback err={}", .{err});
|
||||
}
|
||||
},
|
||||
|
||||
.release => {
|
||||
win.mouse.click_state = .none;
|
||||
log.debug("click end", .{});
|
||||
},
|
||||
|
||||
.repeat => {},
|
||||
// Selection is always cleared
|
||||
if (win.terminal.selection != null) {
|
||||
win.terminal.selection = null;
|
||||
win.render_timer.schedule() catch |err|
|
||||
log.err("error scheduling render in mouseButtinCallback err={}", .{err});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -934,7 +943,7 @@ fn cursorPosCallback(
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
|
||||
// If the cursor isn't clicked currently, it doesn't matter
|
||||
if (win.mouse.click_state != .left) return;
|
||||
if (win.mouse.click_state[@enumToInt(input.MouseButton.left)] != .press) return;
|
||||
|
||||
// All roads lead to requiring a re-render at this pont.
|
||||
win.render_timer.schedule() catch |err|
|
||||
@ -983,13 +992,13 @@ fn cursorPosCallback(
|
||||
const cell_xboundary = win.grid.cell_size.width * 0.6;
|
||||
|
||||
// first xpos of the clicked cell
|
||||
const cell_xstart = @intToFloat(f32, win.mouse.click_point.x) * win.grid.cell_size.width;
|
||||
const cell_start_xpos = win.mouse.click_xpos - cell_xstart;
|
||||
const cell_xstart = @intToFloat(f32, win.mouse.left_click_point.x) * win.grid.cell_size.width;
|
||||
const cell_start_xpos = win.mouse.left_click_xpos - cell_xstart;
|
||||
|
||||
// If this is the same cell, then we only start the selection if weve
|
||||
// moved past the boundary point the opposite direction from where we
|
||||
// started.
|
||||
if (std.meta.eql(screen_point, win.mouse.click_point)) {
|
||||
if (std.meta.eql(screen_point, win.mouse.left_click_point)) {
|
||||
const cell_xpos = xpos - cell_xstart;
|
||||
const selected: bool = if (cell_start_xpos < cell_xboundary)
|
||||
cell_xpos >= cell_xboundary
|
||||
@ -1011,9 +1020,9 @@ fn cursorPosCallback(
|
||||
// the starting cell if we started after the boundary, else
|
||||
// we start selection of the prior cell.
|
||||
// - Inverse logic for a point after the start.
|
||||
const click_point = win.mouse.click_point;
|
||||
const click_point = win.mouse.left_click_point;
|
||||
const start: terminal.point.ScreenPoint = if (screen_point.before(click_point)) start: {
|
||||
if (win.mouse.click_xpos > cell_xboundary) {
|
||||
if (win.mouse.left_click_xpos > cell_xboundary) {
|
||||
break :start click_point;
|
||||
} else {
|
||||
break :start if (click_point.x > 0) terminal.point.ScreenPoint{
|
||||
@ -1025,7 +1034,7 @@ fn cursorPosCallback(
|
||||
};
|
||||
}
|
||||
} else start: {
|
||||
if (win.mouse.click_xpos < cell_xboundary) {
|
||||
if (win.mouse.left_click_xpos < cell_xboundary) {
|
||||
break :start click_point;
|
||||
} else {
|
||||
break :start if (click_point.x < win.terminal.screen.cols - 1) terminal.point.ScreenPoint{
|
||||
@ -1387,8 +1396,9 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void {
|
||||
if (enabled) .@"132_cols" else .@"80_cols",
|
||||
),
|
||||
|
||||
.mouse_event_x10 => self.terminal.modes.mouse_event = .x10,
|
||||
.mouse_event_normal => self.terminal.modes.mouse_event = .normal,
|
||||
.mouse_event_x10 => self.terminal.modes.mouse_event = if (enabled) .x10 else .none,
|
||||
.mouse_event_normal => self.terminal.modes.mouse_event = if (enabled) .normal else .none,
|
||||
.mouse_event_button => self.terminal.modes.mouse_event = if (enabled) .button else .none,
|
||||
|
||||
else => if (enabled) log.warn("unimplemented mode: {}", .{mode}),
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub usingnamespace @import("input/mouse.zig");
|
||||
pub usingnamespace @import("input/key.zig");
|
||||
pub const Binding = @import("input/Binding.zig");
|
||||
|
||||
|
38
src/input/mouse.zig
Normal file
38
src/input/mouse.zig
Normal file
@ -0,0 +1,38 @@
|
||||
/// The state of a mouse button.
|
||||
pub const MouseButtonState = enum(u1) {
|
||||
release = 0,
|
||||
press = 1,
|
||||
};
|
||||
|
||||
/// Possible mouse buttons. We only track up to 11 because thats the maximum
|
||||
/// button input that terminal mouse tracking handles without becoming
|
||||
/// ambiguous.
|
||||
///
|
||||
/// 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) {
|
||||
const Self = @This();
|
||||
|
||||
/// The maximum value in this enum. This can be used to create a densely
|
||||
/// packed array, for example.
|
||||
pub const max = max: {
|
||||
var cur = 0;
|
||||
for (@typeInfo(Self).Enum.fields) |field| {
|
||||
if (field.value > cur) cur = field.value;
|
||||
}
|
||||
|
||||
break :max cur;
|
||||
};
|
||||
|
||||
left = 1,
|
||||
right = 2,
|
||||
middle = 3,
|
||||
four = 4,
|
||||
five = 5,
|
||||
six = 6,
|
||||
seven = 7,
|
||||
eight = 8,
|
||||
nine = 9,
|
||||
ten = 10,
|
||||
eleven = 11,
|
||||
};
|
@ -85,9 +85,9 @@ pub const MouseEvents = enum(u3) {
|
||||
none = 0,
|
||||
x10 = 1, // 9
|
||||
normal = 2, // 1000
|
||||
button = 3, // 1002
|
||||
|
||||
// TODO:
|
||||
button = 3, // 1002
|
||||
any = 4, // 1003
|
||||
};
|
||||
|
||||
|
@ -72,6 +72,10 @@ pub const Mode = enum(u16) {
|
||||
/// "Normal" mouse events: click/release, scroll
|
||||
mouse_event_normal = 1000,
|
||||
|
||||
/// Same as normal mode but also send events for mouse motion
|
||||
/// while the button is pressed when the cell in the grid changes.
|
||||
mouse_event_button = 1002,
|
||||
|
||||
/// Alternate screen mode with save cursor and clear on enter.
|
||||
alt_screen_save_cursor_clear_enter = 1049,
|
||||
|
||||
|
Reference in New Issue
Block a user