a modern tui library written in zig
at v0.4.0 5.9 kB view raw
1const std = @import("std"); 2const Image = @import("Image.zig"); 3 4char: Character = .{}, 5style: Style = .{}, 6link: Hyperlink = .{}, 7image: ?Image.Placement = null, 8default: bool = false, 9/// Set to true if this cell is the last cell printed in a row before wrap. Vaxis will determine if 10/// it should rely on the terminal's autowrap feature which can help with primary screen resizes 11wrapped: bool = false, 12 13/// Segment is a contiguous run of text that has a constant style 14pub const Segment = struct { 15 text: []const u8, 16 style: Style = .{}, 17 link: Hyperlink = .{}, 18}; 19 20pub const Character = struct { 21 grapheme: []const u8 = " ", 22 /// width should only be provided when the application is sure the terminal 23 /// will measure the same width. This can be ensure by using the gwidth method 24 /// included in libvaxis. If width is 0, libvaxis will measure the glyph at 25 /// render time 26 width: usize = 1, 27}; 28 29pub const CursorShape = enum { 30 default, 31 block_blink, 32 block, 33 underline_blink, 34 underline, 35 beam_blink, 36 beam, 37}; 38 39pub const Hyperlink = struct { 40 uri: []const u8 = "", 41 /// ie "id=app-1234" 42 params: []const u8 = "", 43}; 44 45pub const Style = struct { 46 pub const Underline = enum { 47 off, 48 single, 49 double, 50 curly, 51 dotted, 52 dashed, 53 }; 54 55 fg: Color = .default, 56 bg: Color = .default, 57 ul: Color = .default, 58 ul_style: Underline = .off, 59 60 bold: bool = false, 61 dim: bool = false, 62 italic: bool = false, 63 blink: bool = false, 64 reverse: bool = false, 65 invisible: bool = false, 66 strikethrough: bool = false, 67 68 pub fn eql(a: Style, b: Style) bool { 69 const SGRBits = packed struct { 70 bold: bool, 71 dim: bool, 72 italic: bool, 73 blink: bool, 74 reverse: bool, 75 invisible: bool, 76 strikethrough: bool, 77 }; 78 const a_sgr: SGRBits = .{ 79 .bold = a.bold, 80 .dim = a.dim, 81 .italic = a.italic, 82 .blink = a.blink, 83 .reverse = a.reverse, 84 .invisible = a.invisible, 85 .strikethrough = a.strikethrough, 86 }; 87 const b_sgr: SGRBits = .{ 88 .bold = b.bold, 89 .dim = b.dim, 90 .italic = b.italic, 91 .blink = b.blink, 92 .reverse = b.reverse, 93 .invisible = b.invisible, 94 .strikethrough = b.strikethrough, 95 }; 96 const a_cast: u7 = @bitCast(a_sgr); 97 const b_cast: u7 = @bitCast(b_sgr); 98 return a_cast == b_cast and 99 Color.eql(a.fg, b.fg) and 100 Color.eql(a.bg, b.bg) and 101 Color.eql(a.ul, b.ul) and 102 a.ul_style == b.ul_style; 103 } 104}; 105 106pub const Color = union(enum) { 107 default, 108 index: u8, 109 rgb: [3]u8, 110 111 pub const Kind = union(enum) { 112 fg, 113 bg, 114 cursor, 115 index: u8, 116 }; 117 118 /// Returned when querying a color from the terminal 119 pub const Report = struct { 120 kind: Kind, 121 value: [3]u8, 122 }; 123 124 pub const Scheme = enum { 125 dark, 126 light, 127 }; 128 129 pub fn eql(a: Color, b: Color) bool { 130 switch (a) { 131 .default => return b == .default, 132 .index => |a_idx| { 133 switch (b) { 134 .index => |b_idx| return a_idx == b_idx, 135 else => return false, 136 } 137 }, 138 .rgb => |a_rgb| { 139 switch (b) { 140 .rgb => |b_rgb| return a_rgb[0] == b_rgb[0] and 141 a_rgb[1] == b_rgb[1] and 142 a_rgb[2] == b_rgb[2], 143 else => return false, 144 } 145 }, 146 } 147 } 148 149 pub fn rgbFromUint(val: u24) Color { 150 const r_bits = val & 0b11111111_00000000_00000000; 151 const g_bits = val & 0b00000000_11111111_00000000; 152 const b_bits = val & 0b00000000_00000000_11111111; 153 const rgb = [_]u8{ 154 @truncate(r_bits >> 16), 155 @truncate(g_bits >> 8), 156 @truncate(b_bits), 157 }; 158 return .{ .rgb = rgb }; 159 } 160 161 /// parse an XParseColor-style rgb specification into an rgb Color. The spec 162 /// is of the form: rgb:rrrr/gggg/bbbb. Generally, the high two bits will always 163 /// be the same as the low two bits. 164 pub fn rgbFromSpec(spec: []const u8) !Color { 165 var iter = std.mem.splitScalar(u8, spec, ':'); 166 const prefix = iter.next() orelse return error.InvalidColorSpec; 167 if (!std.mem.eql(u8, "rgb", prefix)) return error.InvalidColorSpec; 168 169 const spec_str = iter.next() orelse return error.InvalidColorSpec; 170 171 var spec_iter = std.mem.splitScalar(u8, spec_str, '/'); 172 173 const r_raw = spec_iter.next() orelse return error.InvalidColorSpec; 174 if (r_raw.len != 4) return error.InvalidColorSpec; 175 176 const g_raw = spec_iter.next() orelse return error.InvalidColorSpec; 177 if (g_raw.len != 4) return error.InvalidColorSpec; 178 179 const b_raw = spec_iter.next() orelse return error.InvalidColorSpec; 180 if (b_raw.len != 4) return error.InvalidColorSpec; 181 182 const r = try std.fmt.parseUnsigned(u8, r_raw[2..], 16); 183 const g = try std.fmt.parseUnsigned(u8, g_raw[2..], 16); 184 const b = try std.fmt.parseUnsigned(u8, b_raw[2..], 16); 185 186 return .{ 187 .rgb = [_]u8{ r, g, b }, 188 }; 189 } 190 191 test "rgbFromSpec" { 192 const spec = "rgb:aaaa/bbbb/cccc"; 193 const actual = try rgbFromSpec(spec); 194 switch (actual) { 195 .rgb => |rgb| { 196 try std.testing.expectEqual(0xAA, rgb[0]); 197 try std.testing.expectEqual(0xBB, rgb[1]); 198 try std.testing.expectEqual(0xCC, rgb[2]); 199 }, 200 else => try std.testing.expect(false), 201 } 202 } 203};