···11+# libvaxis
22+33+```
44+It begins with them, but ends with me. Their son, Vaxis
55+```
66+77+libvaxis is a zig port of the go TUI library
88+[Vaxis](https://git.sr.ht/~rockorager/vaxis). The goal is to have the same
99+feature set, only written in zig.
1010+1111+Like it's sibling library, libvaxis _does not use terminfo_. Support for vt
1212+features is detected through terminal queries.
1313+1414+Contributions are welcome.
1515+1616+## Feature comparison
1717+1818+| Feature | Vaxis | libvaxis | notcurses |
1919+| ------------------------------ | :---: | :------: | :-------: |
2020+| RGB | ✅ | ✅ | ✅ |
2121+| Hyperlinks | ✅ | planned | ❌ |
2222+| Bracketed Paste | ✅ | planned | ❌ |
2323+| Kitty Keyboard | ✅ | ✅ | ✅ |
2424+| Styled Underlines | ✅ | ✅ | ✅ |
2525+| Mouse Shapes (OSC 22) | ✅ | planned | ❌ |
2626+| System Clipboard (OSC 52) | ✅ | planned | ❌ |
2727+| System Notifications (OSC 9) | ✅ | planned | ❌ |
2828+| System Notifications (OSC 777) | ✅ | planned | ❌ |
2929+| Synchronized Output (DEC 2026) | ✅ | ✅ | ✅ |
3030+| Unicode Core (DEC 2027) | ✅ | ✅ | ❌ |
3131+| Color Mode Updates (DEC 2031) | ✅ | planned | ❌ |
3232+| Images (full/space) | ✅ | planned | ✅ |
3333+| Images (half block) | ✅ | planned | ✅ |
3434+| Images (quadrant) | ✅ | planned | ✅ |
3535+| Images (sextant) | ❌ | ❌ | ✅ |
3636+| Images (sixel) | ✅ | planned | ✅ |
3737+| Images (kitty) | ✅ | planned | ✅ |
3838+| Images (iterm2) | ❌ | ❌ | ✅ |
3939+| Video | ❌ | ❌ | ✅ |
4040+| Dank | 🆗 | 🆗 | ✅ |
4141+4242+## Usage
4343+4444+The below example can be run using `zig build run 2>log`. stderr must be
4545+redirected in order to not print to the same screen.
4646+4747+```zig
4848+const std = @import("std");
4949+const vaxis = @import("vaxis");
5050+const Cell = vaxis.Cell;
5151+const TextInput = vaxis.widgets.TextInput;
5252+const border = vaxis.widgets.border;
5353+5454+const log = std.log.scoped(.main);
5555+5656+// Our EventType. This can contain internal events as well as Vaxis events.
5757+// Internal events can be posted into the same queue as vaxis events to allow
5858+// for a single event loop with exhaustive switching. Booya
5959+const Event = union(enum) {
6060+ key_press: vaxis.Key,
6161+ winsize: vaxis.Winsize,
6262+ focus_in,
6363+ foo: u8,
6464+};
6565+6666+pub fn main() !void {
6767+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
6868+ defer {
6969+ const deinit_status = gpa.deinit();
7070+ //fail test; can't try in defer as defer is executed after we return
7171+ if (deinit_status == .leak) {
7272+ log.err("memory leak", .{});
7373+ }
7474+ }
7575+ const alloc = gpa.allocator();
7676+7777+ // Initialize Vaxis with our event type
7878+ var vx = try vaxis.init(Event, .{});
7979+ // deinit takes an optional allocator. If your program is exiting, you can
8080+ // choose to pass a null allocator to save some exit time.
8181+ defer vx.deinit(alloc);
8282+8383+ // Start the read loop. This puts the terminal in raw mode and begins
8484+ // reading user input
8585+ try vx.startReadThread();
8686+ defer vx.stopReadThread();
8787+8888+ // Optionally enter the alternate screen
8989+ try vx.enterAltScreen();
9090+9191+ // We'll adjust the color index every keypress for the border
9292+ var color_idx: u8 = 0;
9393+9494+ // init our text input widget. The text input widget needs an allocator to
9595+ // store the contents of the input
9696+ var text_input = TextInput.init(alloc);
9797+ defer text_input.deinit();
9898+9999+ // Sends queries to terminal to detect certain features. This should
100100+ // _always_ be called, but is left to the application to decide when
101101+ try vx.queryTerminal();
102102+103103+ // The main event loop. Vaxis provides a thread safe, blocking, buffered
104104+ // queue which can serve as the primary event queue for an application
105105+ outer: while (true) {
106106+ // nextEvent blocks until an event is in the queue
107107+ const event = vx.nextEvent();
108108+ log.debug("event: {}\r\n", .{event});
109109+ // exhaustive switching ftw. Vaxis will send events if your EventType
110110+ // enum has the fields for those events (ie "key_press", "winsize")
111111+ switch (event) {
112112+ .key_press => |key| {
113113+ color_idx = switch (color_idx) {
114114+ 255 => 0,
115115+ else => color_idx + 1,
116116+ };
117117+ if (key.matches('c', .{ .ctrl = true })) {
118118+ break :outer;
119119+ } else if (key.matches('l', .{ .ctrl = true })) {
120120+ vx.queueRefresh();
121121+ } else {
122122+ try text_input.update(.{ .key_press = key });
123123+ }
124124+ },
125125+126126+ // winsize events are sent to the application to ensure that all
127127+ // resizes occur in the main thread. This lets us avoid expensive
128128+ // locks on the screen. All applications must handle this event
129129+ // unless they aren't using a screen (IE only detecting features)
130130+ //
131131+ // This is the only call that the core of Vaxis needs an allocator
132132+ // for. The allocations are because we keep a copy of each cell to
133133+ // optimize renders. When resize is called, we allocated two slices:
134134+ // one for the screen, and one for our buffered screen. Each cell in
135135+ // the buffered screen contains an ArrayList(u8) to be able to store
136136+ // the grapheme for that cell Each cell is initialized with a size
137137+ // of 1, which is sufficient for all of ASCII. Anything requiring
138138+ // more than one byte will incur an allocation on the first render
139139+ // after it is drawn. Thereafter, it will not allocate unless the
140140+ // screen is resized
141141+ .winsize => |ws| try vx.resize(alloc, ws),
142142+ else => {},
143143+ }
144144+145145+ // vx.window() returns the root window. This window is the size of the
146146+ // terminal and can spawn child windows as logical areas. Child windows
147147+ // cannot draw outside of their bounds
148148+ const win = vx.window();
149149+150150+ // Clear the entire space because we are drawing in immediate mode.
151151+ // vaxis double buffers the screen. This new frame will be compared to
152152+ // the old and only updated cells will be drawn
153153+ win.clear();
154154+ const child = win.initChild(
155155+ win.width / 2 - 20,
156156+ win.height / 2 - 3,
157157+ .{ .limit = 40 },
158158+ .{ .limit = 3 },
159159+ );
160160+ // draw the text_input using a bordered window
161161+ const style: vaxis.Style = .{
162162+ .fg = .{ .index = color_idx },
163163+ };
164164+ text_input.draw(border.all(child, style));
165165+166166+ // Render the screen
167167+ try vx.render();
168168+ }
169169+}
170170+```