From 93ecfbeda0347f8cb67594258c33c68cc4c7606e Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Wed, 9 Oct 2024 11:10:40 +0200 Subject: [PATCH] add(PopupMenu): menu to enable space key binding groupings Some minor layout changes of the application --- src/main.zig | 78 +++++++++++++++++---------------------- src/widget.zig | 1 + src/widget/PopupMenu.zig | 80 ++++++++++++++++++++++++++++++++++++++++ src/widget/ViewPort.zig | 2 +- 4 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 src/widget/PopupMenu.zig diff --git a/src/main.zig b/src/main.zig index d5259c3..ff17d7f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -50,60 +50,52 @@ pub fn main() !void { // Optionally enter the alternate screen try vx.enterAltScreen(tty.anyWriter()); - // We'll adjust the color index every keypress for the border - var color_idx: u8 = 0; - - // init our text input widget. The text input widget needs an allocator to - // store the contents of the input - var text_input = TextInput.init(alloc, &vx.unicode); - defer text_input.deinit(); - var header = try widget.Header.init(alloc, &vx.unicode); defer header.deinit(); var view_port = widget.ViewPort.init(alloc, &vx.unicode); defer view_port.deinit(); + var active_menu = false; + var menu = widget.PopupMenu.init(alloc, &vx.unicode); + defer menu.deinit(); + // Sends queries to terminal to detect certain features. This should always // be called after entering the alt screen, if you are using the alt screen try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); + loop.postEvent(.{ .path = "./doc/home.md" }); + while (true) { const event = loop.nextEvent(); // update widgets header.update(event); view_port.update(event); + if (active_menu) { + if (menu.update(event)) |e| { + _ = loop.tryPostEvent(e); + active_menu = false; + } + } switch (event) { .key_press => |key| { - color_idx = switch (color_idx) { - 255 => 0, - else => color_idx + 1, - }; + if (active_menu) { + if (key.matches(vaxis.Key.escape, .{})) { + active_menu = false; + } + } + if (key.matches('c', .{ .ctrl = true })) { break; + } else if (key.matches(vaxis.Key.space, .{})) { + active_menu = true; } else if (key.matches('l', .{ .ctrl = true })) { vx.queueRefresh(); - } else if (key.matches(vaxis.Key.enter, .{})) { - var len: usize = 0; - for (text_input.buf.buffer) |c| { - if (c == 0xaa or c == 0) - break; - len += 1; - } - const path = alloc.alloc(u8, len) catch @panic("OOM"); - @memcpy(path, text_input.buf.buffer[0..len]); - if (loop.tryPostEvent(.{ .path = path })) { - text_input.clearAndFree(); - } - } else { - try text_input.update(.{ .key_press = key }); } }, .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), - .path => |*path| { - alloc.free(path.*); - }, + else => {}, } var root_window = vx.window(); @@ -118,25 +110,21 @@ pub fn main() !void { .border = .{ .where = .all }, })); - text_input.draw(root_window.child(.{ - .x_off = 20, + view_port.draw(root_window.child(.{ + .x_off = root_window.width / 8, .y_off = 3, - .width = .{ .limit = 40 }, - .height = .{ .limit = 3 }, - .border = .{ - .where = .all, - .style = .{ .fg = .{ .index = color_idx } }, - }, + .width = .{ .limit = root_window.width / 2 + (root_window.width / 4) }, })); - view_port.draw(root_window.child(.{ - .x_off = root_window.width / 4, - .y_off = 3, - .width = .{ .limit = root_window.width / 2 }, - .border = .{ - .where = .{ .other = .{ .right = true, .left = true } }, - }, - })); + if (active_menu) { + menu.draw(root_window.child(.{ + .x_off = root_window.width / 2 - 25, + .y_off = root_window.height / 2 - 10, + .width = .{ .limit = 50 }, + .height = .{ .limit = 20 }, + .border = .{ .where = .all }, + })); + } // Render the screen. Using a buffered writer will offer much better // performance, but is not required diff --git a/src/widget.zig b/src/widget.zig index ba23d22..5c8b8a7 100644 --- a/src/widget.zig +++ b/src/widget.zig @@ -60,3 +60,4 @@ pub fn createFrom(object: anytype) @This() { pub const Header = @import("widget/Header.zig"); pub const ViewPort = @import("widget/ViewPort.zig"); +pub const PopupMenu = @import("widget/PopupMenu.zig"); diff --git a/src/widget/PopupMenu.zig b/src/widget/PopupMenu.zig new file mode 100644 index 0000000..53f7f7e --- /dev/null +++ b/src/widget/PopupMenu.zig @@ -0,0 +1,80 @@ +//! Pop-up Menu widget to show the available keybindings + +const std = @import("std"); +const vaxis = @import("vaxis"); + +const widget = @import("../widget.zig"); + +const Event = widget.Event; + +allocator: std.mem.Allocator = undefined, +unicode: *const vaxis.Unicode = undefined, + +pub fn init(allocator: std.mem.Allocator, unicode: *const vaxis.Unicode) @This() { + return .{ + .allocator = allocator, + .unicode = unicode, + }; +} + +pub fn deinit(this: *@This()) void { + this.* = undefined; +} + +/// Update loop for a given widget to react to the provided `Event`. It may +/// change its internal state, update variables, react to user input, etc. +pub fn update(this: *@This(), event: Event) ?Event { + _ = this; + switch (event) { + .key_press => |key| { + if (key.matches('a', .{})) { + // About + return .{ .path = "./doc/about.md" }; + } + if (key.matches('h', .{})) { + // Home + return .{ .path = "./doc/home.md" }; + } + }, + else => {}, + } + return null; +} + +/// Draw a given widget using the provided `vaxis.Window`. The window controls +/// the dimension one widget may take on the screen. The widget itself has no +/// control over this. +pub fn draw(this: *@This(), win: vaxis.Window) void { + var view = vaxis.widgets.View.init(this.allocator, this.unicode, .{ .width = win.width, .height = win.height }) catch @panic("OOM"); + defer view.deinit(); + + const msg = + \\# Goto + \\ + \\*a* about + \\*h* home + ; + var col: usize = 0; + var row: usize = 0; + for (msg, 0..) |_, i| { + const cell: vaxis.Cell = .{ + // each cell takes a _grapheme_ as opposed to a single + // codepoint. This allows Vaxis to handle emoji properly, + // particularly with terminals that the Unicode Core extension + // (IE Mode 2027) + .char = .{ .grapheme = msg[i .. i + 1] }, + .style = .{ + .fg = .{ .index = 6 }, + .bold = true, + }, + }; + view.writeCell(col, row, cell); + if (std.mem.eql(u8, cell.char.grapheme, "\n")) { + col = 0; + row += 1; + } else { + col += 1; + } + } + view.draw(win, .{}); +} diff --git a/src/widget/ViewPort.zig b/src/widget/ViewPort.zig index 08df345..b98aba4 100644 --- a/src/widget/ViewPort.zig +++ b/src/widget/ViewPort.zig @@ -17,7 +17,7 @@ pub fn init(allocator: std.mem.Allocator, unicode: *const vaxis.Unicode) @This() .allocator = allocator, .unicode = unicode, .buffer = .{}, - .view = .{ .vertical_scrollbar = .{} }, + .view = .{ .vertical_scrollbar = null }, }; }