a modern tui library written in zig
1const std = @import("std");
2const fmt = std.fmt;
3const heap = std.heap;
4const mem = std.mem;
5const meta = std.meta;
6
7const vaxis = @import("vaxis");
8
9const log = std.log.scoped(.main);
10
11const ActiveSection = enum {
12 top,
13 mid,
14 btm,
15};
16
17pub fn main() !void {
18 var gpa = heap.GeneralPurposeAllocator(.{}){};
19 defer if (gpa.detectLeaks()) log.err("Memory leak detected!", .{});
20 const alloc = gpa.allocator();
21
22 // Users set up below the main function
23 const users_buf = try alloc.dupe(User, users[0..]);
24 const user_list = std.ArrayList(User).fromOwnedSlice(alloc, users_buf);
25 defer user_list.deinit();
26
27 var tty = try vaxis.Tty.init();
28 defer tty.deinit();
29
30 var vx = try vaxis.init(alloc, .{});
31 defer vx.deinit(alloc, tty.anyWriter());
32
33 var loop: vaxis.Loop(union(enum) {
34 key_press: vaxis.Key,
35 winsize: vaxis.Winsize,
36 }) = .{ .tty = &tty, .vaxis = &vx };
37
38 try loop.start();
39 defer loop.stop();
40 try vx.enterAltScreen(tty.anyWriter());
41 try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s);
42
43 const logo =
44 \\░█░█░█▀█░█░█░▀█▀░█▀▀░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░
45 \\░▀▄▀░█▀█░▄▀▄░░█░░▀▀█░░░░█░░█▀█░█▀▄░█░░░█▀▀░
46 \\░░▀░░▀░▀░▀░▀░▀▀▀░▀▀▀░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░
47 ;
48 const title_logo = vaxis.Cell.Segment{
49 .text = logo,
50 .style = .{},
51 };
52 const title_info = vaxis.Cell.Segment{
53 .text = "===A Demo of the the Vaxis Table Widget!===",
54 .style = .{},
55 };
56 const title_disclaimer = vaxis.Cell.Segment{
57 .text = "(All data is non-sensical & LLM generated.)",
58 .style = .{},
59 };
60 var title_segs = [_]vaxis.Cell.Segment{ title_logo, title_info, title_disclaimer };
61
62 var cmd_input = vaxis.widgets.TextInput.init(alloc, &vx.unicode);
63 defer cmd_input.deinit();
64
65 // Colors
66 const selected_bg: vaxis.Cell.Color = .{ .rgb = .{ 64, 128, 255 } };
67 const other_bg: vaxis.Cell.Color = .{ .rgb = .{ 32, 32, 48 } };
68
69 // Table Context
70 var demo_tbl: vaxis.widgets.Table.TableContext = .{ .selected_bg = selected_bg };
71
72 // TUI State
73 var active: ActiveSection = .mid;
74 var moving = false;
75
76 while (true) {
77 // Create an Arena Allocator for easy allocations on each Event.
78 var event_arena = heap.ArenaAllocator.init(alloc);
79 defer event_arena.deinit();
80 const event_alloc = event_arena.allocator();
81 const event = loop.nextEvent();
82
83 switch (event) {
84 .key_press => |key| keyEvt: {
85 // Close the Program
86 if (key.matches('c', .{ .ctrl = true })) {
87 break;
88 }
89 // Refresh the Screen
90 if (key.matches('l', .{ .ctrl = true })) {
91 vx.queueRefresh();
92 break :keyEvt;
93 }
94 // Enter Moving State
95 if (key.matches('w', .{ .ctrl = true })) {
96 moving = !moving;
97 break :keyEvt;
98 }
99 // Command State
100 if (active != .btm and
101 key.matchesAny(&.{ ':', '/', 'g', 'G' }, .{}))
102 {
103 active = .btm;
104 for (0..cmd_input.buf.items.len) |_| _ = cmd_input.buf.orderedRemove(0);
105 try cmd_input.update(.{ .key_press = key });
106 cmd_input.cursor_idx = 1;
107 break :keyEvt;
108 }
109
110 switch (active) {
111 .top => {
112 if (key.matchesAny(&.{ vaxis.Key.down, 'j' }, .{}) and moving) active = .mid;
113 },
114 .mid => midEvt: {
115 if (moving) {
116 if (key.matchesAny(&.{ vaxis.Key.up, 'k' }, .{})) active = .top;
117 if (key.matchesAny(&.{ vaxis.Key.down, 'j' }, .{})) active = .btm;
118 break :midEvt;
119 }
120 // Change Row
121 if (key.matchesAny(&.{ vaxis.Key.up, 'k' }, .{})) demo_tbl.row -|= 1;
122 if (key.matchesAny(&.{ vaxis.Key.down, 'j' }, .{})) demo_tbl.row +|= 1;
123 // Change Column
124 if (key.matchesAny(&.{ vaxis.Key.left, 'h' }, .{})) demo_tbl.col -|= 1;
125 if (key.matchesAny(&.{ vaxis.Key.right, 'l' }, .{})) demo_tbl.col +|= 1;
126 },
127 .btm => {
128 if (key.matchesAny(&.{ vaxis.Key.up, 'k' }, .{}) and moving) active = .mid
129 // Run Command and Clear Command Bar
130 else if (key.matchExact(vaxis.Key.enter, .{})) {
131 const cmd = cmd_input.buf.items;
132 if (mem.eql(u8, ":q", cmd) or
133 mem.eql(u8, ":quit", cmd) or
134 mem.eql(u8, ":exit", cmd)) return;
135 if (mem.eql(u8, "G", cmd)) {
136 demo_tbl.row = user_list.items.len - 1;
137 active = .mid;
138 }
139 if (cmd.len >= 2 and mem.eql(u8, "gg", cmd[0..2])) {
140 const goto_row = fmt.parseInt(usize, cmd[2..], 0) catch 0;
141 demo_tbl.row = goto_row;
142 active = .mid;
143 }
144 for (0..cmd_input.buf.items.len) |_| _ = cmd_input.buf.orderedRemove(0);
145 cmd_input.cursor_idx = 0;
146 } else try cmd_input.update(.{ .key_press = key });
147 },
148 }
149 moving = false;
150 },
151 .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws),
152 //else => {},
153 }
154
155 // Sections
156 // - Window
157 const win = vx.window();
158 win.clear();
159
160 // - Top
161 const top_div = 6;
162 const top_bar = win.initChild(
163 0,
164 0,
165 .{ .limit = win.width },
166 .{ .limit = win.height / top_div },
167 );
168 for (title_segs[0..]) |*title_seg|
169 title_seg.*.style.bg = if (active == .top) selected_bg else other_bg;
170 top_bar.fill(.{ .style = .{
171 .bg = if (active == .top) selected_bg else other_bg,
172 } });
173 const logo_bar = vaxis.widgets.alignment.center(
174 top_bar,
175 44,
176 top_bar.height - (top_bar.height / 3),
177 );
178 _ = try logo_bar.print(title_segs[0..], .{ .wrap = .word });
179
180 // - Middle
181 const middle_bar = win.initChild(
182 0,
183 win.height / top_div,
184 .{ .limit = win.width },
185 .{ .limit = win.height - (top_bar.height + 1) },
186 );
187 if (user_list.items.len > 0) {
188 demo_tbl.active = active == .mid;
189 try vaxis.widgets.Table.drawTable(
190 event_alloc,
191 middle_bar,
192 &.{ "First", "Last", "Username", "Email", "Phone#" },
193 user_list,
194 &demo_tbl,
195 );
196 }
197
198 // - Bottom
199 const bottom_bar = win.initChild(
200 0,
201 win.height - 1,
202 .{ .limit = win.width },
203 .{ .limit = 1 },
204 );
205 if (active == .btm) bottom_bar.fill(.{ .style = .{ .bg = selected_bg } });
206 cmd_input.draw(bottom_bar);
207
208 // Render the screen
209 try vx.render(tty.anyWriter());
210 }
211}
212
213/// User Struct
214pub const User = struct {
215 first: []const u8,
216 last: []const u8,
217 user: []const u8,
218 email: ?[]const u8 = null,
219 phone: ?[]const u8 = null,
220};
221
222// Users Array
223const users = [_]User{
224 .{ .first = "Nancy", .last = "Dudley", .user = "angela73", .email = "brian47@rodriguez.biz", .phone = null },
225 .{ .first = "Emily", .last = "Thornton", .user = "mrogers", .email = null, .phone = "(558)888-8604x094" },
226 .{ .first = "Kyle", .last = "Huff", .user = "xsmith", .email = null, .phone = "301.127.0801x12398" },
227 .{ .first = "Christine", .last = "Dodson", .user = "amandabradley", .email = "cheryl21@sullivan.com", .phone = null },
228 .{ .first = "Nathaniel", .last = "Kennedy", .user = "nrobinson", .email = null, .phone = null },
229 .{ .first = "Laura", .last = "Leon", .user = "dawnjones", .email = "fjenkins@patel.com", .phone = "1833013180" },
230 .{ .first = "Patrick", .last = "Landry", .user = "michaelhutchinson", .email = "daniel17@medina-wallace.net", .phone = "+1-634-486-6444x964" },
231 .{ .first = "Tammy", .last = "Hall", .user = "jamessmith", .email = null, .phone = "(926)810-3385x22059" },
232 .{ .first = "Stephanie", .last = "Anderson", .user = "wgillespie", .email = "campbelljaime@yahoo.com", .phone = null },
233 .{ .first = "Jennifer", .last = "Williams", .user = "shawn60", .email = null, .phone = "611-385-4771x97523" },
234 .{ .first = "Elizabeth", .last = "Ortiz", .user = "jennifer76", .email = "johnbradley@delgado.info", .phone = null },
235 .{ .first = "Stacy", .last = "Mays", .user = "scottgonzalez", .email = "kramermatthew@gmail.com", .phone = null },
236 .{ .first = "Jennifer", .last = "Smith", .user = "joseph75", .email = "masseyalexander@hill-moore.net", .phone = null },
237 .{ .first = "Gary", .last = "Hammond", .user = "brittany26", .email = null, .phone = null },
238 .{ .first = "Lisa", .last = "Johnson", .user = "tina28", .email = null, .phone = "850-606-2978x1081" },
239 .{ .first = "Zachary", .last = "Hopkins", .user = "vargasmichael", .email = null, .phone = null },
240 .{ .first = "Joshua", .last = "Kidd", .user = "ghanna", .email = "jbrown@yahoo.com", .phone = null },
241 .{ .first = "Dawn", .last = "Jones", .user = "alisonlindsey", .email = null, .phone = null },
242 .{ .first = "Monica", .last = "Berry", .user = "barbara40", .email = "michael00@hotmail.com", .phone = "(295)346-6453x343" },
243 .{ .first = "Shannon", .last = "Roberts", .user = "krystal37", .email = null, .phone = "980-920-9386x454" },
244 .{ .first = "Thomas", .last = "Mitchell", .user = "williamscorey", .email = "richardduncan@roberts.com", .phone = null },
245 .{ .first = "Nicole", .last = "Shaffer", .user = "rogerstroy", .email = null, .phone = "(570)128-5662" },
246 .{ .first = "Edward", .last = "Bennett", .user = "andersonchristina", .email = null, .phone = null },
247 .{ .first = "Duane", .last = "Howard", .user = "pcarpenter", .email = "griffithwayne@parker.net", .phone = null },
248 .{ .first = "Mary", .last = "Brown", .user = "kimberlyfrost", .email = "perezsara@anderson-andrews.net", .phone = null },
249 .{ .first = "Pamela", .last = "Sloan", .user = "kvelez", .email = "huynhlacey@moore-bell.biz", .phone = "001-359-125-1393x8716" },
250 .{ .first = "Timothy", .last = "Charles", .user = "anthony04", .email = "morrissara@hawkins.info", .phone = "+1-619-369-9572" },
251 .{ .first = "Sydney", .last = "Torres", .user = "scott42", .email = "asnyder@mitchell.net", .phone = null },
252 .{ .first = "John", .last = "Jones", .user = "anthonymoore", .email = null, .phone = "701.236.0571x99622" },
253 .{ .first = "Erik", .last = "Johnson", .user = "allisonsanders", .email = null, .phone = null },
254 .{ .first = "Donna", .last = "Kirk", .user = "laurie81", .email = null, .phone = null },
255 .{ .first = "Karina", .last = "White", .user = "uperez", .email = null, .phone = null },
256 .{ .first = "Jesse", .last = "Schwartz", .user = "ryan60", .email = "latoyawilliams@gmail.com", .phone = null },
257 .{ .first = "Cindy", .last = "Romero", .user = "christopher78", .email = "faulknerchristina@gmail.com", .phone = "780.288.2319x583" },
258 .{ .first = "Tyler", .last = "Sanders", .user = "bennettjessica", .email = null, .phone = "1966269423" },
259 .{ .first = "Pamela", .last = "Carter", .user = "zsnyder", .email = null, .phone = "125-062-9130x58413" },
260};