a modern tui library written in zig
at v0.1.0 128 lines 5.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 EventType. 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 // Initialize Vaxis with our event type 33 var vx = try vaxis.init(Event, .{}); 34 // deinit takes an optional allocator. If your program is exiting, you can 35 // choose to pass a null allocator to save some exit time. 36 defer vx.deinit(alloc); 37 38 // Start the read loop. This puts the terminal in raw mode and begins 39 // reading user input 40 try vx.startReadThread(); 41 defer vx.stopReadThread(); 42 43 // Optionally enter the alternate screen 44 try vx.enterAltScreen(); 45 46 // We'll adjust the color index every keypress for the border 47 var color_idx: u8 = 0; 48 49 // init our text input widget. The text input widget needs an allocator to 50 // store the contents of the input 51 var text_input = TextInput.init(alloc); 52 defer text_input.deinit(); 53 54 // Sends queries to terminal to detect certain features. This should 55 // _always_ be called, but is left to the application to decide when 56 try vx.queryTerminal(); 57 58 try vx.setMouseMode(true); 59 60 // The main event loop. Vaxis provides a thread safe, blocking, buffered 61 // queue which can serve as the primary event queue for an application 62 outer: while (true) { 63 // nextEvent blocks until an event is in the queue 64 const event = vx.nextEvent(); 65 log.debug("event: {}\r\n", .{event}); 66 // exhaustive switching ftw. Vaxis will send events if your EventType 67 // enum has the fields for those events (ie "key_press", "winsize") 68 switch (event) { 69 .key_press => |key| { 70 color_idx = switch (color_idx) { 71 255 => 0, 72 else => color_idx + 1, 73 }; 74 if (key.matches('c', .{ .ctrl = true })) { 75 break :outer; 76 } else if (key.matches('l', .{ .ctrl = true })) { 77 vx.queueRefresh(); 78 } else if (key.matches('n', .{ .ctrl = true })) { 79 try vx.notify("vaxis", "hello from vaxis"); 80 } else { 81 try text_input.update(.{ .key_press = key }); 82 } 83 }, 84 85 // winsize events are sent to the application to ensure that all 86 // resizes occur in the main thread. This lets us avoid expensive 87 // locks on the screen. All applications must handle this event 88 // unless they aren't using a screen (IE only detecting features) 89 // 90 // This is the only call that the core of Vaxis needs an allocator 91 // for. The allocations are because we keep a copy of each cell to 92 // optimize renders. When resize is called, we allocated two slices: 93 // one for the screen, and one for our buffered screen. Each cell in 94 // the buffered screen contains an ArrayList(u8) to be able to store 95 // the grapheme for that cell Each cell is initialized with a size 96 // of 1, which is sufficient for all of ASCII. Anything requiring 97 // more than one byte will incur an allocation on the first render 98 // after it is drawn. Thereafter, it will not allocate unless the 99 // screen is resized 100 .winsize => |ws| try vx.resize(alloc, ws), 101 else => {}, 102 } 103 104 // vx.window() returns the root window. This window is the size of the 105 // terminal and can spawn child windows as logical areas. Child windows 106 // cannot draw outside of their bounds 107 const win = vx.window(); 108 109 // Clear the entire space because we are drawing in immediate mode. 110 // vaxis double buffers the screen. This new frame will be compared to 111 // the old and only updated cells will be drawn 112 win.clear(); 113 const child = win.initChild( 114 win.width / 2 - 20, 115 win.height / 2 - 3, 116 .{ .limit = 40 }, 117 .{ .limit = 3 }, 118 ); 119 // draw the text_input using a bordered window 120 const style: vaxis.Style = .{ 121 .fg = .{ .index = color_idx }, 122 }; 123 text_input.draw(border.all(child, style)); 124 125 // Render the screen 126 try vx.render(); 127 } 128}