mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #979 from vancluever/vancluever-rect-crosshair
Surface: set crosshair, change event processing logic for mouse tracking
This commit is contained in:
@ -37,6 +37,7 @@ const input = @import("input.zig");
|
|||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const internal_os = @import("os/main.zig");
|
const internal_os = @import("os/main.zig");
|
||||||
const inspector = @import("inspector/main.zig");
|
const inspector = @import("inspector/main.zig");
|
||||||
|
const SurfaceMouse = @import("surface_mouse.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.surface);
|
const log = std.log.scoped(.surface);
|
||||||
|
|
||||||
@ -1285,23 +1286,16 @@ pub fn keyCallback(
|
|||||||
if (rehide) self.hideMouse();
|
if (rehide) self.hideMouse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we are in the middle of a mouse event and we press shift,
|
// Process the cursor state logic. This will update the cursor shape if
|
||||||
// we change the mouse to a text shape so that selection appears
|
// needed, depending on the key state.
|
||||||
// possible.
|
if ((SurfaceMouse{
|
||||||
if (self.io.terminal.flags.mouse_event != .none and
|
.physical_key = event.physical_key,
|
||||||
event.physical_key == .left_shift or
|
.mouse_event = self.io.terminal.flags.mouse_event,
|
||||||
event.physical_key == .right_shift)
|
.mouse_shape = self.io.terminal.mouse_shape,
|
||||||
{
|
.mods = self.mouse.mods,
|
||||||
switch (event.action) {
|
.over_link = self.mouse.over_link,
|
||||||
.press => if (!self.mouse.over_link) {
|
}).keyToMouseShape()) |shape|
|
||||||
// If the cursor is over a link then the pointer shape takes
|
try self.rt_surface.setMouseShape(shape);
|
||||||
// priority
|
|
||||||
try self.rt_surface.setMouseShape(.text);
|
|
||||||
},
|
|
||||||
.release => try self.rt_surface.setMouseShape(self.io.terminal.mouse_shape),
|
|
||||||
.repeat => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No binding, so we have to perform an encoding task. This
|
// No binding, so we have to perform an encoding task. This
|
||||||
// may still result in no encoding. Under different modes and
|
// may still result in no encoding. Under different modes and
|
||||||
@ -2260,17 +2254,6 @@ pub fn cursorPosCallback(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks to see if super is on in mods (MacOS) or ctrl. We use this for
|
|
||||||
// rectangle select along with alt.
|
|
||||||
//
|
|
||||||
// Not to be confused with ctrlOrSuper in Config.
|
|
||||||
fn ctrlOrSuper(mods: input.Mods) bool {
|
|
||||||
if (comptime builtin.target.isDarwin()) {
|
|
||||||
return mods.super;
|
|
||||||
}
|
|
||||||
return mods.ctrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Double-click dragging moves the selection one "word" at a time.
|
/// Double-click dragging moves the selection one "word" at a time.
|
||||||
fn dragLeftClickDouble(
|
fn dragLeftClickDouble(
|
||||||
self: *Surface,
|
self: *Surface,
|
||||||
@ -2385,7 +2368,7 @@ fn dragLeftClickSingle(
|
|||||||
self.setSelection(if (selected) .{
|
self.setSelection(if (selected) .{
|
||||||
.start = screen_point,
|
.start = screen_point,
|
||||||
.end = screen_point,
|
.end = screen_point,
|
||||||
.rectangle = ctrlOrSuper(self.mouse.mods) and self.mouse.mods.alt,
|
.rectangle = self.mouse.mods.ctrlOrSuper() and self.mouse.mods.alt,
|
||||||
} else null);
|
} else null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -2432,7 +2415,7 @@ fn dragLeftClickSingle(
|
|||||||
self.setSelection(.{
|
self.setSelection(.{
|
||||||
.start = start,
|
.start = start,
|
||||||
.end = screen_point,
|
.end = screen_point,
|
||||||
.rectangle = ctrlOrSuper(self.mouse.mods) and self.mouse.mods.alt,
|
.rectangle = self.mouse.mods.ctrlOrSuper() and self.mouse.mods.alt,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2491,7 +2474,7 @@ fn dragLeftClickBefore(
|
|||||||
click_point: terminal.point.ScreenPoint,
|
click_point: terminal.point.ScreenPoint,
|
||||||
mods: input.Mods,
|
mods: input.Mods,
|
||||||
) bool {
|
) bool {
|
||||||
if (ctrlOrSuper(mods) and mods.alt) {
|
if (mods.ctrlOrSuper() and mods.alt) {
|
||||||
return screen_point.x < click_point.x;
|
return screen_point.x < click_point.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,23 +881,23 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
// Fonts
|
// Fonts
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .equal, .mods = ctrlOrSuper(.{}) },
|
.{ .key = .equal, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||||
.{ .increase_font_size = 1 },
|
.{ .increase_font_size = 1 },
|
||||||
);
|
);
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .minus, .mods = ctrlOrSuper(.{}) },
|
.{ .key = .minus, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||||
.{ .decrease_font_size = 1 },
|
.{ .decrease_font_size = 1 },
|
||||||
);
|
);
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .zero, .mods = ctrlOrSuper(.{}) },
|
.{ .key = .zero, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||||
.{ .reset_font_size = {} },
|
.{ .reset_font_size = {} },
|
||||||
);
|
);
|
||||||
|
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .j, .mods = ctrlOrSuper(.{ .shift = true }) },
|
.{ .key = .j, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||||
.{ .write_scrollback_file = {} },
|
.{ .write_scrollback_file = {} },
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1098,14 +1098,14 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
// Toggle fullscreen
|
// Toggle fullscreen
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .enter, .mods = ctrlOrSuper(.{}) },
|
.{ .key = .enter, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||||
.{ .toggle_fullscreen = {} },
|
.{ .toggle_fullscreen = {} },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Toggle zoom a split
|
// Toggle zoom a split
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .enter, .mods = ctrlOrSuper(.{ .shift = true }) },
|
.{ .key = .enter, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||||
.{ .toggle_split_zoom = {} },
|
.{ .toggle_split_zoom = {} },
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1289,21 +1289,6 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This sets either "ctrl" or "super" to true (but not both)
|
|
||||||
/// on mods depending on if the build target is Mac or not. On
|
|
||||||
/// Mac, we default to super (i.e. super+c for copy) and on
|
|
||||||
/// non-Mac we default to ctrl (i.e. ctrl+c for copy).
|
|
||||||
fn ctrlOrSuper(mods: inputpkg.Mods) inputpkg.Mods {
|
|
||||||
var copy = mods;
|
|
||||||
if (comptime builtin.target.isDarwin()) {
|
|
||||||
copy.super = true;
|
|
||||||
} else {
|
|
||||||
copy.ctrl = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load configuration from an iterator that yields values that look like
|
/// Load configuration from an iterator that yields values that look like
|
||||||
/// command-line arguments, i.e. `--key=value`.
|
/// command-line arguments, i.e. `--key=value`.
|
||||||
pub fn loadIter(
|
pub fn loadIter(
|
||||||
|
@ -145,6 +145,14 @@ pub const Mods = packed struct(Mods.Backing) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks to see if super is on (MacOS) or ctrl.
|
||||||
|
pub fn ctrlOrSuper(self: Mods) bool {
|
||||||
|
if (comptime builtin.target.isDarwin()) {
|
||||||
|
return self.super;
|
||||||
|
}
|
||||||
|
return self.ctrl;
|
||||||
|
}
|
||||||
|
|
||||||
// For our own understanding
|
// For our own understanding
|
||||||
test {
|
test {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
@ -607,6 +615,26 @@ pub const Key = enum(c_int) {
|
|||||||
=> null,
|
=> null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// true if this key is one of the left or right versions of super (MacOS)
|
||||||
|
/// or ctrl.
|
||||||
|
pub fn ctrlOrSuper(self: Key) bool {
|
||||||
|
if (comptime builtin.target.isDarwin()) {
|
||||||
|
return self == .left_super or self == .right_super;
|
||||||
|
}
|
||||||
|
return self == .left_control or self == .right_control;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// true if this key is either left or right shift.
|
||||||
|
pub fn leftOrRightShift(self: Key) bool {
|
||||||
|
return self == .left_shift or self == .right_shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// true if this key is either left or right alt.
|
||||||
|
pub fn leftOrRightAlt(self: Key) bool {
|
||||||
|
return self == .left_alt or self == .right_alt;
|
||||||
|
}
|
||||||
|
|
||||||
test "fromASCII should not return keypad keys" {
|
test "fromASCII should not return keypad keys" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
try testing.expect(Key.fromASCII('0').? == .zero);
|
try testing.expect(Key.fromASCII('0').? == .zero);
|
||||||
@ -689,3 +717,25 @@ pub const Key = enum(c_int) {
|
|||||||
.{ '=', .kp_equal },
|
.{ '=', .kp_equal },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// This sets either "ctrl" or "super" to true (but not both)
|
||||||
|
/// on mods depending on if the build target is Mac or not. On
|
||||||
|
/// Mac, we default to super (i.e. super+c for copy) and on
|
||||||
|
/// non-Mac we default to ctrl (i.e. ctrl+c for copy).
|
||||||
|
pub fn ctrlOrSuper(mods: Mods) Mods {
|
||||||
|
var copy = mods;
|
||||||
|
if (comptime builtin.target.isDarwin()) {
|
||||||
|
copy.super = true;
|
||||||
|
} else {
|
||||||
|
copy.ctrl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ctrlOrSuper" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var m: Mods = ctrlOrSuper(.{});
|
||||||
|
|
||||||
|
try testing.expect(m.ctrlOrSuper());
|
||||||
|
}
|
||||||
|
@ -313,6 +313,7 @@ test {
|
|||||||
_ = @import("termio.zig");
|
_ = @import("termio.zig");
|
||||||
_ = @import("input.zig");
|
_ = @import("input.zig");
|
||||||
_ = @import("cli.zig");
|
_ = @import("cli.zig");
|
||||||
|
_ = @import("surface_mouse.zig");
|
||||||
|
|
||||||
// Libraries
|
// Libraries
|
||||||
_ = @import("segmented_pool.zig");
|
_ = @import("segmented_pool.zig");
|
||||||
|
302
src/surface_mouse.zig
Normal file
302
src/surface_mouse.zig
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
/// SurfaceMouse represents mouse helper functionality for the core surface.
|
||||||
|
///
|
||||||
|
/// It's currently small in scope; its purpose is to isolate mouse logic that
|
||||||
|
/// has gotten a bit complex (e.g. pointer shape handling for key events), but
|
||||||
|
/// the intention is to grow it later so that we can better test said logic).
|
||||||
|
const SurfaceMouse = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const input = @import("input.zig");
|
||||||
|
const apprt = @import("apprt.zig");
|
||||||
|
const terminal = @import("terminal/main.zig");
|
||||||
|
const MouseShape = @import("terminal/mouse_shape.zig").MouseShape;
|
||||||
|
|
||||||
|
/// For processing key events; the key that was physically pressed on the
|
||||||
|
/// keyboard.
|
||||||
|
physical_key: input.Key,
|
||||||
|
|
||||||
|
/// The mouse event tracking mode, if any.
|
||||||
|
mouse_event: terminal.Terminal.MouseEvents,
|
||||||
|
|
||||||
|
/// The current terminal's mouse shape.
|
||||||
|
mouse_shape: MouseShape,
|
||||||
|
|
||||||
|
/// The last mods state when the last mouse button (whatever it was) was
|
||||||
|
/// pressed or release.
|
||||||
|
mods: input.Mods,
|
||||||
|
|
||||||
|
/// True if the mouse position is currently over a link.
|
||||||
|
over_link: bool,
|
||||||
|
|
||||||
|
/// Translates key state to mouse shape (cursor) state, based on a state
|
||||||
|
/// machine.
|
||||||
|
///
|
||||||
|
/// There are 4 current states:
|
||||||
|
///
|
||||||
|
/// * text: starting state, displays a text bar.
|
||||||
|
/// * default: default state when in a mouse tracking mode. (e.g. vim, etc).
|
||||||
|
/// Displays an arrow pointer.
|
||||||
|
/// * pointer: default state when over a link. Displays a pointing finger.
|
||||||
|
/// * crosshair: any above state can transition to this when the rectangle
|
||||||
|
/// select keys are pressed (ctrl/super+alt).
|
||||||
|
///
|
||||||
|
/// Additionally, default can transition back to text if one of the shift keys
|
||||||
|
/// are pressed during mouse tracking mode.
|
||||||
|
///
|
||||||
|
/// Any secondary state transitions back to its default state when the
|
||||||
|
/// appropriate keys are released.
|
||||||
|
///
|
||||||
|
/// null is returned when the mouse shape does not need changing.
|
||||||
|
pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape {
|
||||||
|
// Filter for appropriate key events
|
||||||
|
if (!eligibleMouseShapeKeyEvent(self.physical_key)) return null;
|
||||||
|
|
||||||
|
// Exception: link hover overrides any other shape processing currently and
|
||||||
|
// does not change state.
|
||||||
|
//
|
||||||
|
// TODO: As we unravel mouse state, we can fix this to be more explicit.
|
||||||
|
if (self.over_link) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set our current default state
|
||||||
|
var current_shape_state: MouseShape = undefined;
|
||||||
|
if (self.mouse_event != .none) {
|
||||||
|
// In mouse tracking mode, should be default (arrow pointer)
|
||||||
|
current_shape_state = .default;
|
||||||
|
} else {
|
||||||
|
// Default terminal mode, should be text (text bar)
|
||||||
|
current_shape_state = .text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition table.
|
||||||
|
//
|
||||||
|
// TODO: This could be updated eventually to be a true transition table if
|
||||||
|
// we move to a full stateful mouse surface, e.g. very specific inputs
|
||||||
|
// transitioning state based on previous state, versus flags like "is the
|
||||||
|
// mouse over a link", etc.
|
||||||
|
switch (current_shape_state) {
|
||||||
|
.default => {
|
||||||
|
if (isMouseModeOverrideState(self.mods) and isRectangleSelectState(self.mods)) {
|
||||||
|
// Crosshair (rectangle select), only set if we are also
|
||||||
|
// overriding (e.g. shift+ctrl+alt)
|
||||||
|
return .crosshair;
|
||||||
|
} else if (isMouseModeOverrideState(self.mods)) {
|
||||||
|
// Normal override state
|
||||||
|
return .text;
|
||||||
|
} else {
|
||||||
|
return .default;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.text => {
|
||||||
|
if (isRectangleSelectState(self.mods)) {
|
||||||
|
// Crosshair (rectangle select)
|
||||||
|
return .crosshair;
|
||||||
|
} else {
|
||||||
|
return .text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fall back on default state
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eligibleMouseShapeKeyEvent(physical_key: input.Key) bool {
|
||||||
|
return physical_key.ctrlOrSuper() or
|
||||||
|
physical_key.leftOrRightShift() or
|
||||||
|
physical_key.leftOrRightAlt();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isRectangleSelectState(mods: input.Mods) bool {
|
||||||
|
return mods.ctrlOrSuper() and mods.alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isMouseModeOverrideState(mods: input.Mods) bool {
|
||||||
|
return mods.shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "keyToMouseShape" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
{
|
||||||
|
// No specific key pressed
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .invalid,
|
||||||
|
.mouse_event = .none,
|
||||||
|
.mouse_shape = .progress,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(got == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Over a link. NOTE: This tests that we don't touch the inbound state,
|
||||||
|
// not necessarily if we're over a link.
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_shift,
|
||||||
|
.mouse_event = .none,
|
||||||
|
.mouse_shape = .progress,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(got == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// default, no mods (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_shift,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .default,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .default;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// default -> crosshair (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_alt,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .default,
|
||||||
|
.mods = .{ .ctrl = true, .super = true, .alt = true, .shift = true },
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .crosshair;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// default -> text (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_shift,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .default,
|
||||||
|
.mods = .{ .shift = true },
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .text;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// crosshair -> text (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_alt,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .crosshair,
|
||||||
|
.mods = .{ .shift = true },
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .text;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// crosshair -> default (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_alt,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .crosshair,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .default;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// text -> crosshair (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_alt,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .text,
|
||||||
|
.mods = .{ .ctrl = true, .super = true, .alt = true, .shift = true },
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .crosshair;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// text -> default (mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_shift,
|
||||||
|
.mouse_event = .x10,
|
||||||
|
.mouse_shape = .text,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .default;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// text, no mods (no mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_shift,
|
||||||
|
.mouse_event = .none,
|
||||||
|
.mouse_shape = .text,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .text;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// text -> crosshair (no mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_alt,
|
||||||
|
.mouse_event = .none,
|
||||||
|
.mouse_shape = .text,
|
||||||
|
.mods = .{ .ctrl = true, .super = true, .alt = true },
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .crosshair;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// crosshair -> text (no mouse tracking)
|
||||||
|
const m: SurfaceMouse = .{
|
||||||
|
.physical_key = .left_alt,
|
||||||
|
.mouse_event = .none,
|
||||||
|
.mouse_shape = .crosshair,
|
||||||
|
.mods = .{},
|
||||||
|
.over_link = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const want: MouseShape = .text;
|
||||||
|
const got = m.keyToMouseShape();
|
||||||
|
try testing.expect(want == got);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user