From f3cf7bcfcd393dae4e5529b778e0174c1d41d981 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Wed, 31 Jan 2024 12:50:00 -0600 Subject: [PATCH] mouse: implement mouse parsing and events Signed-off-by: Tim Culverhouse --- build.zig | 2 +- examples/text_input.zig | 1 + src/Mouse.zig | 32 ++++++++++++++++++++++++- src/Parser.zig | 52 +++++++++++++++++++++++++++++++++++++++++ src/Tty.zig | 5 ++++ src/event.zig | 2 ++ src/main.zig | 1 + 7 files changed, 93 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index cc3840d..e60c15a 100644 --- a/build.zig +++ b/build.zig @@ -20,7 +20,7 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "vaxis", - .root_source_file = .{ .path = "examples/image.zig" }, + .root_source_file = .{ .path = "examples/text_input.zig" }, .target = target, .optimize = optimize, }); diff --git a/examples/text_input.zig b/examples/text_input.zig index a7e199e..ce79d44 100644 --- a/examples/text_input.zig +++ b/examples/text_input.zig @@ -11,6 +11,7 @@ const log = std.log.scoped(.main); // for a single event loop with exhaustive switching. Booya const Event = union(enum) { key_press: vaxis.Key, + mouse: vaxis.Mouse, winsize: vaxis.Winsize, focus_in, focus_out, diff --git a/src/Mouse.zig b/src/Mouse.zig index 719cfcc..2129835 100644 --- a/src/Mouse.zig +++ b/src/Mouse.zig @@ -13,4 +13,34 @@ pub const Shape = enum { cell, }; -// TODO: mouse support +pub const Button = enum(u8) { + left, + middle, + right, + none, + wheel_up = 64, + wheel_down = 65, + button_8 = 128, + button_9 = 129, + button_10 = 130, + button_11 = 131, +}; + +pub const Modifiers = packed struct(u3) { + shift: bool = false, + alt: bool = false, + ctrl: bool = false, +}; + +pub const Type = enum { + press, + release, + motion, + drag, +}; + +col: usize, +row: usize, +button: Button, +mods: Modifiers, +type: Type, diff --git a/src/Parser.zig b/src/Parser.zig index 3bf92bc..6a15601 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -2,6 +2,7 @@ const std = @import("std"); const testing = std.testing; const Event = @import("event.zig").Event; const Key = @import("Key.zig"); +const Mouse = @import("Mouse.zig"); const CodePointIterator = @import("ziglyph").CodePointIterator; const graphemeBreak = @import("ziglyph").graphemeBreak; @@ -32,6 +33,14 @@ const Sequence = struct { empty_state: std.StaticBitSet(16) = std.StaticBitSet(16).initEmpty(), }; +const mouse_bits = struct { + const motion: u8 = 0b00100000; + const buttons: u8 = 0b11000011; + const shift: u8 = 0b00000100; + const alt: u8 = 0b00001000; + const ctrl: u8 = 0b00010000; +}; + // the state of the parser const State = enum { ground, @@ -226,6 +235,49 @@ pub fn parse(self: *Parser, input: []const u8) !Result { 'E' => Key.kp_begin, 'F' => Key.end, 'H' => Key.home, + 'M', 'm' => { // mouse event + const priv = seq.private_indicator orelse { + log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]}); + return .{ .event = null, .n = i + 1 }; + }; + if (priv != '<') { + log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]}); + return .{ .event = null, .n = i + 1 }; + } + if (seq.param_idx != 3) { + log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]}); + return .{ .event = null, .n = i + 1 }; + } + const button: Mouse.Button = @enumFromInt(seq.params[0] & mouse_bits.buttons); + const motion = seq.params[0] & mouse_bits.motion > 0; + const shift = seq.params[0] & mouse_bits.shift > 0; + const alt = seq.params[0] & mouse_bits.alt > 0; + const ctrl = seq.params[0] & mouse_bits.ctrl > 0; + const col: usize = seq.params[1] - 1; + const row: usize = seq.params[2] - 1; + + const mouse = Mouse{ + .button = button, + .mods = .{ + .shift = shift, + .alt = alt, + .ctrl = ctrl, + }, + .col = col, + .row = row, + .type = blk: { + if (motion and button != Mouse.Button.none) { + break :blk .drag; + } + if (motion and button == Mouse.Button.none) { + break :blk .motion; + } + if (b == 'm') break :blk .release; + break :blk .press; + }, + }; + return .{ .event = .{ .mouse = mouse }, .n = i + 1 }; + }, 'P' => Key.f1, 'Q' => Key.f2, 'R' => Key.f3, diff --git a/src/Tty.zig b/src/Tty.zig index c07746b..52db773 100644 --- a/src/Tty.zig +++ b/src/Tty.zig @@ -157,6 +157,11 @@ pub fn run( vx.postEvent(.{ .key_press = mut_key }); } }, + .mouse => |mouse| { + if (@hasField(EventType, "mouse")) { + vx.postEvent(.{ .mouse = mouse }); + } + }, .focus_in => { if (@hasField(EventType, "focus_in")) { vx.postEvent(.focus_in); diff --git a/src/event.zig b/src/event.zig index e66d134..5003834 100644 --- a/src/event.zig +++ b/src/event.zig @@ -1,8 +1,10 @@ pub const Key = @import("Key.zig"); +pub const Mouse = @import("Mouse.zig"); /// The events that Vaxis emits internally pub const Event = union(enum) { key_press: Key, + mouse: Mouse, focus_in, focus_out, paste_start, diff --git a/src/main.zig b/src/main.zig index 63e2c00..5903ad3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ pub const Cell = cell.Cell; pub const Style = cell.Style; pub const Key = @import("Key.zig"); +pub const Mouse = @import("Mouse.zig"); pub const Winsize = @import("Tty.zig").Winsize; pub const widgets = @import("widgets/main.zig");