a modern tui library written in zig
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}