mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
track mouse state, setup selection state on cursor move with click
This commit is contained in:
116
src/Window.zig
116
src/Window.zig
@ -50,6 +50,9 @@ pty: Pty,
|
||||
/// The command we're running for our tty.
|
||||
command: Command,
|
||||
|
||||
/// Mouse state.
|
||||
mouse: Mouse,
|
||||
|
||||
/// The terminal emulator internal state. This is the abstract "terminal"
|
||||
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
||||
/// just stores internal state about a grid. This is connected back to
|
||||
@ -130,6 +133,18 @@ const Cursor = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Mouse state for the window.
|
||||
const Mouse = struct {
|
||||
/// The current state of mouse click.
|
||||
click_state: ClickState = .none,
|
||||
|
||||
/// The point at which the mouse click happened. This is in screen
|
||||
/// coordinates so that scrolling preserves the location.
|
||||
click_point: terminal.point.ScreenPoint = .{},
|
||||
|
||||
const ClickState = enum { none, left };
|
||||
};
|
||||
|
||||
/// Create a new window. This allocates and returns a pointer because we
|
||||
/// need a stable pointer for user data callbacks. Therefore, a stack-only
|
||||
/// initialization is not currently possible.
|
||||
@ -267,6 +282,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
||||
.grid = grid,
|
||||
.pty = pty,
|
||||
.command = cmd,
|
||||
.mouse = .{},
|
||||
.terminal = term,
|
||||
.terminal_stream = .{ .handler = self },
|
||||
.terminal_cursor = .{
|
||||
@ -290,6 +306,8 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
||||
window.setFocusCallback(focusCallback);
|
||||
window.setRefreshCallback(refreshCallback);
|
||||
window.setScrollCallback(scrollCallback);
|
||||
window.setCursorPosCallback(cursorPosCallback);
|
||||
window.setMouseButtonCallback(mouseButtonCallback);
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -548,6 +566,104 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
}
|
||||
|
||||
fn mouseButtonCallback(
|
||||
window: glfw.Window,
|
||||
button: glfw.MouseButton,
|
||||
action: glfw.Action,
|
||||
mods: glfw.Mods,
|
||||
) void {
|
||||
_ = mods;
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (button == .left) {
|
||||
switch (action) {
|
||||
.press => {
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
const pos = 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);
|
||||
log.debug("click start state={} viewport={} screen={}", .{
|
||||
win.mouse.click_state,
|
||||
point,
|
||||
win.mouse.click_point,
|
||||
});
|
||||
|
||||
// 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 => {
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
win.mouse.click_state = .none;
|
||||
log.debug("click end", .{});
|
||||
},
|
||||
|
||||
.repeat => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cursorPosCallback(
|
||||
window: glfw.Window,
|
||||
xpos: f64,
|
||||
ypos: f64,
|
||||
) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
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;
|
||||
|
||||
// All roads lead to requiring a re-render at this pont.
|
||||
win.render_timer.schedule() catch |err|
|
||||
log.err("error scheduling render timer in cursorPosCallback err={}", .{err});
|
||||
|
||||
// Convert to points
|
||||
const viewport_point = win.posToViewport(xpos, ypos);
|
||||
const screen_point = viewport_point.toScreen(&win.terminal.screen);
|
||||
|
||||
// If the start point is the same as this point, we clear selection.
|
||||
// This means we either haven't moved enough OR we moved the mouse back
|
||||
// to the same place, and we "unhighlighted" everything.
|
||||
if (std.meta.eql(screen_point, win.mouse.click_point)) {
|
||||
win.terminal.selection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: detect if selection point is passed the point where we've
|
||||
// actually written data before and disallow it.
|
||||
|
||||
// We moved! Set the selection
|
||||
win.terminal.selection = .{
|
||||
.start = win.mouse.click_point,
|
||||
.end = screen_point,
|
||||
};
|
||||
}
|
||||
|
||||
fn posToViewport(self: Window, xpos: f64, ypos: f64) terminal.point.Viewport {
|
||||
// Convert the mouse position to the viewport x/y
|
||||
const cell_width = @floatCast(f64, self.grid.cell_size.width);
|
||||
const cell_height = @floatCast(f64, self.grid.cell_size.height);
|
||||
return .{
|
||||
.x = @floatToInt(usize, xpos / cell_width),
|
||||
.y = @floatToInt(usize, ypos / cell_height),
|
||||
};
|
||||
}
|
||||
|
||||
fn cursorTimerCallback(t: *libuv.Timer) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
@ -2,7 +2,7 @@ const stream = @import("stream.zig");
|
||||
const ansi = @import("ansi.zig");
|
||||
const csi = @import("csi.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const point = @import("point.zig");
|
||||
pub const point = @import("point.zig");
|
||||
pub const color = @import("color.zig");
|
||||
|
||||
pub const Terminal = @import("Terminal.zig");
|
||||
@ -20,7 +20,6 @@ pub const EraseDisplay = csi.EraseDisplay;
|
||||
pub const EraseLine = csi.EraseLine;
|
||||
pub const TabClear = csi.TabClear;
|
||||
pub const Attribute = sgr.Attribute;
|
||||
pub const Point = point.Point;
|
||||
|
||||
// Not exported because they're just used for tests.
|
||||
|
||||
|
Reference in New Issue
Block a user