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