mod(style): styling and color revamp now with fewer characters to print to the terminal
This commit is contained in:
@@ -22,8 +22,12 @@ pub fn main() !void {
|
|||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
var container = try App.Container.init(allocator, .{});
|
var container = try App.Container.init(allocator, .{
|
||||||
try container.append(try App.Container.init(allocator, .{}));
|
.border = .{ .color = .green, .corners = .rounded },
|
||||||
|
});
|
||||||
|
try container.append(try App.Container.init(allocator, .{
|
||||||
|
.border = .{ .color = .light_green, .corners = .squared },
|
||||||
|
}));
|
||||||
defer container.deinit();
|
defer container.deinit();
|
||||||
|
|
||||||
// NOTE: should the min-size here be required?
|
// NOTE: should the min-size here be required?
|
||||||
|
|||||||
128
src/color.zig
128
src/color.zig
@@ -1,9 +1,16 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Color = union(enum) {
|
pub const Color = enum(u8) {
|
||||||
ansi: enum(u32) {
|
default = 0,
|
||||||
reset = 0,
|
black = 16,
|
||||||
black = 30,
|
light_red = 1,
|
||||||
|
light_green,
|
||||||
|
light_yellow,
|
||||||
|
light_blue,
|
||||||
|
light_magenta,
|
||||||
|
light_cyan,
|
||||||
|
light_grey,
|
||||||
|
grey,
|
||||||
red,
|
red,
|
||||||
green,
|
green,
|
||||||
yellow,
|
yellow,
|
||||||
@@ -11,116 +18,21 @@ pub const Color = union(enum) {
|
|||||||
magenta,
|
magenta,
|
||||||
cyan,
|
cyan,
|
||||||
white,
|
white,
|
||||||
bright_black = 90,
|
// TODO: add further colors as described in https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b # Color / Graphics Mode - 256 Colors
|
||||||
bright_red,
|
|
||||||
bright_green,
|
|
||||||
bright_yellow,
|
|
||||||
bright_blue,
|
|
||||||
bright_magenta,
|
|
||||||
bright_cyan,
|
|
||||||
bright_white,
|
|
||||||
},
|
|
||||||
rgb: [3]u8,
|
|
||||||
|
|
||||||
pub fn eql(a: Color, b: Color) bool {
|
pub inline fn write(this: Color, writer: anytype, comptime coloring: enum { fg, bg, ul }) !void {
|
||||||
switch (a) {
|
if (this == .default) {
|
||||||
.ansi => |a_idx| {
|
switch (coloring) {
|
||||||
switch (b) {
|
|
||||||
.ansi => |b_idx| return a_idx == b_idx,
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.rgb => |a_rgb| {
|
|
||||||
switch (b) {
|
|
||||||
.rgb => |b_rgb| return a_rgb[0] == b_rgb[0] and
|
|
||||||
a_rgb[1] == b_rgb[1] and
|
|
||||||
a_rgb[2] == b_rgb[2],
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(this: Color, writer: anytype, comptime coloring: enum { fg, bg, ul }) !void {
|
|
||||||
switch (this) {
|
|
||||||
.ansi => |index| blk: {
|
|
||||||
if (index == .reset)
|
|
||||||
break :blk switch (coloring) {
|
|
||||||
.fg => try std.fmt.format(writer, "39", .{}),
|
.fg => try std.fmt.format(writer, "39", .{}),
|
||||||
.bg => try std.fmt.format(writer, "49", .{}),
|
.bg => try std.fmt.format(writer, "49", .{}),
|
||||||
.ul => try std.fmt.format(writer, "59", .{}),
|
.ul => try std.fmt.format(writer, "59", .{}),
|
||||||
};
|
|
||||||
|
|
||||||
break :blk switch (coloring) {
|
|
||||||
.fg => try std.fmt.format(writer, "38;5;{d}", .{@intFromEnum(index)}),
|
|
||||||
.bg => try std.fmt.format(writer, "48;5;{d}", .{@intFromEnum(index)}),
|
|
||||||
.ul => try std.fmt.format(writer, "58;5;{d}", .{@intFromEnum(index)}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.rgb => |rgb| switch (coloring) {
|
|
||||||
.fg => try std.fmt.format(writer, "38;2;{d};{d};{d}", .{ rgb[0], rgb[1], rgb[2] }),
|
|
||||||
.bg => try std.fmt.format(writer, "48;2;{d};{d};{d}", .{ rgb[0], rgb[1], rgb[2] }),
|
|
||||||
.ul => try std.fmt.format(writer, "58;2;{d};{d};{d}", .{ rgb[0], rgb[1], rgb[2] }),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
switch (coloring) {
|
||||||
|
.fg => try std.fmt.format(writer, "38;5;{d}", .{@intFromEnum(this)}),
|
||||||
|
.bg => try std.fmt.format(writer, "48;5;{d}", .{@intFromEnum(this)}),
|
||||||
|
.ul => try std.fmt.format(writer, "58;5;{d}", .{@intFromEnum(this)}),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rgbFromUint(val: u24) Color {
|
|
||||||
const r_bits = val & 0b11111111_00000000_00000000;
|
|
||||||
const g_bits = val & 0b00000000_11111111_00000000;
|
|
||||||
const b_bits = val & 0b00000000_00000000_11111111;
|
|
||||||
const rgb = [_]u8{
|
|
||||||
@truncate(r_bits >> 16),
|
|
||||||
@truncate(g_bits >> 8),
|
|
||||||
@truncate(b_bits),
|
|
||||||
};
|
|
||||||
return .{ .rgb = rgb };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// parse an XParseColor-style rgb specification into an rgb Color. The spec
|
|
||||||
/// is of the form: rgb:rrrr/gggg/bbbb. Generally, the high two bits will always
|
|
||||||
/// be the same as the low two bits.
|
|
||||||
pub fn rgbFromSpec(spec: []const u8) !Color {
|
|
||||||
var iter = std.mem.splitScalar(u8, spec, ':');
|
|
||||||
const prefix = iter.next() orelse return error.InvalidColorSpec;
|
|
||||||
if (!std.mem.eql(u8, "rgb", prefix)) return error.InvalidColorSpec;
|
|
||||||
|
|
||||||
const spec_str = iter.next() orelse return error.InvalidColorSpec;
|
|
||||||
|
|
||||||
var spec_iter = std.mem.splitScalar(u8, spec_str, '/');
|
|
||||||
|
|
||||||
const r_raw = spec_iter.next() orelse return error.InvalidColorSpec;
|
|
||||||
if (r_raw.len != 4) return error.InvalidColorSpec;
|
|
||||||
|
|
||||||
const g_raw = spec_iter.next() orelse return error.InvalidColorSpec;
|
|
||||||
if (g_raw.len != 4) return error.InvalidColorSpec;
|
|
||||||
|
|
||||||
const b_raw = spec_iter.next() orelse return error.InvalidColorSpec;
|
|
||||||
if (b_raw.len != 4) return error.InvalidColorSpec;
|
|
||||||
|
|
||||||
const r = try std.fmt.parseUnsigned(u8, r_raw[2..], 16);
|
|
||||||
const g = try std.fmt.parseUnsigned(u8, g_raw[2..], 16);
|
|
||||||
const b = try std.fmt.parseUnsigned(u8, b_raw[2..], 16);
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.rgb = [_]u8{ r, g, b },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "rgbFromSpec" {
|
|
||||||
const spec = "rgb:aaaa/bbbb/cccc";
|
|
||||||
const actual = try rgbFromSpec(spec);
|
|
||||||
switch (actual) {
|
|
||||||
.rgb => |rgb| {
|
|
||||||
try std.testing.expectEqual(0xAA, rgb[0]);
|
|
||||||
try std.testing.expectEqual(0xBB, rgb[1]);
|
|
||||||
try std.testing.expectEqual(0xCC, rgb[2]);
|
|
||||||
},
|
|
||||||
else => try std.testing.expect(false),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test {
|
|
||||||
_ = Color;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
|||||||
const Cell = @import("cell.zig");
|
const Cell = @import("cell.zig");
|
||||||
const Color = @import("color.zig").Color;
|
const Color = @import("color.zig").Color;
|
||||||
const Size = @import("size.zig");
|
const Size = @import("size.zig");
|
||||||
|
const Style = @import("style.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.container);
|
const log = std.log.scoped(.container);
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ pub const Border = struct {
|
|||||||
pub const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
|
pub const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
|
||||||
pub const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
|
pub const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
|
||||||
/// Color to use for the border
|
/// Color to use for the border
|
||||||
color: Color = .{ .ansi = .reset },
|
color: Color = .white,
|
||||||
/// Configure the corner type to be used for the border
|
/// Configure the corner type to be used for the border
|
||||||
corners: enum(u1) {
|
corners: enum(u1) {
|
||||||
squared,
|
squared,
|
||||||
@@ -29,7 +30,7 @@ pub const Border = struct {
|
|||||||
/// Configure separator borders between child element to added to the layout
|
/// Configure separator borders between child element to added to the layout
|
||||||
separator: struct {
|
separator: struct {
|
||||||
enabled: bool = false,
|
enabled: bool = false,
|
||||||
color: Color = .{ .ansi = .reset },
|
color: Color = .white,
|
||||||
line: enum {
|
line: enum {
|
||||||
line,
|
line,
|
||||||
dotted,
|
dotted,
|
||||||
@@ -39,6 +40,7 @@ pub const Border = struct {
|
|||||||
|
|
||||||
// NOTE: caller owns `cells` slice and ensures that `cells.len == size.cols * size.rows`
|
// NOTE: caller owns `cells` slice and ensures that `cells.len == size.cols * size.rows`
|
||||||
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
|
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
|
||||||
|
log.debug("Content of Border: {any}", .{this});
|
||||||
std.debug.assert(cells.len == size.cols * size.rows);
|
std.debug.assert(cells.len == size.cols * size.rows);
|
||||||
|
|
||||||
const frame = switch (this.corners) {
|
const frame = switch (this.corners) {
|
||||||
@@ -68,14 +70,16 @@ pub const Border = struct {
|
|||||||
cells[last_row + col].cp = frame[1];
|
cells[last_row + col].cp = frame[1];
|
||||||
}
|
}
|
||||||
// TODO: fix rendering of styling?
|
// TODO: fix rendering of styling?
|
||||||
cells[col].style = .{ .fg = .{ .ansi = .red }, .attributes = &.{} };
|
cells[col].style.fg = this.color;
|
||||||
cells[last_row + col].style = .{ .fg = .{ .ansi = .red }, .attributes = &.{} };
|
cells[last_row + col].style.fg = this.color;
|
||||||
}
|
}
|
||||||
// render left and right border
|
// render left and right border
|
||||||
for (1..size.rows - 1) |row| {
|
for (1..size.rows - 1) |row| {
|
||||||
const idx = (row * size.cols);
|
const idx = (row * size.cols);
|
||||||
cells[idx].cp = frame[3]; // left
|
cells[idx].cp = frame[3]; // left
|
||||||
|
cells[idx].style.fg = this.color;
|
||||||
cells[idx + size.cols - 1].cp = frame[3]; // right
|
cells[idx + size.cols - 1].cp = frame[3]; // right
|
||||||
|
cells[idx + size.cols - 1].style.fg = this.color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -85,7 +89,7 @@ pub const Rectangle = struct {
|
|||||||
/// `Color` to use to fill the `Rectangle` with
|
/// `Color` to use to fill the `Rectangle` with
|
||||||
/// NOTE: used as background color when rendering! such that it renders the
|
/// NOTE: used as background color when rendering! such that it renders the
|
||||||
/// children accordingly without removing the coloring of the `Rectangle`
|
/// children accordingly without removing the coloring of the `Rectangle`
|
||||||
fill: Color = .{ .ansi = .reset },
|
fill: Color = .black,
|
||||||
/// Configure the corners of the `Rectangle`
|
/// Configure the corners of the `Rectangle`
|
||||||
corners: enum(u1) {
|
corners: enum(u1) {
|
||||||
squared,
|
squared,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub const Underline = enum {
|
|||||||
dashed,
|
dashed,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Attribute = enum(u32) {
|
pub const Attribute = enum(u8) {
|
||||||
reset = 0,
|
reset = 0,
|
||||||
bold = 1,
|
bold = 1,
|
||||||
dim,
|
dim,
|
||||||
@@ -34,52 +34,42 @@ pub const Attribute = enum(u32) {
|
|||||||
strikethrough,
|
strikethrough,
|
||||||
};
|
};
|
||||||
|
|
||||||
fg: Color = .{ .ansi = .reset },
|
fg: Color = .white,
|
||||||
bg: Color = .{ .ansi = .reset },
|
bg: Color = .default,
|
||||||
ul: Color = .{ .ansi = .reset },
|
ul: Color = .default,
|
||||||
ul_style: Underline = .off,
|
ul_style: Underline = .off,
|
||||||
attributes: []const Attribute,
|
attributes: []const Attribute,
|
||||||
|
|
||||||
pub fn eql(this: Style, other: Style) bool {
|
pub fn eql(this: Style, other: Style) bool {
|
||||||
const ret = this.fg.eql(other.fg) and
|
return std.meta.eql(this, other);
|
||||||
this.bg.eql(other.bg) and
|
|
||||||
this.ul.eql(other.ul) and
|
|
||||||
other.ul_style == this.ul_style and
|
|
||||||
other.attributes.len == this.attributes.len;
|
|
||||||
if (ret == false) return false;
|
|
||||||
|
|
||||||
// are the attributes also identical?
|
|
||||||
for (0..this.attributes.len) |i| {
|
|
||||||
if (this.attributes[i] != other.attributes[i])
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(this: Style, writer: anytype, cp: u21) !void {
|
pub fn value(this: Style, writer: anytype, cp: u21) !void {
|
||||||
var buffer: [4]u8 = undefined;
|
var buffer: [4]u8 = undefined;
|
||||||
const bytes = try std.unicode.utf8Encode(cp, &buffer);
|
const bytes = try std.unicode.utf8Encode(cp, &buffer);
|
||||||
std.debug.assert(bytes > 0);
|
std.debug.assert(bytes > 0);
|
||||||
// start escape sequence
|
// build ansi sequence for 256 colors ...
|
||||||
|
// foreground
|
||||||
try std.fmt.format(writer, "\x1b[", .{});
|
try std.fmt.format(writer, "\x1b[", .{});
|
||||||
// colors
|
|
||||||
try this.fg.write(writer, .fg);
|
try this.fg.write(writer, .fg);
|
||||||
|
// background
|
||||||
try std.fmt.format(writer, ";", .{});
|
try std.fmt.format(writer, ";", .{});
|
||||||
try this.bg.write(writer, .bg);
|
try this.bg.write(writer, .bg);
|
||||||
// try std.fmt.format(writer, ":", .{});
|
|
||||||
// underline
|
// underline
|
||||||
// try this.ul.write(writer, .ul);
|
// FIX: assert that if the underline property is set that the ul style and the attribute for underlining is available
|
||||||
// try std.fmt.format(writer, ":", .{});
|
try std.fmt.format(writer, ";", .{});
|
||||||
// attributes
|
try this.ul.write(writer, .ul);
|
||||||
|
// append styles (aka attributes like bold, italic, strikethrough, etc.)
|
||||||
for (this.attributes) |attribute| {
|
for (this.attributes) |attribute| {
|
||||||
try std.fmt.format(writer, ":{d}", .{@intFromEnum(attribute)});
|
try std.fmt.format(writer, ";{d}", .{@intFromEnum(attribute)});
|
||||||
}
|
}
|
||||||
// end styling
|
|
||||||
try std.fmt.format(writer, "m", .{});
|
try std.fmt.format(writer, "m", .{});
|
||||||
// content
|
// content
|
||||||
try std.fmt.format(writer, "{s}", .{buffer});
|
try std.fmt.format(writer, "{s}", .{buffer});
|
||||||
// end escape sequence
|
// TODO: is it necessary to reset the graphics mode afterwards to ensure
|
||||||
try writer.print("\x1b[0m", .{});
|
// that everything rendered behind is still as expected (without any
|
||||||
|
// side-effects)?
|
||||||
|
// try writer.print("\x1b[0m", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement helper functions for terminal capabilities:
|
// TODO: implement helper functions for terminal capabilities:
|
||||||
|
|||||||
Reference in New Issue
Block a user