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