diff --git a/examples/container.zig b/examples/container.zig index eb2d9d0..e15dce3 100644 --- a/examples/container.zig +++ b/examples/container.zig @@ -119,9 +119,9 @@ pub fn main() !void { .quit => break, .resize => |size| try renderer.resize(size), .key => |key| { - if (key.matches(.{ .cp = 'q' })) app.quit(); + if (key.eql(.{ .cp = 'q' })) app.quit(); - if (key.matches(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { + if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { try app.interrupt(); defer app.start() catch @panic("could not start app event loop"); var child = std.process.Child.init(&.{"hx"}, allocator); diff --git a/src/app.zig b/src/app.zig index d09f56d..3f392df 100644 --- a/src/app.zig +++ b/src/app.zig @@ -1,14 +1,15 @@ //! Application type for TUI-applications const std = @import("std"); -const terminal = @import("terminal.zig"); +const code_point = @import("code_point"); const event = @import("event.zig"); +const input = @import("input.zig"); +const terminal = @import("terminal.zig"); const mergeTaggedUnions = event.mergeTaggedUnions; const isTaggedUnion = event.isTaggedUnion; -const key = @import("key.zig"); -const Mouse = @import("mouse.zig").Mouse; -const Key = key.Key; +const Mouse = input.Mouse; +const Key = input.Key; const Size = @import("size.zig").Size; const Queue = @import("queue.zig").Queue; @@ -182,21 +183,21 @@ pub fn App(comptime E: type) type { 0x4F => { // ss3 if (read_bytes < 3) continue; - const k: Key = switch (buf[2]) { - 'A' => .{ .cp = key.Up }, - 'B' => .{ .cp = key.Down }, - 'C' => .{ .cp = key.Right }, - 'D' => .{ .cp = key.Left }, - 'E' => .{ .cp = key.KpBegin }, - 'F' => .{ .cp = key.End }, - 'H' => .{ .cp = key.Home }, - 'P' => .{ .cp = key.F1 }, - 'Q' => .{ .cp = key.F2 }, - 'R' => .{ .cp = key.F3 }, - 'S' => .{ .cp = key.F4 }, + const key: Key = switch (buf[2]) { + 'A' => .{ .cp = input.Up }, + 'B' => .{ .cp = input.Down }, + 'C' => .{ .cp = input.Right }, + 'D' => .{ .cp = input.Left }, + 'E' => .{ .cp = input.KpBegin }, + 'F' => .{ .cp = input.End }, + 'H' => .{ .cp = input.Home }, + 'P' => .{ .cp = input.F1 }, + 'Q' => .{ .cp = input.F2 }, + 'R' => .{ .cp = input.F3 }, + 'S' => .{ .cp = input.F4 }, else => continue, }; - this.postEvent(.{ .key = k }); + this.postEvent(.{ .key = key }); }, 0x5B => { // csi if (read_bytes < 3) continue; @@ -215,23 +216,23 @@ pub fn App(comptime E: type) type { // Legacy keys // CSI {ABCDEFHPQS} // CSI 1 ; modifier:event_type {ABCDEFHPQS} - const k: Key = .{ + const key: Key = .{ .cp = switch (final) { - 'A' => key.Up, - 'B' => key.Down, - 'C' => key.Right, - 'D' => key.Left, - 'E' => key.KpBegin, - 'F' => key.End, - 'H' => key.Home, - 'P' => key.F1, - 'Q' => key.F2, - 'R' => key.F3, - 'S' => key.F4, + 'A' => input.Up, + 'B' => input.Down, + 'C' => input.Right, + 'D' => input.Left, + 'E' => input.KpBegin, + 'F' => input.End, + 'H' => input.Home, + 'P' => input.F1, + 'Q' => input.F2, + 'R' => input.F3, + 'S' => input.F4, else => unreachable, // switch case prevents in this case form ever happening }, }; - this.postEvent(.{ .key = k }); + this.postEvent(.{ .key = key }); }, '~' => { // Legacy keys @@ -242,34 +243,35 @@ pub fn App(comptime E: type) type { const number_buf = field_iter.next() orelse unreachable; // always will have one field const number = std.fmt.parseUnsigned(u16, number_buf, 10) catch break; - const k: Key = .{ + const key: Key = .{ .cp = switch (number) { - 2 => key.Insert, - 3 => key.Delete, - 5 => key.PageUp, - 6 => key.PageDown, - 7 => key.Home, - 8 => key.End, - 11 => key.F1, - 12 => key.F2, - 13 => key.F3, - 14 => key.F4, - 15 => key.F5, - 17 => key.F6, - 18 => key.F7, - 19 => key.F8, - 20 => key.F9, - 21 => key.F10, - 23 => key.F11, - 24 => key.F12, + 2 => input.Insert, + 3 => input.Delete, + 5 => input.PageUp, + 6 => input.PageDown, + 7 => input.Home, + 8 => input.End, + 11 => input.F1, + 12 => input.F2, + 13 => input.F3, + 14 => input.F4, + 15 => input.F5, + 17 => input.F6, + 18 => input.F7, + 19 => input.F8, + 20 => input.F9, + 21 => input.F10, + 23 => input.F11, + 24 => input.F12, // 200 => return .{ .event = .paste_start, .n = sequence.len }, // 201 => return .{ .event = .paste_end, .n = sequence.len }, - 57427 => key.KpBegin, + 57427 => input.KpBegin, else => unreachable, }, }; - this.postEvent(.{ .key = k }); + this.postEvent(.{ .key = key }); }, + // TODO: focus usage? should this even be in the default event system? 'I' => this.postEvent(.{ .focus = true }), 'O' => this.postEvent(.{ .focus = false }), 'M', 'm' => { @@ -296,7 +298,7 @@ pub fn App(comptime E: type) type { // const alt = button_mask & mouse_bits.alt > 0; // const ctrl = button_mask & mouse_bits.ctrl > 0; - const mouse = Mouse{ + const mouse: Mouse = .{ .button = button, .col = px -| 1, .row = py -| 1, @@ -363,24 +365,24 @@ pub fn App(comptime E: type) type { } } else { const b = buf[0]; - const k: Key = switch (b) { + const key: Key = switch (b) { 0x00 => .{ .cp = '@', .mod = .{ .ctrl = true } }, - 0x08 => .{ .cp = key.Backspace }, - 0x09 => .{ .cp = key.Tab }, - 0x0a, 0x0d => .{ .cp = key.Enter }, + 0x08 => .{ .cp = input.Backspace }, + 0x09 => .{ .cp = input.Tab }, + 0x0a, 0x0d => .{ .cp = input.Enter }, 0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } }, 0x1b => escape: { std.debug.assert(read_bytes == 1); - break :escape .{ .cp = key.Escape }; + break :escape .{ .cp = input.Escape }; }, - 0x7f => .{ .cp = key.Backspace }, + 0x7f => .{ .cp = input.Backspace }, else => { - var iter = terminal.code_point.Iterator{ .bytes = buf[0..read_bytes] }; + var iter = code_point.Iterator{ .bytes = buf[0..read_bytes] }; while (iter.next()) |cp| this.postEvent(.{ .key = .{ .cp = cp.code } }); continue; }, }; - this.postEvent(.{ .key = k }); + this.postEvent(.{ .key = key }); } continue; }; diff --git a/src/event.zig b/src/event.zig index c80b314..a0126e9 100644 --- a/src/event.zig +++ b/src/event.zig @@ -1,11 +1,12 @@ //! Events which are defined by the library. They might be extended by user //! events. See `App` for more details about user defined events. const std = @import("std"); +const input = @import("input.zig"); const terminal = @import("terminal.zig"); -const Mouse = @import("mouse.zig").Mouse; +const Key = input.Key; +const Mouse = input.Mouse; const Size = @import("size.zig").Size; -const Key = @import("key.zig").Key; /// System events available to every `zterm.App` pub const SystemEvent = union(enum) { diff --git a/src/key.zig b/src/input.zig similarity index 79% rename from src/key.zig rename to src/input.zig index 2d85dba..fb58dcc 100644 --- a/src/key.zig +++ b/src/input.zig @@ -1,8 +1,46 @@ -//! Keybindings and Modifiers for user input detection and selection. - -// TODO: rename this module to 'input' and include the mouse.zig contents as well! +//! Input module for `zterm`. Contains structs to represent key events and mouse events. const std = @import("std"); +const Size = @import("size.zig").Size; + +pub const Mouse = packed struct { + col: u16, + row: u16, + button: Button, + kind: Kind, + + pub const Button = enum(u8) { + left, + middle, + right, + none, + wheel_up = 64, + wheel_down = 65, + wheel_right = 66, + wheel_left = 67, + button_8 = 128, + button_9 = 129, + button_10 = 130, + button_11 = 131, + }; + + pub const Kind = enum(u2) { + press, + release, + motion, + drag, + }; + + pub fn eql(this: @This(), other: @This()) bool { + return std.meta.eql(this, other); + } + + pub fn in(this: @This(), size: Size) bool { + return this.col >= size.anchor.col and this.col <= size.cols -| size.anchor.col and + this.row >= size.anchor.row and this.row <= size.rows -| size.anchor.row; + } +}; + pub const Key = packed struct { cp: u21, mod: Modifier = .{}, @@ -24,14 +62,13 @@ pub const Key = packed struct { /// .quit => break, /// .key => |key| { /// // ctrl+c to quit - /// if (terminal.Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + /// if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) /// app.quit.set(); - /// } /// }, /// else => {}, /// } /// ``` - pub fn matches(this: @This(), other: @This()) bool { + pub fn eql(this: @This(), other: @This()) bool { return std.meta.eql(this, other); } }; diff --git a/src/main.zig b/src/main.zig index cb43b87..f64b35a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const zterm = @import("zterm"); +const input = zterm.input; const App = zterm.App(union(enum) {}); const log = std.log.scoped(.default); @@ -42,7 +43,7 @@ pub const HelloWorldText = packed struct { switch (event) { .init => log.debug(".init event", .{}), .key => |key| { - if (key.matches(.{ .cp = zterm.key.Space })) { + if (key.eql(.{ .cp = input.Space })) { var next_color_idx = @intFromEnum(this.text_color); next_color_idx += 1; next_color_idx %= 17; // iterate over the first 16 colors (but exclude `.default` == 0) @@ -142,13 +143,13 @@ pub fn main() !void { .quit => break, .resize => |size| try renderer.resize(size), .key => |key| { - if (key.matches(.{ .cp = 'q' })) app.quit(); + if (key.eql(.{ .cp = 'q' })) app.quit(); // corresponding element could even be changed from the 'outside' (not forced through the event system) // event system however allows for cross element communication (i.e. broadcasting messages, etc.) - if (key.matches(.{ .cp = zterm.key.Escape })) element_wrapper.text_color = .black; + if (key.eql(.{ .cp = input.Escape })) element_wrapper.text_color = .black; - if (key.matches(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { + if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { try app.interrupt(); defer app.start() catch @panic("could not start app event loop"); var child = std.process.Child.init(&.{"hx"}, allocator); diff --git a/src/mouse.zig b/src/mouse.zig deleted file mode 100644 index 121289e..0000000 --- a/src/mouse.zig +++ /dev/null @@ -1,29 +0,0 @@ -/// Mouse input detection. -pub const Mouse = packed struct { - col: u16, - row: u16, - button: Button, - kind: Kind, - - pub const Button = enum(u8) { - left, - middle, - right, - none, - wheel_up = 64, - wheel_down = 65, - wheel_right = 66, - wheel_left = 67, - button_8 = 128, - button_9 = 129, - button_10 = 130, - button_11 = 131, - }; - - pub const Kind = enum(u2) { - press, - release, - motion, - drag, - }; -}; diff --git a/src/terminal.zig b/src/terminal.zig index 76c6ca3..55310c1 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -1,8 +1,9 @@ const std = @import("std"); -pub const code_point = @import("code_point"); +const code_point = @import("code_point"); const ctlseqs = @import("ctlseqs.zig"); +const input = @import("input.zig"); -const Key = @import("key.zig").Key; +const Key = input.Key; const Position = @import("size.zig").Position; const Size = @import("size.zig").Size; const Cell = @import("cell.zig"); diff --git a/src/zterm.zig b/src/zterm.zig index b23e4dc..63073b9 100644 --- a/src/zterm.zig +++ b/src/zterm.zig @@ -4,7 +4,7 @@ const color = @import("color.zig"); const size = @import("size.zig"); // public exports -pub const key = @import("key.zig"); +pub const input = @import("input.zig"); pub const App = @import("app.zig").App; // App also exports further types once initialized with the user events at compile time: @@ -20,7 +20,8 @@ pub const Layout = container.Layout; pub const Cell = @import("cell.zig"); pub const Color = color.Color; -pub const Key = key.Key; +pub const Key = input.Key; +pub const Mouse = input.Mouse; pub const Position = size.Position; pub const Size = size.Size; pub const Style = @import("style.zig");