### Zig Vaxis Initialization and Event Loop Example Source: https://github.com/rockorager/libvaxis/blob/main/README.md This Zig code snippet demonstrates the initialization of the Vaxis library, including TTY setup, Vaxis instance creation, and the integration of a multi-threaded event loop. It shows how to handle key press and window resize events, manage a text input widget, and interact with the terminal's alternate screen and features. Dependencies include the 'std' and 'vaxis' libraries. ```zig const std = @import("std"); const vaxis = @import("vaxis"); const Cell = vaxis.Cell; const TextInput = vaxis.widgets.TextInput; const border = vaxis.widgets.border; // 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, winsize: vaxis.Winsize, focus_in, 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) { std.log.err("memory leak", .{{}}); } } const alloc = gpa.allocator(); // Initialize a tty var buffer: [1024]u8 = undefined; var tty = try vaxis.Tty.init(&buffer); defer tty.deinit(); // Initialize Vaxis var vx = try vaxis.init(alloc, .{}); // 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, tty.writer()); // The event loop requires an intrusive init. We create an instance with // stable pointers to Vaxis and our TTY, then init the instance. Doing so // installs a signal handler for SIGWINCH on posix TTYs // // This event loop is thread safe. It reads the tty in a separate thread var loop: vaxis.Loop(Event) = {{ .tty = &tty, .vaxis = &vx, }}; try loop.init(); // Start the read loop. This puts the terminal in raw mode and begins // reading user input try loop.start(); defer loop.stop(); // Optionally enter the alternate screen try vx.enterAltScreen(tty.writer()); // 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 after entering the alt screen, if you are using the alt screen try vx.queryTerminal(tty.writer(), 1 * std.time.ns_per_s); while (true) { // nextEvent blocks until an event is in the queue const event = loop.nextEvent(); // 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 { 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) // // 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, tty.writer(), ws), else => {{}}, } } } ``` -------------------------------- ### Install libvaxis using zig fetch Source: https://context7.com/rockorager/libvaxis/llms.txt This command adds the libvaxis library to your Zig project by fetching it from its GitHub repository. ```bash zig fetch --save git+https://github.com/rockorager/libvaxis.git ``` -------------------------------- ### Text Input Widget Example in Zig Source: https://context7.com/rockorager/libvaxis/llms.txt Demonstrates how to create and manage a text input widget using libvaxis. It shows event handling for key presses, clearing input, and rendering the widget within a window. Requires the 'vaxis' library. ```zig const std = @import("std"); const vaxis = @import("vaxis"); const TextInput = vaxis.widgets.TextInput; pub fn textInputExample(alloc: std.mem.Allocator) !void { var text_input = TextInput.init(alloc); defer text_input.deinit(); // In event loop while (true) { const event = loop.nextEvent(); switch (event) { .key_press => |key| { if (key.matches(vaxis.Key.enter, .{})) { // Get the content const content = text_input.buf.items; std.debug.print("Input: {s}\n", .{content}); text_input.clearAndFree(); } else { try text_input.update(.{ .key_press = key }); } }, .winsize => |ws| try vx.resize(alloc, tty.writer(), ws), else => {}, } const win = vx.window(); win.clear(); const input_win = win.child(.{ .x_off = 5, .y_off = 5, .width = 50, .height = 3, .border = .{ .where = .all }, }); text_input.draw(input_win); try vx.render(tty.writer()); } } ``` -------------------------------- ### Button Counter App with vxfw Framework in Zig Source: https://context7.com/rockorager/libvaxis/llms.txt Illustrates building a stateful application using the vxfw framework in libvaxis. This example creates a simple button counter where clicks increment a counter. It showcases event handling, drawing widgets, and application lifecycle management. Requires 'vaxis' and 'vxfw'. ```zig const std = @import("std"); const vaxis = @import("vaxis"); const vxfw = vaxis.vxfw; const Model = struct { count: u32 = 0, button: vxfw.Button, pub fn widget(self: *Model) vxfw.Widget { return .{ .userdata = self, .eventHandler = Model.typeErasedEventHandler, .drawFn = Model.typeErasedDrawFn, }; } fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { const self: *Model = @ptrCast(@alignCast(ptr)); switch (event) { .init => return ctx.requestFocus(self.button.widget()), .key_press => |key| { if (key.matches('c', .{ .ctrl = true })) { ctx.quit = true; return; } }, .focus_in => return ctx.requestFocus(self.button.widget()), else => {}, } } fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface { const self: *Model = @ptrCast(@alignCast(ptr)); const max_size = ctx.max.size(); if (self.count > 0) { self.button.label = try std.fmt.allocPrint(ctx.arena, "Clicks: {d}", .{self.count}); } else { self.button.label = "Click me!"; } const button_child: vxfw.SubSurface = .{ .origin = .{ .row = 0, .col = 0 }, .surface = try self.button.draw(ctx.withConstraints( ctx.min, .{ .width = 16, .height = 3 }, )), }; const children = try ctx.arena.alloc(vxfw.SubSurface, 1); children[0] = button_child; return .{ .size = max_size, .widget = self.widget(), .buffer = &.{}., .children = children, }; } fn onClick(maybe_ptr: ?*anyopaque, ctx: *vxfw.EventContext) anyerror!void { const ptr = maybe_ptr orelse return; const self: *Model = @ptrCast(@alignCast(ptr)); self.count +|= 1; return ctx.consumeAndRedraw(); } }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var app = try vxfw.App.init(allocator); defer app.deinit(); const model = try allocator.create(Model); defer allocator.destroy(model); model.* = .{ .count = 0, .button = .{ .label = "Click me!", .onClick = Model.onClick, .userdata = model, }, }; try app.run(model.widget(), .{}); } ``` -------------------------------- ### Manage and Render Terminal Windows with libvaxis Source: https://github.com/rockorager/libvaxis/blob/main/README.md This snippet demonstrates basic window management in libvaxis. It shows how to get the root window, clear the screen, create a styled child window with borders, draw text input within the child window, and finally render the entire screen. It utilizes vaxis's immediate mode drawing and double buffering capabilities. The rendering can be optimized using a buffered writer. ```zig const std = @import("std"); const tty = std.tty; const vaxis = @import("vaxis"); const color_idx = 1; pub fn main() void { const vx = vaxis.init(); defer vx.deinit(); // 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(); // Create a style const style: vaxis.Style = .{ .fg = .{ .index = color_idx } }; // Create a bordered child window const child = win.child(.{ .x_off = win.width / 2 - 20, .y_off = win.height / 2 - 3, .width = 40, .height = 3, .border = .{ .where = .all, .style = style, }, }); // Assuming text_input is an object with a draw method // text_input.draw(child); // Render the screen. Using a buffered writer will offer much better // performance, but is not required try vx.render(tty.writer()); } ``` -------------------------------- ### Initialize and Run libvaxis Application Source: https://github.com/rockorager/libvaxis/blob/main/README.md The main function initializes the application using libvaxis, sets up memory allocation, and creates the main application model. It then runs the application loop with the defined model and root widget. ```zig pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var app = try vxfw.App.init(allocator); defer app.deinit(); // We heap allocate our model because we will require a stable pointer to it in our Button // widget const model = try allocator.create(Model); defer allocator.destroy(model); // Set the initial state of our button model.* = { .count = 0, .button = { .label = "Click me!", .onClick = Model.onClick, .userdata = model, }, }; try app.run(model.widget(), .{}); } ``` -------------------------------- ### Vaxis Reader Task Initialization and Handling Source: https://github.com/rockorager/libvaxis/blob/main/USAGE.md This snippet demonstrates the initialization and conditional execution of reader tasks for TTY input within the Vaxis library. It handles different operating systems by selecting appropriate reader functions and includes error handling for task cancellation and other errors. Dependencies include the 'coro' and 'vaxis' modules. ```zig fn ttyReaderTask(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) void { return switch (builtin.target.os.tag) { .windows => self.ttyReaderWindows(vx, tty), else => self.ttyReaderPosix(vx, tty, paste_allocator), } catch |err| { if (err != error.Canceled) log.err("ttyReader: {}", .{err}); self.fatal = true; }; } /// Spawns tasks to handle winsize signal and tty pub fn spawn( self: *@This(), scheduler: *coro.Scheduler, vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator, spawn_options: coro.Scheduler.SpawnOptions, ) coro.Scheduler.SpawnError!void { if (self.reader_task) |_| unreachable; // programming error // This is required even if app doesn't care about winsize // It is because it consumes the EventSource, so it can wakeup the scheduler // Without that custom `postEvent`'s wouldn't wake up the scheduler and UI wouldn't update self.winsize_task = try scheduler.spawn(winsizeTask, .{ self, tty }, spawn_options); self.reader_task = try scheduler.spawn(ttyReaderTask, .{ self, vx, tty, paste_allocator }, spawn_options); } ``` -------------------------------- ### Initialize Vaxis and Event Loop in Zig Source: https://context7.com/rockorager/libvaxis/llms.txt This Zig code sets up a basic libvaxis application, initializing the TTY and Vaxis instance, and creating an event loop to handle terminal events like key presses and window resizes. ```zig const std = @import("std"); const vaxis = @import("vaxis"); const Event = union(enum) { key_press: vaxis.Key, winsize: vaxis.Winsize, focus_in, }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const alloc = gpa.allocator(); // Initialize TTY with buffer var buffer: [1024]u8 = undefined; var tty = try vaxis.Tty.init(&buffer); defer tty.deinit(); // Initialize Vaxis with options var vx = try vaxis.init(alloc, .{ .kitty_keyboard_flags = .{ .report_events = true }, }); defer vx.deinit(alloc, tty.writer()); // Create event loop var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx, }; try loop.init(); try loop.start(); defer loop.stop(); // Enter alternate screen and query terminal capabilities try vx.enterAltScreen(tty.writer()); try vx.queryTerminal(tty.writer(), 1 * std.time.ns_per_s); while (true) { const event = loop.nextEvent(); switch (event) { .key_press => |key| { if (key.matches('c', .{ .ctrl = true })) break; }, .winsize => |ws| try vx.resize(alloc, tty.writer(), ws), else => {}, } const win = vx.window(); win.clear(); try vx.render(tty.writer()); } } ``` -------------------------------- ### Integrate with System Clipboard and Notifications using Vaxis Source: https://context7.com/rockorager/libvaxis/llms.txt Demonstrates interaction with the system clipboard (copying and requesting content) and sending system notifications using OSC sequences. It also covers setting the window title and mouse cursor shape. Dependencies include the vaxis library. ```zig const std = @import("std"); const vaxis = @import("vaxis"); pub fn systemIntegration(vx: *vaxis.Vaxis, tty: *vaxis.Tty) !void { const writer = tty.writer(); // Copy to system clipboard using OSC 52 const text_to_copy = "Hello from Vaxis!"; try vx.copyToClipboard(writer, text_to_copy); // Request clipboard content (requires system_clipboard_allocator in Options) try vx.requestClipboard(writer); // Send system notification (OSC 9 or OSC 777) try vx.notify(writer, "App Title", "Notification message"); // Set window title try vx.setTitle(writer, "My Vaxis Application"); // Set mouse cursor shape try vx.setMouseShape(writer, .pointer); // Handle clipboard paste in event loop const Event = union(enum) { key_press: vaxis.Key, paste: []const u8, winsize: vaxis.Winsize, }; // In event handler: // .paste => |content| { // defer if (allocator) |a| a.free(content); // // Use pasted content // } } ``` -------------------------------- ### Zig-AIO Event Loop Initialization and Management Source: https://github.com/rockorager/libvaxis/blob/main/USAGE.md Initializes and manages an event loop using zig-aio. It sets up event sources, handles tasks for window resizing and TTY reading, and processes events. This function is generic and requires types for events, aio, and coroutines. ```zig const builtin = @import("builtin"); const std = @import("std"); const vaxis = @import("vaxis"); const handleEventGeneric = vaxis.loop.handleEventGeneric; const log = std.log.scoped(.vaxis_aio); const Yield = enum { no_state, took_event, }; /// zig-aio based event loop /// pub fn LoopWithModules(comptime T: type, comptime aio: type, comptime coro: type) type { return struct { const Event = T; winsize_task: ?coro.Task.Generic2(winsizeTask) = null, reader_task: ?coro.Task.Generic2(ttyReaderTask) = null, queue: std.BoundedArray(T, 512) = . {}, source: aio.EventSource, fatal: bool = false, pub fn init() !@This() { return .{ .source = try aio.EventSource.init() }; } pub fn deinit(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty) void { vx.deviceStatusReport(tty.writer()) catch {}; if (self.winsize_task) |task| task.cancel(); if (self.reader_task) |task| task.cancel(); self.source.deinit(); self.* = undefined; } fn winsizeInner(self: *@This(), tty: *vaxis.Tty) !void { const Context = struct { loop: *@TypeOf(self.*), tty: *vaxis.Tty, winsize: ?vaxis.Winsize = null, fn cb(ptr: *anyopaque) void { std.debug.assert(coro.current() == null); const ctx: *@This() = @ptrCast(@alignCast(ptr)); ctx.winsize = vaxis.Tty.getWinsize(ctx.tty.fd) catch return; ctx.loop.source.notify(); } }; // keep on stack var ctx: Context = .{ .loop = self, .tty = tty }; if (builtin.target.os.tag != .windows) { if (@hasField(Event, "winsize")) { const handler: vaxis.Tty.SignalHandler = .{ .context = &ctx, .callback = Context.cb }; try vaxis.Tty.notifyWinsize(handler); } } while (true) { try coro.io.single(aio.WaitEventSource{ .source = &self.source }); if (ctx.winsize) |winsize| { if (!@hasField(Event, "winsize")) unreachable; ctx.loop.postEvent(.{ .winsize = winsize }) catch {}; ctx.winsize = null; } } } fn winsizeTask(self: *@This(), tty: *vaxis.Tty) void { self.winsizeInner(tty) catch |err| { if (err != error.Canceled) log.err("winsize: {}", .{err}); self.fatal = true; }; } fn windowsReadEvent(tty: *vaxis.Tty) !vaxis.Event { var state: vaxis.Tty.EventState = .{}; while (true) { var bytes_read: usize = 0; var input_record: vaxis.Tty.INPUT_RECORD = undefined; try coro.io.single(aio.ReadTty{ .tty = .{ .handle = tty.stdin }, .buffer = std.mem.asBytes(&input_record), .out_read = &bytes_read, }); if (try tty.eventFromRecord(&input_record, &state)) |ev| { return ev; } } } fn ttyReaderWindows(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty) !void { var cache: vaxis.GraphemeCache = .{}; while (true) { const event = try windowsReadEvent(tty); try handleEventGeneric(self, vx, &cache, Event, event, null); } } fn ttyReaderPosix(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) !void { // initialize a grapheme cache var cache: vaxis.GraphemeCache = .{}; // get our initial winsize const winsize = try vaxis.Tty.getWinsize(tty.fd); if (@hasField(Event, "winsize")) { try self.postEvent(.{ .winsize = winsize }); } var parser: vaxis.Parser = .{ .grapheme_data = &vx.unicode.width_data.g_data, }; const file: std.fs.File = .{ .handle = tty.fd }; while (true) { var buf: [4096]u8 = undefined; var n: usize = undefined; var read_start: usize = 0; try coro.io.single(aio.ReadTty{ .tty = file, .buffer = buf[read_start..], .out_read = &n }); var seq_start: usize = 0; while (seq_start < n) { const result = try parser.parse(buf[seq_start..n], paste_allocator); if (result.n == 0) { ``` -------------------------------- ### Zig libxev TTY Event Loop Implementation Source: https://github.com/rockorager/libvaxis/blob/main/USAGE.md This Zig code implements an event loop for `libxev` to monitor TTY events. It sets up watchers for keyboard input, mouse events, and terminal window size changes. The watcher parses input sequences and dispatches them as specific `Event` types, including key presses, mouse movements, paste content, and color reports. It also handles asynchronous operations and callbacks. ```zig const std = @import("std"); const xev = @import("xev"); const Tty = @import("main.zig").Tty; const Winsize = @import("main.zig").Winsize; const Vaxis = @import("Vaxis.zig"); const Parser = @import("Parser.zig"); const Key = @import("Key.zig"); const Mouse = @import("Mouse.zig"); const Color = @import("Cell.zig").Color; const log = std.log.scoped(.vaxis_xev); pub const Event = union(enum) { key_press: Key, key_release: Key, mouse: Mouse, focus_in, focus_out, paste_start, // bracketed paste start paste_end, // bracketed paste end paste: []const u8, // osc 52 paste, caller must free color_report: Color.Report, // osc 4, 10, 11, 12 response color_scheme: Color.Scheme, winsize: Winsize, }; pub fn TtyWatcher(comptime Userdata: type) type { return struct { const Self = @This(); file: xev.File, tty: *Tty, read_buf: [4096]u8, read_buf_start: usize, read_cmp: xev.Completion, winsize_wakeup: xev.Async, winsize_cmp: xev.Completion, callback: *const fn ( ud: ?*Userdata, loop: *xev.Loop, watcher: *Self, event: Event, ) xev.CallbackAction, ud: ?*Userdata, vx: *Vaxis, parser: Parser, pub fn init( self: *Self, tty: *Tty, vaxis: *Vaxis, loop: *xev.Loop, userdata: ?*Userdata, callback: *const fn ( ud: ?*Userdata, loop: *xev.Loop, watcher: *Self, event: Event, ) xev.CallbackAction, ) !void { self.* = .{ // Using struct literal for initialization .tty = tty, .file = xev.File.initFd(tty.fd), .read_buf = undefined, .read_buf_start = 0, .read_cmp = .{}, .winsize_wakeup = try xev.Async.init(), .winsize_cmp = .{}, .callback = callback, .ud = userdata, .vx = vaxis, .parser = .{ .grapheme_data = &vaxis.unicode.width_data.g_data }, }; self.file.read( loop, &self.read_cmp, .{ .slice = &self.read_buf }, Self, self, Self.ttyReadCallback, ); self.winsize_wakeup.wait( loop, &self.winsize_cmp, Self, self, winsizeCallback, ); const handler: Tty.SignalHandler = .{ // Using struct literal for initialization .context = self, .callback = Self.signalCallback, }; try Tty.notifyWinsize(handler); } fn signalCallback(ptr: *anyopaque) void { const self: *Self = @ptrCast(@alignCast(ptr)); self.winsize_wakeup.notify() catch |err| { log.warn("couldn't wake up winsize callback: {s}", .{err}); }; } fn ttyReadCallback( ud: ?*Self, loop: *xev.Loop, c: *xev.Completion, _: xev.File, buf: xev.ReadBuffer, r: xev.ReadError!usize, ) xev.CallbackAction { const n = r catch |err| { log.err("read error: {s}", .{err}); return .disarm; }; const self = ud orelse unreachable; // reset read start state self.read_buf_start = 0; var seq_start: usize = 0; parse_loop: while (seq_start < n) { const result = self.parser.parse(buf.slice[seq_start..n], null) catch |err| { log.err("couldn't parse input: {s}", .{err}); return .disarm; }; if (result.n == 0) { // copy the read to the beginning. We don't use memcpy because // this could be overlapping, and it's also rare const initial_start = seq_start; while (seq_start < n) : (seq_start += 1) { self.read_buf[seq_start - initial_start] = self.read_buf[seq_start]; } self.read_buf_start = seq_start - initial_start + 1; return .rearm; } seq_start += n; const event_inner = result.event orelse { log.debug("unknown event: {s}", .{self.read_buf[seq_start - n + 1 .. seq_start]}); continue :parse_loop; }; // Capture events we want to bubble up const event: ?Event = switch (event_inner) { ``` -------------------------------- ### Handle Window Resize Events in Vaxis Source: https://github.com/rockorager/libvaxis/blob/main/USAGE.md Callback function to handle window resize events. It retrieves the new window size, updates the terminal state, and schedules a subsequent resize event using `winsize_wakeup`. Handles potential asynchronous errors during event processing. ```zig fn winsizeCallback( ud: ?*Self, l: *xev.Loop, c: *xev.Completion, r: xev.Async.WaitError!void, ) xev.CallbackAction { _ = r catch |err| { log.err("async error: {}", .{err}); return .disarm; }; const self = ud orelse unreachable; // no userdata const winsize = Tty.getWinsize(self.tty.fd) catch |err| { log.err("couldn't get winsize: {}", .{err}); return .disarm; }; const ret = self.callback(self.ud, l, self, .{ .winsize = winsize }); if (ret == .disarm) return .disarm; self.winsize_wakeup.wait( l, c, Self, self, winsizeCallback, ); return .disarm; } }; ``` -------------------------------- ### Handle Mouse and Keyboard Events with Vaxis Source: https://context7.com/rockorager/libvaxis/llms.txt Enables advanced mouse and keyboard event handling, including mouse mode activation, event looping, and specific key/mouse action detection. It also handles window resize events. Dependencies include the vaxis library. ```zig const std = @import("std"); const vaxis = @import("vaxis"); pub fn advancedEventHandling(vx: *vaxis.Vaxis, tty: *vaxis.Tty) !void { // Enable mouse mode try vx.setMouseMode(tty.writer(), true); const Event = union(enum) { key_press: vaxis.Key, mouse: vaxis.Mouse, winsize: vaxis.Winsize, }; var loop: vaxis.Loop(Event) = .{ .tty = tty, .vaxis = vx, }; try loop.init(); try loop.start(); defer loop.stop(); while (true) { const event = loop.nextEvent(); switch (event) { .key_press => |key| { // Check for specific key combinations if (key.matches('q', .{ .ctrl = true })) { break; } else if (key.matches(vaxis.Key.up, .{})) { // Handle arrow key } else if (key.matches('s', .{ .ctrl = true, .shift = true })) { // Handle Ctrl+Shift+S } }, .mouse => |mouse| { switch (mouse.type) { .press => |button| { // Handle mouse press at mouse.col, mouse.row if (button == .left) { std.debug.print("Left click at {d},{d}\n", .{mouse.col, mouse.row}); } }, .release => |button| { // Handle mouse release }, .motion => { // Handle mouse motion }, .scroll_up, .scroll_down => { // Handle scroll events }, else => {}, } }, .winsize => |ws| { try vx.resize(vx.screen.buf.allocator, tty.writer(), ws); }, } } } ``` -------------------------------- ### Add libvaxis Dependency to Zig Project Source: https://github.com/rockorager/libvaxis/blob/main/README.md Demonstrates how to add the libvaxis library as a dependency to a Zig project. This involves fetching the library and configuring the build.zig file to include it as a module. This is essential for using libvaxis in your Zig applications. ```console zig fetch --save git+https://github.com/rockorager/libvaxis.git ``` ```zig const vaxis = b.dependency("vaxis", .{ .target = target, .optimize = optimize, }); exe.root_module.addImport("vaxis", vaxis.module("vaxis")); ``` ```zig // create module const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // add vaxis dependency to module const vaxis = b.dependency("vaxis", .{ .target = target, .optimize = optimize, }); exe_mod.addImport("vaxis", vaxis.module("vaxis")); //create executable const exe = b.addExecutable(.{ .name = "project_foo", .root_module = exe_mod, }); // install exe below ``` -------------------------------- ### Configure build.zig for libvaxis Source: https://context7.com/rockorager/libvaxis/llms.txt This Zig code snippet demonstrates how to include libvaxis as a dependency in your Zig project's build.zig file, making its modules available for your application. ```zig const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const vaxis = b.dependency("vaxis", .{ .target = target, .optimize = optimize, }); const exe = b.addExecutable(.{ .name = "myapp", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); exe.root_module.addImport("vaxis", vaxis.module("vaxis")); b.installArtifact(exe); } ``` -------------------------------- ### Display Image in Terminal with Kitty Graphics (Zig) Source: https://context7.com/rockorager/libvaxis/llms.txt Renders a PNG image file to the terminal using the Kitty Graphics Protocol via the libvaxis library. This function loads an image, creates a vaxis image representation, and displays it within a child window of the main vaxis window. It requires the 'vaxis' and 'zigimg' libraries. ```zig const std = @import("std"); const vaxis = @import("vaxis"); const zigimg = vaxis.zigimg; pub fn displayImage(vx: *vaxis.Vaxis, alloc: std.mem.Allocator) !void { // Load an image file var img_file = try std.fs.cwd().openFile("image.png", .{}); defer img_file.close(); var img = try zigimg.Image.fromFile(alloc, &img_file); defer img.deinit(); // Create vaxis image var vaxis_img = try vaxis.Image.init(alloc, img, .{ .id = vx.nextImageId(), }); defer vaxis_img.deinit(alloc); // Get window and display image const win = vx.window(); // Create child window for image const img_win = win.child(.{ .x_off = 5, .y_off = 2, .width = 40, .height = 20, }); // Draw the image try img_win.image(&vaxis_img); } ``` -------------------------------- ### Draw UI Elements in Vaxis with Styling in Zig Source: https://context7.com/rockorager/libvaxis/llms.txt This Zig code snippet illustrates how to create and draw styled text segments and a bordered child window within a Vaxis application. It demonstrates setting foreground and background colors, bold text, and defining border styles for child windows. ```zig const std = @import("std"); const vaxis = @import("vaxis"); pub fn drawUI(vx: *vaxis.Vaxis) !void { const win = vx.window(); win.clear(); // Create a styled segment const style: vaxis.Style = .{ .fg = .{ .rgb = [3]u8{ 255, 100, 50 } }, .bg = .{ .index = 236 }, .bold = true, }; // Draw text with style const segment: vaxis.Segment = .{ .text = "Hello, Vaxis!", .style = style, }; _ = try win.print(&[_]vaxis.Segment{segment}, .{.row_offset = 0, .col_offset = 0}); // Create a bordered child window const child = win.child(.{ .x_off = 10, .y_off = 5, .width = 40, .height = 10, .border = .{ .where = .all, .style = .{ .fg = .{ .index = 12 } }, .glyphs = .single_rounded, }, }); // Fill child window with content const text: vaxis.Segment = .{ .text = "Child window content" }; _ = try child.print(&[_]vaxis.Segment{text}, .{ .row_offset = 1, .col_offset = 2 }); } ``` -------------------------------- ### Allocate Button and Text Widgets using Arena Source: https://github.com/rockorager/libvaxis/blob/main/README.md This code demonstrates allocating memory for button and text widgets using an arena allocator. The arena is suitable for short-lived allocations, such as those needed within a single frame. The allocated widgets are then assigned to a slice for subsequent use. ```zig const children = try ctx.arena.alloc(vxfw.SubSurface, 2); children[0] = text_child; children[1] = button_child; ``` -------------------------------- ### Zig Vaxis Framework Button Counter Application Source: https://github.com/rockorager/libvaxis/blob/main/README.md This Zig code implements a simple button counter application using the vaxis.vxfw framework. It demonstrates stateful widgets, event handling for clicks and key presses, and custom drawing logic. The application supports mouse hover and click effects, and cancellation of press events if the release occurs outside the button. Dependencies include the standard library and the vaxis framework. It takes no explicit input but responds to user interactions. ```zig const std = @import("std"); const vaxis = @import("vaxis"); const vxfw = vaxis.vxfw; /// Our main application state const Model = struct { /// State of the counter count: u32 = 0, /// The button. This widget is stateful and must live between frames button: vxfw.Button, /// Helper function to return a vxfw.Widget struct pub fn widget(self: *Model) vxfw.Widget { return .{ .userdata = self, .eventHandler = Model.typeErasedEventHandler, .drawFn = Model.typeErasedDrawFn, }; } /// This function will be called from the vxfw runtime. fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { const self: *Model = @ptrCast(@alignCast(ptr)); switch (event) { // The root widget is always sent an init event as the first event. Users of the // library can also send this event to other widgets they create if they need to do // some initialization. .init => return ctx.requestFocus(self.button.widget()), .key_press => |key| { if (key.matches('c', .{ .ctrl = true })) { ctx.quit = true; return; } }, // We can request a specific widget gets focus. In this case, we always want to focus // our button. Having focus means that key events will be sent up the widget tree to // the focused widget, and then bubble back down the tree to the root. Users can tell // the runtime the event was handled and the capture or bubble phase will stop .focus_in => return ctx.requestFocus(self.button.widget()), else => {}, } } /// This function is called from the vxfw runtime. It will be called on a regular interval, and /// only when any event handler has marked the redraw flag in EventContext as true. By /// explicitly requiring setting the redraw flag, vxfw can prevent excessive redraws for events /// which don't change state (ie mouse motion, unhandled key events, etc) fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface { const self: *Model = @ptrCast(@alignCast(ptr)); // The DrawContext is inspired from Flutter. Each widget will receive a minimum and maximum // constraint. The minimum constraint will always be set, even if it is set to 0x0. The // maximum constraint can have null width and/or height - meaning there is no constraint in // that direction and the widget should take up as much space as it needs. By calling size() // on the max, we assert that it has some constrained size. This is *always* the case for // the root widget - the maximum size will always be the size of the terminal screen. const max_size = ctx.max.size(); // The DrawContext also contains an arena allocator that can be used for each frame. The // lifetime of this allocation is until the next time we draw a frame. This is useful for // temporary allocations such as the one below: we have an integer we want to print as text. // We can safely allocate this with the ctx arena since we only need it for this frame. const count_text = try std.fmt.allocPrint(ctx.arena, "{d}", .{self.count}); const text: vxfw.Text = .{ .text = count_text }; // Each widget returns a Surface from its draw function. A Surface contains the rectangular // area of the widget, as well as some information about the surface or widget: can we focus // it? does it handle the mouse? // // It DOES NOT contain the location it should be within its parent. Only the parent can set // this via a SubSurface. Here, we will return a Surface for the root widget (Model), which // has two SubSurfaces: one for the text and one for the button. A SubSurface is a Surface // with an offset and a z-index - the offset can be negative. This lets a parent draw a // child and place it within itself const text_child: vxfw.SubSurface = .{ .origin = .{ .row = 0, .col = 0 }, .surface = try text.draw(ctx), }; const button_child: vxfw.SubSurface = .{ .origin = .{ .row = 2, .col = 0 }, .surface = try self.button.draw(ctx.withConstraints( ctx.min, ``` -------------------------------- ### Vaxis Event Queue Management Source: https://github.com/rockorager/libvaxis/blob/main/USAGE.md This section details the methods for managing the event queue in the Vaxis library. It includes functions to pop events from the queue, handling potential communication errors, and posting new events to the queue, with mechanisms to wake up the scheduler when necessary. Dependencies include the 'coro' module. ```zig pub const PopEventError = error{TtyCommunicationSevered}; /// Call this in a while loop in the main event handler until it returns null pub fn popEvent(self: *@This()) PopEventError!?T { if (self.fatal) return error.TtyCommunicationSevered; defer self.winsize_task.?.wakeupIf(Yield.took_event); defer self.reader_task.?.wakeupIf(Yield.took_event); return self.queue.popOrNull(); } pub const PostEventError = error{Overflow}; pub fn postEvent(self: *@This(), event: T) !void { if (coro.current()) |_| { while (true) { self.queue.insert(0, event) catch { // wait for the app to take event try coro.yield(Yield.took_event); continue; }; break; } } else { // queue can be full, app could handle this error by spinning the scheduler try self.queue.insert(0, event); } // wakes up the scheduler, so custom events update UI self.source.notify(); } ```