a modern tui library written in zig
at main 6.1 kB view raw
1const std = @import("std"); 2const vaxis = @import("vaxis"); 3const Cell = vaxis.Cell; 4const TextInput = vaxis.widgets.TextInput; 5const border = vaxis.widgets.border; 6 7const log = std.log.scoped(.main); 8 9// Our Event. This can contain internal events as well as Vaxis events. 10// Internal events can be posted into the same queue as vaxis events to allow 11// for a single event loop with exhaustive switching. Booya 12const Event = union(enum) { 13 key_press: vaxis.Key, 14 mouse: vaxis.Mouse, 15 winsize: vaxis.Winsize, 16 focus_in, 17 focus_out, 18 foo: u8, 19}; 20 21pub fn main() !void { 22 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 23 defer { 24 const deinit_status = gpa.deinit(); 25 //fail test; can't try in defer as defer is executed after we return 26 if (deinit_status == .leak) { 27 log.err("memory leak", .{}); 28 } 29 } 30 const alloc = gpa.allocator(); 31 32 // Initalize a tty 33 var buffer: [1024]u8 = undefined; 34 var tty = try vaxis.Tty.init(&buffer); 35 defer tty.deinit(); 36 37 // Use a buffered writer for better performance. There are a lot of writes 38 // in the render loop and this can have a significant savings 39 const writer = tty.writer(); 40 41 // Initialize Vaxis 42 var vx = try vaxis.init(alloc, .{ 43 .kitty_keyboard_flags = .{ .report_events = true }, 44 }); 45 defer vx.deinit(alloc, tty.writer()); 46 47 var loop: vaxis.Loop(Event) = .{ 48 .vaxis = &vx, 49 .tty = &tty, 50 }; 51 try loop.init(); 52 53 // Start the read loop. This puts the terminal in raw mode and begins 54 // reading user input 55 try loop.start(); 56 defer loop.stop(); 57 58 // Optionally enter the alternate screen 59 try vx.enterAltScreen(writer); 60 61 // We'll adjust the color index every keypress for the border 62 var color_idx: u8 = 0; 63 64 // init our text input widget. The text input widget needs an allocator to 65 // store the contents of the input 66 var text_input = TextInput.init(alloc); 67 defer text_input.deinit(); 68 69 try vx.setMouseMode(writer, true); 70 71 try writer.flush(); 72 // Sends queries to terminal to detect certain features. This should 73 // _always_ be called, but is left to the application to decide when 74 try vx.queryTerminal(tty.writer(), 1 * std.time.ns_per_s); 75 76 // The main event loop. Vaxis provides a thread safe, blocking, buffered 77 // queue which can serve as the primary event queue for an application 78 while (true) { 79 // nextEvent blocks until an event is in the queue 80 const event = loop.nextEvent(); 81 log.debug("event: {}", .{event}); 82 // exhaustive switching ftw. Vaxis will send events if your Event 83 // enum has the fields for those events (ie "key_press", "winsize") 84 switch (event) { 85 .key_press => |key| { 86 color_idx = switch (color_idx) { 87 255 => 0, 88 else => color_idx + 1, 89 }; 90 if (key.matches('c', .{ .ctrl = true })) { 91 break; 92 } else if (key.matches('l', .{ .ctrl = true })) { 93 vx.queueRefresh(); 94 } else if (key.matches('n', .{ .ctrl = true })) { 95 try vx.notify(tty.writer(), "vaxis", "hello from vaxis"); 96 loop.stop(); 97 var child = std.process.Child.init(&.{"nvim"}, alloc); 98 _ = try child.spawnAndWait(); 99 try loop.start(); 100 try vx.enterAltScreen(tty.writer()); 101 vx.queueRefresh(); 102 } else if (key.matches(vaxis.Key.enter, .{}) or key.matches('j', .{ .ctrl = true })) { 103 text_input.clearAndFree(); 104 } else { 105 try text_input.update(.{ .key_press = key }); 106 } 107 }, 108 109 // winsize events are sent to the application to ensure that all 110 // resizes occur in the main thread. This lets us avoid expensive 111 // locks on the screen. All applications must handle this event 112 // unless they aren't using a screen (IE only detecting features) 113 // 114 // This is the only call that the core of Vaxis needs an allocator 115 // for. The allocations are because we keep a copy of each cell to 116 // optimize renders. When resize is called, we allocated two slices: 117 // one for the screen, and one for our buffered screen. Each cell in 118 // the buffered screen contains an ArrayList(u8) to be able to store 119 // the grapheme for that cell Each cell is initialized with a size 120 // of 1, which is sufficient for all of ASCII. Anything requiring 121 // more than one byte will incur an allocation on the first render 122 // after it is drawn. Thereafter, it will not allocate unless the 123 // screen is resized 124 .winsize => |ws| try vx.resize(alloc, tty.writer(), ws), 125 else => {}, 126 } 127 128 // vx.window() returns the root window. This window is the size of the 129 // terminal and can spawn child windows as logical areas. Child windows 130 // cannot draw outside of their bounds 131 const win = vx.window(); 132 133 // Clear the entire space because we are drawing in immediate mode. 134 // vaxis double buffers the screen. This new frame will be compared to 135 // the old and only updated cells will be drawn 136 win.clear(); 137 // draw the text_input using a bordered window 138 const style: vaxis.Style = .{ 139 .fg = .{ .index = color_idx }, 140 }; 141 const child = win.child(.{ 142 .x_off = win.width / 2 - 20, 143 .y_off = win.height / 2 - 3, 144 .width = 40, 145 .height = 3, 146 .border = .{ 147 .where = .all, 148 .style = style, 149 }, 150 }); 151 text_input.draw(child); 152 153 // Render the screen 154 try vx.render(writer); 155 try writer.flush(); 156 } 157}