a modern tui library written in zig

refactor: make code more idiomatic

- Added standard .gitattributes file for Zig projects.
- Reworked build.zig a little, hopefully it's a bit clearer. Also, now zig build will run all steps.
- outer: while in examples was redundant since there's only one loop to break from. switch expressions don't allow breaking from them, so breaking is only for loops, i.e. while and for.
- When returning a struct instance from a function, the compiler infers the return type from function signature, so instead of return MyType{...}; , it's more idiomatic to write return .{...};.
- Logging adds a new line by default, so you don't usually need to write \n like here: log.debug("event: {}\r\n", .{event});.

authored by Jora Troosh and committed by GitHub 4a463cfa a733860a

+2
.gitattributes
···
··· 1 + *.zig text eol=lf 2 + *.zon text eol=lf
+6 -6
README.md
··· 53 54 const log = std.log.scoped(.main); 55 56 - // Our EventType. This can contain internal events as well as Vaxis events. 57 // Internal events can be posted into the same queue as vaxis events to allow 58 // for a single event loop with exhaustive switching. Booya 59 const Event = union(enum) { ··· 102 103 // The main event loop. Vaxis provides a thread safe, blocking, buffered 104 // queue which can serve as the primary event queue for an application 105 - outer: while (true) { 106 // nextEvent blocks until an event is in the queue 107 const event = vx.nextEvent(); 108 - log.debug("event: {}\r\n", .{event}); 109 - // exhaustive switching ftw. Vaxis will send events if your EventType 110 - // enum has the fields for those events (ie "key_press", "winsize") 111 switch (event) { 112 .key_press => |key| { 113 color_idx = switch (color_idx) { ··· 115 else => color_idx + 1, 116 }; 117 if (key.matches('c', .{ .ctrl = true })) { 118 - break :outer; 119 } else if (key.matches('l', .{ .ctrl = true })) { 120 vx.queueRefresh(); 121 } else {
··· 53 54 const log = std.log.scoped(.main); 55 56 + // This can contain internal events as well as Vaxis events. 57 // Internal events can be posted into the same queue as vaxis events to allow 58 // for a single event loop with exhaustive switching. Booya 59 const Event = union(enum) { ··· 102 103 // The main event loop. Vaxis provides a thread safe, blocking, buffered 104 // queue which can serve as the primary event queue for an application 105 + while (true) { 106 // nextEvent blocks until an event is in the queue 107 const event = vx.nextEvent(); 108 + log.debug("event: {}", .{event}); 109 + // exhaustive switching ftw. Vaxis will send events if your Event enum 110 + // has the fields for those events (ie "key_press", "winsize") 111 switch (event) { 112 .key_press => |key| { 113 color_idx = switch (color_idx) { ··· 115 else => color_idx + 1, 116 }; 117 if (key.matches('c', .{ .ctrl = true })) { 118 + break; 119 } else if (key.matches('l', .{ .ctrl = true })) { 120 vx.queueRefresh(); 121 } else {
+38 -29
build.zig
··· 3 pub fn build(b: *std.Build) void { 4 const target = b.standardTargetOptions(.{}); 5 const optimize = b.standardOptimizeOption(.{}); 6 7 - const vaxis = b.addModule("vaxis", .{ .root_source_file = .{ .path = "src/main.zig" } }); 8 - 9 - const ziglyph = b.dependency("ziglyph", .{ 10 .optimize = optimize, 11 .target = target, 12 }); 13 - vaxis.addImport("ziglyph", ziglyph.module("ziglyph")); 14 - 15 - const zigimg = b.dependency("zigimg", .{ 16 .optimize = optimize, 17 .target = target, 18 }); 19 - vaxis.addImport("zigimg", zigimg.module("zigimg")); 20 21 - const exe = b.addExecutable(.{ 22 - .name = "vaxis", 23 - .root_source_file = .{ .path = "examples/pathological.zig" }, 24 .target = target, 25 .optimize = optimize, 26 }); 27 - exe.root_module.addImport("vaxis", vaxis); 28 29 - const run_cmd = b.addRunArtifact(exe); 30 31 - run_cmd.step.dependOn(b.getInstallStep()); 32 33 - if (b.args) |args| { 34 - run_cmd.addArgs(args); 35 - } 36 - 37 - const run_step = b.step("run", "Run the app"); 38 - run_step.dependOn(&run_cmd.step); 39 - 40 - // Creates a step for unit testing. This only builds the test executable 41 - // but does not run it. 42 - const lib_unit_tests = b.addTest(.{ 43 - .root_source_file = .{ .path = "src/main.zig" }, 44 .target = target, 45 .optimize = optimize, 46 }); 47 - lib_unit_tests.root_module.addImport("ziglyph", ziglyph.module("ziglyph")); 48 - lib_unit_tests.root_module.addImport("zigimg", zigimg.module("zigimg")); 49 50 - const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); 51 52 - const test_step = b.step("test", "Run unit tests"); 53 - test_step.dependOn(&run_lib_unit_tests.step); 54 }
··· 3 pub fn build(b: *std.Build) void { 4 const target = b.standardTargetOptions(.{}); 5 const optimize = b.standardOptimizeOption(.{}); 6 + const root_source_file = std.Build.LazyPath.relative("src/main.zig"); 7 8 + // Dependencies 9 + const ziglyph_dep = b.dependency("ziglyph", .{ 10 .optimize = optimize, 11 .target = target, 12 }); 13 + const zigimg_dep = b.dependency("zigimg", .{ 14 .optimize = optimize, 15 .target = target, 16 }); 17 18 + // Module 19 + const vaxis_mod = b.addModule("vaxis", .{ .root_source_file = root_source_file }); 20 + vaxis_mod.addImport("ziglyph", ziglyph_dep.module("ziglyph")); 21 + vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg")); 22 + 23 + // Examples 24 + const example_step = b.step("example", "Run examples"); 25 + 26 + const example = b.addExecutable(.{ 27 + .name = "vaxis_pathological_example", 28 + .root_source_file = std.Build.LazyPath.relative("examples/pathological.zig"), 29 .target = target, 30 .optimize = optimize, 31 }); 32 + example.root_module.addImport("vaxis", vaxis_mod); 33 34 + const example_run = b.addRunArtifact(example); 35 + example_step.dependOn(&example_run.step); 36 + b.default_step.dependOn(example_step); 37 38 + // Tests 39 + const tests_step = b.step("test", "Run tests"); 40 41 + const tests = b.addTest(.{ 42 + .root_source_file = root_source_file, 43 .target = target, 44 .optimize = optimize, 45 }); 46 + tests.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); 47 + tests.root_module.addImport("zigimg", zigimg_dep.module("zigimg")); 48 + 49 + const tests_run = b.addRunArtifact(tests); 50 + tests_step.dependOn(&tests_run.step); 51 + b.default_step.dependOn(tests_step); 52 53 + // Lints 54 + const lints_step = b.step("lint", "Run lints"); 55 56 + const lints = b.addFmt(.{ 57 + .paths = &.{ "src", "build.zig" }, 58 + .check = true, 59 + }); 60 + 61 + lints_step.dependOn(&lints.step); 62 + b.default_step.dependOn(lints_step); 63 }
+7 -28
build.zig.zon
··· 1 .{ 2 .name = "vaxis", 3 - // This is a [Semantic Version](https://semver.org/). 4 - // In a future version of Zig it will be used for package deduplication. 5 .version = "0.1.0", 6 - 7 - // This field is optional. 8 - // This is currently advisory only; Zig does not yet do anything 9 - // with this value. 10 - //.minimum_zig_version = "0.11.0", 11 - 12 .dependencies = .{ 13 .ziglyph = .{ 14 - .url = "https://codeberg.org/dude_the_builder/ziglyph/archive/ac50ab06c91d2dd632ff4573c035dafe3b374aba.tar.gz", 15 - .hash = "1220e097fbfb3a15a6f3484cf507f1f10ab571d1bcf519c3b5447ca727782b7a5264", 16 }, 17 - .zigimg = .{ 18 - .url = "https://github.com/zigimg/zigimg/archive/f6998808f283f8d3c2ef34e8b4af423bc1786f32.tar.gz", 19 - .hash = "12202ee5d22ade0c300e9e7eae4c1951bda3d5f236fe1a139eb3613b43e2f12a88db", 20 - } 21 - }, 22 - 23 - .paths = .{ 24 - // This makes *all* files, recursively, included in this package. It is generally 25 - // better to explicitly list the files and directories instead, to insure that 26 - // fetching from tarballs, file system paths, and version control all result 27 - // in the same contents hash. 28 - "", 29 - // For example... 30 - //"build.zig", 31 - //"build.zig.zon", 32 - //"src", 33 - //"LICENSE", 34 - //"README.md", 35 }, 36 }
··· 1 .{ 2 .name = "vaxis", 3 .version = "0.1.0", 4 + .paths = .{""}, 5 .dependencies = .{ 6 .ziglyph = .{ 7 + .url = "https://codeberg.org/dude_the_builder/ziglyph/archive/ac50ab06c9.tar.gz", 8 + .hash = "1220e097fbfb3a15a6f3484cf507f1f10ab571d1bcf519c3b5447ca727782b7a5264", 9 + }, 10 + .zigimg = .{ 11 + .url = "https://github.com/zigimg/zigimg/archive/2224f91.tar.gz", 12 + .hash = "12207067e4892c48369415268648380859baa89c324748ae5bfda414a12868c9fc8b", 13 }, 14 }, 15 }
+1 -1
examples/image.zig
··· 56 57 const img = imgs[n]; 58 const dims = try img.cellSize(win); 59 - const center = vaxis.alignment.center(win, dims.cols, dims.rows); 60 const scale = false; 61 const z_index = 0; 62 img.draw(center, scale, z_index);
··· 56 57 const img = imgs[n]; 58 const dims = try img.cellSize(win); 59 + const center = vaxis.widgets.alignment.center(win, dims.cols, dims.rows); 60 const scale = false; 61 const z_index = 0; 62 img.draw(center, scale, z_index);
+5 -5
examples/main.zig
··· 32 33 // The main event loop. Vaxis provides a thread safe, blocking, buffered 34 // queue which can serve as the primary event queue for an application 35 - outer: while (true) { 36 // nextEvent blocks until an event is in the queue 37 const event = vx.nextEvent(); 38 - log.debug("event: {}\r\n", .{event}); 39 - // exhaustive switching ftw. Vaxis will send events if your EventType 40 // enum has the fields for those events (ie "key_press", "winsize") 41 switch (event) { 42 .key_press => |key| { ··· 45 else => color_idx + 1, 46 }; 47 if (key.codepoint == 'c' and key.mods.ctrl) { 48 - break :outer; 49 } 50 }, 51 .winsize => |ws| { ··· 87 } 88 } 89 90 - // Our EventType. This can contain internal events as well as Vaxis events. 91 // Internal events can be posted into the same queue as vaxis events to allow 92 // for a single event loop with exhaustive switching. Booya 93 const Event = union(enum) {
··· 32 33 // The main event loop. Vaxis provides a thread safe, blocking, buffered 34 // queue which can serve as the primary event queue for an application 35 + while (true) { 36 // nextEvent blocks until an event is in the queue 37 const event = vx.nextEvent(); 38 + log.debug("event: {}", .{event}); 39 + // exhaustive switching ftw. Vaxis will send events if your Event 40 // enum has the fields for those events (ie "key_press", "winsize") 41 switch (event) { 42 .key_press => |key| { ··· 45 else => color_idx + 1, 46 }; 47 if (key.codepoint == 'c' and key.mods.ctrl) { 48 + break; 49 } 50 }, 51 .winsize => |ws| { ··· 87 } 88 } 89 90 + // Our Event. This can contain internal events as well as Vaxis events. 91 // Internal events can be posted into the same queue as vaxis events to allow 92 // for a single event loop with exhaustive switching. Booya 93 const Event = union(enum) {
+2 -2
examples/pathological.zig
··· 19 try vx.enterAltScreen(); 20 try vx.queryTerminal(); 21 22 - outer: while (true) { 23 const event = vx.nextEvent(); 24 switch (event) { 25 .winsize => |ws| { 26 try vx.resize(alloc, ws); 27 - break :outer; 28 }, 29 } 30 }
··· 19 try vx.enterAltScreen(); 20 try vx.queryTerminal(); 21 22 + while (true) { 23 const event = vx.nextEvent(); 24 switch (event) { 25 .winsize => |ws| { 26 try vx.resize(alloc, ws); 27 + break; 28 }, 29 } 30 }
+5 -5
examples/text_input.zig
··· 6 7 const 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 12 const Event = union(enum) { ··· 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| { ··· 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 })) {
··· 6 7 const log = std.log.scoped(.main); 8 9 + // Our Event. 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 12 const Event = union(enum) { ··· 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 + while (true) { 63 // nextEvent blocks until an event is in the queue 64 const event = vx.nextEvent(); 65 + log.debug("event: {}", .{event}); 66 + // exhaustive switching ftw. Vaxis will send events if your Event 67 // enum has the fields for those events (ie "key_press", "winsize") 68 switch (event) { 69 .key_press => |key| { ··· 72 else => color_idx + 1, 73 }; 74 if (key.matches('c', .{ .ctrl = true })) { 75 + break; 76 } else if (key.matches('l', .{ .ctrl = true })) { 77 vx.queueRefresh(); 78 } else if (key.matches('n', .{ .ctrl = true })) {
+2 -2
src/InternalScreen.zig
··· 1 const std = @import("std"); 2 const assert = std.debug.assert; 3 - const Style = @import("cell.zig").Style; 4 - const Cell = @import("cell.zig").Cell; 5 const Shape = @import("Mouse.zig").Shape; 6 7 const log = std.log.scoped(.internal_screen);
··· 1 const std = @import("std"); 2 const assert = std.debug.assert; 3 + const Style = @import("Cell.zig").Style; 4 + const Cell = @import("Cell.zig"); 5 const Shape = @import("Mouse.zig").Shape; 6 7 const log = std.log.scoped(.internal_screen);
+1 -1
src/Screen.zig
··· 1 const std = @import("std"); 2 const assert = std.debug.assert; 3 4 - const Cell = @import("cell.zig").Cell; 5 const Shape = @import("Mouse.zig").Shape; 6 const Image = @import("Image.zig"); 7 const Winsize = @import("Tty.zig").Winsize;
··· 1 const std = @import("std"); 2 const assert = std.debug.assert; 3 4 + const Cell = @import("Cell.zig"); 5 const Shape = @import("Mouse.zig").Shape; 6 const Image = @import("Image.zig"); 7 const Winsize = @import("Tty.zig").Winsize;
+13 -16
src/Tty.zig
··· 1 const std = @import("std"); 2 const builtin = @import("builtin"); 3 const os = std.os; 4 - const vaxis = @import("main.zig"); 5 - const Vaxis = vaxis.Vaxis; 6 - const Event = @import("event.zig").Event; 7 const Parser = @import("Parser.zig"); 8 - const Key = vaxis.Key; 9 const GraphemeCache = @import("GraphemeCache.zig"); 10 11 const log = std.log.scoped(.tty); ··· 68 /// read input from the tty 69 pub fn run( 70 self: *Tty, 71 - comptime EventType: type, 72 - vx: *Vaxis(EventType), 73 ) !void { 74 // create a pipe so we can signal to exit the run loop 75 const pipe = try os.pipe(); ··· 78 79 // get our initial winsize 80 const winsize = try getWinsize(self.fd); 81 - if (@hasField(EventType, "winsize")) { 82 vx.postEvent(.{ .winsize = winsize }); 83 } 84 ··· 91 const WinchHandler = struct { 92 const Self = @This(); 93 94 - var vx_winch: *Vaxis(EventType) = undefined; 95 var fd: os.fd_t = undefined; 96 97 - fn init(vx_arg: *Vaxis(EventType), fd_arg: os.fd_t) !void { 98 vx_winch = vx_arg; 99 fd = fd_arg; 100 var act = os.Sigaction{ ··· 114 const ws = getWinsize(fd) catch { 115 return; 116 }; 117 - if (@hasField(EventType, "winsize")) { 118 vx_winch.postEvent(.{ .winsize = ws }); 119 } 120 } ··· 156 const event = result.event orelse continue; 157 switch (event) { 158 .key_press => |key| { 159 - if (@hasField(EventType, "key_press")) { 160 // HACK: yuck. there has to be a better way 161 var mut_key = key; 162 if (key.text) |text| { ··· 166 } 167 }, 168 .mouse => |mouse| { 169 - if (@hasField(EventType, "mouse")) { 170 vx.postEvent(.{ .mouse = mouse }); 171 } 172 }, 173 .focus_in => { 174 - if (@hasField(EventType, "focus_in")) { 175 vx.postEvent(.focus_in); 176 } 177 }, 178 .focus_out => { 179 - if (@hasField(EventType, "focus_out")) { 180 vx.postEvent(.focus_out); 181 } 182 }, 183 .paste_start => { 184 - if (@hasField(EventType, "paste_start")) { 185 vx.postEvent(.paste_start); 186 } 187 }, 188 .paste_end => { 189 - if (@hasField(EventType, "paste_end")) { 190 vx.postEvent(.paste_end); 191 } 192 },
··· 1 const std = @import("std"); 2 const builtin = @import("builtin"); 3 const os = std.os; 4 + const Vaxis = @import("vaxis.zig").Vaxis; 5 const Parser = @import("Parser.zig"); 6 const GraphemeCache = @import("GraphemeCache.zig"); 7 8 const log = std.log.scoped(.tty); ··· 65 /// read input from the tty 66 pub fn run( 67 self: *Tty, 68 + comptime Event: type, 69 + vx: *Vaxis(Event), 70 ) !void { 71 // create a pipe so we can signal to exit the run loop 72 const pipe = try os.pipe(); ··· 75 76 // get our initial winsize 77 const winsize = try getWinsize(self.fd); 78 + if (@hasField(Event, "winsize")) { 79 vx.postEvent(.{ .winsize = winsize }); 80 } 81 ··· 88 const WinchHandler = struct { 89 const Self = @This(); 90 91 + var vx_winch: *Vaxis(Event) = undefined; 92 var fd: os.fd_t = undefined; 93 94 + fn init(vx_arg: *Vaxis(Event), fd_arg: os.fd_t) !void { 95 vx_winch = vx_arg; 96 fd = fd_arg; 97 var act = os.Sigaction{ ··· 111 const ws = getWinsize(fd) catch { 112 return; 113 }; 114 + if (@hasField(Event, "winsize")) { 115 vx_winch.postEvent(.{ .winsize = ws }); 116 } 117 } ··· 153 const event = result.event orelse continue; 154 switch (event) { 155 .key_press => |key| { 156 + if (@hasField(Event, "key_press")) { 157 // HACK: yuck. there has to be a better way 158 var mut_key = key; 159 if (key.text) |text| { ··· 163 } 164 }, 165 .mouse => |mouse| { 166 + if (@hasField(Event, "mouse")) { 167 vx.postEvent(.{ .mouse = mouse }); 168 } 169 }, 170 .focus_in => { 171 + if (@hasField(Event, "focus_in")) { 172 vx.postEvent(.focus_in); 173 } 174 }, 175 .focus_out => { 176 + if (@hasField(Event, "focus_out")) { 177 vx.postEvent(.focus_out); 178 } 179 }, 180 .paste_start => { 181 + if (@hasField(Event, "paste_start")) { 182 vx.postEvent(.paste_start); 183 } 184 }, 185 .paste_end => { 186 + if (@hasField(Event, "paste_end")) { 187 vx.postEvent(.paste_end); 188 } 189 },
+3 -15
src/Window.zig
··· 4 const GraphemeIterator = ziglyph.GraphemeIterator; 5 6 const Screen = @import("Screen.zig"); 7 - const Cell = @import("cell.zig").Cell; 8 - const Segment = @import("cell.zig").Segment; 9 const gw = @import("gwidth.zig"); 10 11 const log = std.log.scoped(.window); ··· 118 var word_iter = try WordIterator.init(segment.text); 119 while (word_iter.next()) |word| { 120 // break lines when we need 121 - if (isLineBreak(word.bytes)) { 122 row += 1; 123 col = 0; 124 wrapped = false; ··· 155 col += w; 156 } 157 } 158 - } 159 - } 160 - 161 - fn isLineBreak(str: []const u8) bool { 162 - if (std.mem.eql(u8, str, "\r\n")) { 163 - return true; 164 - } else if (std.mem.eql(u8, str, "\r")) { 165 - return true; 166 - } else if (std.mem.eql(u8, str, "\n")) { 167 - return true; 168 - } else { 169 - return false; 170 } 171 } 172
··· 4 const GraphemeIterator = ziglyph.GraphemeIterator; 5 6 const Screen = @import("Screen.zig"); 7 + const Cell = @import("Cell.zig"); 8 + const Segment = @import("Cell.zig").Segment; 9 const gw = @import("gwidth.zig"); 10 11 const log = std.log.scoped(.window); ··· 118 var word_iter = try WordIterator.init(segment.text); 119 while (word_iter.next()) |word| { 120 // break lines when we need 121 + if (word.bytes[0] == '\r' or word.bytes[0] == '\n') { 122 row += 1; 123 col = 0; 124 wrapped = false; ··· 155 col += w; 156 } 157 } 158 } 159 } 160
+5 -7
src/cell.zig src/Cell.zig
··· 1 const Image = @import("Image.zig"); 2 3 - pub const Cell = struct { 4 - char: Character = .{}, 5 - style: Style = .{}, 6 - link: Hyperlink = .{}, 7 - image: ?Image.Placement = null, 8 - }; 9 10 /// Segment is a contiguous run of text that has a constant style 11 pub const Segment = struct { ··· 17 pub const Character = struct { 18 grapheme: []const u8 = " ", 19 /// width should only be provided when the application is sure the terminal 20 - /// will meeasure the same width. This can be ensure by using the gwidth method 21 /// included in libvaxis. If width is 0, libvaxis will measure the glyph at 22 /// render time 23 width: usize = 1,
··· 1 const Image = @import("Image.zig"); 2 3 + char: Character = .{}, 4 + style: Style = .{}, 5 + link: Hyperlink = .{}, 6 + image: ?Image.Placement = null, 7 8 /// Segment is a contiguous run of text that has a constant style 9 pub const Segment = struct { ··· 15 pub const Character = struct { 16 grapheme: []const u8 = " ", 17 /// width should only be provided when the application is sure the terminal 18 + /// will measure the same width. This can be ensure by using the gwidth method 19 /// included in libvaxis. If width is 0, libvaxis will measure the glyph at 20 /// render time 21 width: usize = 1,
+8 -27
src/main.zig
··· 1 pub const Vaxis = @import("vaxis.zig").Vaxis; 2 pub const Options = @import("Options.zig"); 3 4 - const cell = @import("cell.zig"); 5 - pub const Cell = cell.Cell; 6 - pub const Style = cell.Style; 7 - pub const Segment = cell.Segment; 8 - pub const Color = cell.Color; 9 - 10 pub const Key = @import("Key.zig"); 11 pub const Mouse = @import("Mouse.zig"); 12 pub const Winsize = @import("Tty.zig").Winsize; 13 14 - pub const widgets = @import("widgets/main.zig"); 15 - pub const alignment = widgets.alignment; 16 - pub const border = widgets.border; 17 - 18 - pub const Image = @import("Image.zig"); 19 20 /// Initialize a Vaxis application. 21 - pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) { 22 - return Vaxis(EventType).init(opts); 23 } 24 25 test { 26 - _ = @import("GraphemeCache.zig"); 27 - _ = @import("Key.zig"); 28 - _ = @import("Mouse.zig"); 29 - _ = @import("Options.zig"); 30 - _ = @import("Parser.zig"); 31 - _ = @import("Screen.zig"); 32 - _ = @import("Tty.zig"); 33 - _ = @import("Window.zig"); 34 - _ = @import("cell.zig"); 35 - _ = @import("ctlseqs.zig"); 36 - _ = @import("event.zig"); 37 - _ = @import("gwidth.zig"); 38 - _ = @import("queue.zig"); 39 - _ = @import("vaxis.zig"); 40 }
··· 1 + const std = @import("std"); 2 + 3 pub const Vaxis = @import("vaxis.zig").Vaxis; 4 pub const Options = @import("Options.zig"); 5 6 pub const Key = @import("Key.zig"); 7 + pub const Cell = @import("Cell.zig"); 8 + pub const Image = @import("Image.zig"); 9 pub const Mouse = @import("Mouse.zig"); 10 pub const Winsize = @import("Tty.zig").Winsize; 11 12 + pub const widgets = @import("widgets.zig"); 13 14 /// Initialize a Vaxis application. 15 + pub fn init(comptime Event: type, opts: Options) !Vaxis(Event) { 16 + return Vaxis(Event).init(opts); 17 } 18 19 test { 20 + std.testing.refAllDecls(@This()); 21 }
+9 -9
src/vaxis.zig
··· 11 const InternalScreen = @import("InternalScreen.zig"); 12 const Window = @import("Window.zig"); 13 const Options = @import("Options.zig"); 14 - const Style = @import("cell.zig").Style; 15 - const Hyperlink = @import("cell.zig").Hyperlink; 16 const gwidth = @import("gwidth.zig"); 17 const Shape = @import("Mouse.zig").Shape; 18 const Image = @import("Image.zig"); ··· 34 35 const log = std.log.scoped(.vaxis); 36 37 - pub const EventType = T; 38 39 pub const Capabilities = struct { 40 kitty_keyboard: bool = false, ··· 83 84 /// Initialize Vaxis with runtime options 85 pub fn init(_: Options) !Self { 86 - return Self{ 87 .queue = .{}, 88 .tty = null, 89 .screen = .{}, ··· 151 self.queue.push(event); 152 } 153 154 - /// resize allocates a slice of cellsequal to the number of cells 155 /// required to display the screen (ie width x height). Any previous screen is 156 /// freed when resizing 157 pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void { ··· 169 170 /// returns a Window comprising of the entire terminal screen 171 pub fn window(self: *Self) Window { 172 - return Window{ 173 .x_off = 0, 174 .y_off = 0, 175 .width = self.screen.width, ··· 207 if (std.mem.eql(u8, colorterm, "truecolor") or 208 std.mem.eql(u8, colorterm, "24bit")) 209 { 210 - if (@hasField(EventType, "cap_rgb")) { 211 self.postEvent(.cap_rgb); 212 } 213 } ··· 348 } 349 } 350 351 - // something is different, so let's loop throuugh everything and 352 // find out what 353 354 // foreground ··· 642 } 643 } 644 try tty.buffered_writer.flush(); 645 - return Image{ 646 .id = id, 647 .width = img.width, 648 .height = img.height,
··· 11 const InternalScreen = @import("InternalScreen.zig"); 12 const Window = @import("Window.zig"); 13 const Options = @import("Options.zig"); 14 + const Style = @import("Cell.zig").Style; 15 + const Hyperlink = @import("Cell.zig").Hyperlink; 16 const gwidth = @import("gwidth.zig"); 17 const Shape = @import("Mouse.zig").Shape; 18 const Image = @import("Image.zig"); ··· 34 35 const log = std.log.scoped(.vaxis); 36 37 + pub const Event = T; 38 39 pub const Capabilities = struct { 40 kitty_keyboard: bool = false, ··· 83 84 /// Initialize Vaxis with runtime options 85 pub fn init(_: Options) !Self { 86 + return .{ 87 .queue = .{}, 88 .tty = null, 89 .screen = .{}, ··· 151 self.queue.push(event); 152 } 153 154 + /// resize allocates a slice of cells equal to the number of cells 155 /// required to display the screen (ie width x height). Any previous screen is 156 /// freed when resizing 157 pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void { ··· 169 170 /// returns a Window comprising of the entire terminal screen 171 pub fn window(self: *Self) Window { 172 + return .{ 173 .x_off = 0, 174 .y_off = 0, 175 .width = self.screen.width, ··· 207 if (std.mem.eql(u8, colorterm, "truecolor") or 208 std.mem.eql(u8, colorterm, "24bit")) 209 { 210 + if (@hasField(Event, "cap_rgb")) { 211 self.postEvent(.cap_rgb); 212 } 213 } ··· 348 } 349 } 350 351 + // something is different, so let's loop through everything and 352 // find out what 353 354 // foreground ··· 642 } 643 } 644 try tty.buffered_writer.flush(); 645 + return .{ 646 .id = id, 647 .width = img.width, 648 .height = img.height,
+3
src/widgets.zig
···
··· 1 + pub const border = @import("widgets/border.zig"); 2 + pub const alignment = @import("widgets/alignment.zig"); 3 + pub const TextInput = @import("widgets/TextInput.zig");
+1 -1
src/widgets/TextInput.zig
··· 1 const std = @import("std"); 2 - const Cell = @import("../cell.zig").Cell; 3 const Key = @import("../Key.zig"); 4 const Window = @import("../Window.zig"); 5 const GraphemeIterator = @import("ziglyph").GraphemeIterator; 6
··· 1 const std = @import("std"); 2 const Key = @import("../Key.zig"); 3 + const Cell = @import("../Cell.zig"); 4 const Window = @import("../Window.zig"); 5 const GraphemeIterator = @import("ziglyph").GraphemeIterator; 6
src/widgets/align.zig src/widgets/alignment.zig
+4 -3
src/widgets/border.zig
··· 1 const Window = @import("../Window.zig"); 2 - const cell = @import("../cell.zig"); 3 - const Character = cell.Character; 4 - const Style = cell.Style; 5 6 const horizontal = Character{ .grapheme = "─", .width = 1 }; 7 const vertical = Character{ .grapheme = "│", .width = 1 };
··· 1 + const Cell = @import("../Cell.zig"); 2 const Window = @import("../Window.zig"); 3 + 4 + const Style = Cell.Style; 5 + const Character = Cell.Character; 6 7 const horizontal = Character{ .grapheme = "─", .width = 1 }; 8 const vertical = Character{ .grapheme = "│", .width = 1 };
-3
src/widgets/main.zig
··· 1 - pub const TextInput = @import("TextInput.zig"); 2 - pub const border = @import("border.zig"); 3 - pub const alignment = @import("align.zig");
···