From d75c44725efccf29423493b286350d2c6534ab07 Mon Sep 17 00:00:00 2001 From: 00JCIV00 Date: Mon, 11 Mar 2024 16:53:57 -0400 Subject: [PATCH] Added the basic example for pausing the TUI with a Child Process. --- examples/pause_tui.zig | 144 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 examples/pause_tui.zig diff --git a/examples/pause_tui.zig b/examples/pause_tui.zig new file mode 100644 index 0000000..1d29460 --- /dev/null +++ b/examples/pause_tui.zig @@ -0,0 +1,144 @@ +const std = @import("std"); +const mem = std.mem; +const process = std.process; +const vaxis = @import("vaxis"); +const Cell = vaxis.Cell; +const TextInput = vaxis.widgets.TextInput; +const border = vaxis.widgets.border; + +const log = std.log.scoped(.main); + +// Our Event. This can contain internal events as well as Vaxis events. +// Internal events can be posted into the same queue as vaxis events to allow +// 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, + foo: u8, +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + const deinit_status = gpa.deinit(); + //fail test; can't try in defer as defer is executed after we return + if (deinit_status == .leak) { + log.err("memory leak", .{}); + } + } + const alloc = gpa.allocator(); + + // Initialize Vaxis with our event type + var vx = try vaxis.init(Event, .{}); + // deinit takes an optional allocator. If your program is exiting, you can + // choose to pass a null allocator to save some exit time. + defer vx.deinit(alloc); + + // Start the read loop. This puts the terminal in raw mode and begins + // reading user input + try vx.startReadThread(); + defer vx.stopReadThread(); + + // Optionally enter the alternate screen + try vx.enterAltScreen(); + defer vx.exitAltScreen() catch {}; + + // 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); + defer text_input.deinit(); + + // Sends queries to terminal to detect certain features. This should + // _always_ be called, but is left to the application to decide when + try vx.queryTerminal(); + + try vx.setMouseMode(true); + + // The main event loop. Vaxis provides a thread safe, blocking, buffered + // queue which can serve as the primary event queue for an application + while (true) { + // nextEvent blocks until an event is in the queue + const event = vx.nextEvent(); + log.debug("event: {}", .{event}); + // exhaustive switching ftw. Vaxis will send events if your Event + // enum has the fields for those events (ie "key_press", "winsize") + switch (event) { + .key_press => |key| { + color_idx = switch (color_idx) { + 255 => 0, + else => color_idx + 1, + }; + if (key.matches('c', .{ .ctrl = true })) { + break; + } else if (key.matches('l', .{ .ctrl = true })) { + vx.queueRefresh(); + } else if (key.matches('n', .{ .ctrl = true })) { + try vx.notify("vaxis", "hello from vaxis"); + } else if (key.matches('z', .{ .ctrl = true })) { + try openDirVim(alloc, &vx, "examples"); + } else { + try text_input.update(.{ .key_press = key }); + } + }, + + // winsize events are sent to the application to ensure that all + // resizes occur in the main thread. This lets us avoid expensive + // locks on the screen. All applications must handle this event + // unless they aren't using a screen (IE only detecting features) + // + // This is the only call that the core of Vaxis needs an allocator + // for. The allocations are because we keep a copy of each cell to + // optimize renders. When resize is called, we allocated two slices: + // one for the screen, and one for our buffered screen. Each cell in + // the buffered screen contains an ArrayList(u8) to be able to store + // the grapheme for that cell Each cell is initialized with a size + // of 1, which is sufficient for all of ASCII. Anything requiring + // more than one byte will incur an allocation on the first render + // after it is drawn. Thereafter, it will not allocate unless the + // screen is resized + .winsize => |ws| try vx.resize(alloc, ws), + else => {}, + } + + // vx.window() returns the root window. This window is the size of the + // terminal and can spawn child windows as logical areas. Child windows + // cannot draw outside of their bounds + const win = vx.window(); + + // Clear the entire space because we are drawing in immediate mode. + // vaxis double buffers the screen. This new frame will be compared to + // the old and only updated cells will be drawn + win.clear(); + const child = win.initChild( + win.width / 2 - 20, + win.height / 2 - 3, + .{ .limit = 40 }, + .{ .limit = 3 }, + ); + // draw the text_input using a bordered window + const style: vaxis.Style = .{ + .fg = .{ .index = color_idx }, + }; + text_input.draw(border.all(child, style)); + + // Render the screen + try vx.render(); + } +} + +/// Open the provided Directory in a temp Vim session, pausing the TUI. +fn openDirVim(alloc: mem.Allocator, vx: anytype, path: []const u8) !void { + try vx.exitAltScreen(); + vx.stopReadThread(); + var dir_look = process.Child.init(&.{ "vim", "-R", path }, alloc); + _ = try dir_look.spawnAndWait(); + try vx.startReadThread(); + try vx.enterAltScreen(); + vx.queueRefresh(); +}