feat: support rendering theme colors with alpha components

This commit is contained in:
CJ van den Berg 2024-11-04 22:16:04 +01:00
parent 3b28286c91
commit 0a43fa853f
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
9 changed files with 127 additions and 48 deletions

View file

@ -22,8 +22,8 @@
.hash = "122011f0b0bd6798c0575b7c8f5ec8903fe19509976aedffa5f17c6d98a4dc786de3", .hash = "122011f0b0bd6798c0575b7c8f5ec8903fe19509976aedffa5f17c6d98a4dc786de3",
}, },
.themes = .{ .themes = .{
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-d49b4cddde8b33e5a4a60a42a424e836023feb74/flow-themes.tar.gz", .url = "https://github.com/neurocyte/flow-themes/releases/download/master-5f1ca2fd3c784d430306a5cd1df237681a196333/flow-themes.tar.gz",
.hash = "12206f8cf37aa2b69c30697f06127773899e4155612f4fbf56d9198515513de1cee0", .hash = "122095b1a6110b920571c7e49e61c124cd9a164fe9b1b0faa1bd11d04d89822d3304",
}, },
.fuzzig = .{ .fuzzig = .{
.url = "https://github.com/fjebaker/fuzzig/archive/0fd156d5097365151e85a85eef9d8cf0eebe7b00.tar.gz", .url = "https://github.com/fjebaker/fuzzig/archive/0fd156d5097365151e85a85eef9d8cf0eebe7b00.tar.gz",

View file

@ -19,6 +19,14 @@ pub const RGB = struct {
return r | b | g; return r | b | g;
} }
pub inline fn from_u8s(v: [3]u8) RGB {
return .{ .r = v[0], .g = v[1], .b = v[2] };
}
pub fn to_u8s(v: RGB) [3]u8 {
return [_]u8{ v.r, v.g, v.b };
}
pub fn contrast(a_: RGB, b_: RGB) f32 { pub fn contrast(a_: RGB, b_: RGB) f32 {
const a = RGBf.from_RGB(a_).luminance(); const a = RGBf.from_RGB(a_).luminance();
const b = RGBf.from_RGB(b_).luminance(); const b = RGBf.from_RGB(b_).luminance();
@ -60,3 +68,19 @@ pub const RGBf = struct {
pub fn max_contrast(v: u24, a: u24, b: u24) u24 { pub fn max_contrast(v: u24, a: u24, b: u24) u24 {
return RGB.max_contrast(RGB.from_u24(v), RGB.from_u24(a), RGB.from_u24(b)).to_u24(); return RGB.max_contrast(RGB.from_u24(v), RGB.from_u24(a), RGB.from_u24(b)).to_u24();
} }
pub fn apply_alpha(base: RGB, over: RGB, alpha_u8: u8) RGB {
const alpha: f64 = @as(f64, @floatFromInt(alpha_u8)) / @as(f64, @floatFromInt(0xFF));
return .{
.r = component_apply_alpha(base.r, over.r, alpha),
.g = component_apply_alpha(base.g, over.g, alpha),
.b = component_apply_alpha(base.b, over.b, alpha),
};
}
inline fn component_apply_alpha(base_u8: u8, over_u8: u8, alpha: f64) u8 {
const base: f64 = @floatFromInt(base_u8);
const over: f64 = @floatFromInt(over_u8);
const result = ((1 - alpha) * base) + (alpha * over);
return @intFromFloat(result);
}

View file

@ -1,14 +1,20 @@
const vaxis = @import("vaxis"); const vaxis = @import("vaxis");
const Style = @import("theme").Style; const Style = @import("theme").Style;
const Color = @import("theme").Color;
const FontStyle = @import("theme").FontStyle;
const color = @import("color");
const Cell = @This(); const Cell = @This();
cell: vaxis.Cell = .{}, cell: vaxis.Cell = .{},
pub inline fn set_style(self: *Cell, style_: Style) void { pub inline fn set_style(self: *Cell, style_: Style) void {
if (style_.fg) |fg| self.cell.style.fg = vaxis.Cell.Color.rgbFromUint(fg); self.set_style_fg(style_);
if (style_.bg) |bg| self.cell.style.bg = vaxis.Cell.Color.rgbFromUint(bg); self.set_style_bg(style_);
if (style_.fs) |fs| { if (style_.fs) |fs| self.set_font_style(fs);
}
pub inline fn set_font_style(self: *Cell, fs: FontStyle) void {
self.cell.style.ul = .default; self.cell.style.ul = .default;
self.cell.style.ul_style = .off; self.cell.style.ul_style = .off;
self.cell.style.bold = false; self.cell.style.bold = false;
@ -28,18 +34,29 @@ pub inline fn set_style(self: *Cell, style_: Style) void {
.strikethrough => self.cell.style.strikethrough = true, .strikethrough => self.cell.style.strikethrough = true,
} }
} }
}
pub inline fn set_under_color(self: *Cell, arg_rgb: c_uint) void { pub inline fn set_under_color(self: *Cell, arg_rgb: c_uint) void {
self.cell.style.ul = vaxis.Cell.Color.rgbFromUint(@intCast(arg_rgb)); self.cell.style.ul = vaxis.Cell.Color.rgbFromUint(@intCast(arg_rgb));
} }
inline fn apply_alpha(base_vaxis: vaxis.Cell.Color, over_theme: Color) vaxis.Cell.Color {
const alpha = over_theme.alpha;
return if (alpha == 0xFF or base_vaxis != .rgb)
vaxis.Cell.Color.rgbFromUint(over_theme.color)
else blk: {
const base = color.RGB.from_u8s(base_vaxis.rgb);
const over = color.RGB.from_u24(over_theme.color);
const result = color.apply_alpha(base, over, alpha);
break :blk .{ .rgb = result.to_u8s() };
};
}
pub inline fn set_style_fg(self: *Cell, style_: Style) void { pub inline fn set_style_fg(self: *Cell, style_: Style) void {
if (style_.fg) |fg| self.cell.style.fg = vaxis.Cell.Color.rgbFromUint(fg); if (style_.fg) |fg| self.cell.style.fg = apply_alpha(self.cell.style.bg, fg);
} }
pub inline fn set_style_bg(self: *Cell, style_: Style) void { pub inline fn set_style_bg(self: *Cell, style_: Style) void {
if (style_.bg) |bg| self.cell.style.bg = vaxis.Cell.Color.rgbFromUint(bg); if (style_.bg) |bg| self.cell.style.bg = apply_alpha(self.cell.style.bg, bg);
} }
pub inline fn set_fg_rgb(self: *Cell, arg_rgb: c_uint) !void { pub inline fn set_fg_rgb(self: *Cell, arg_rgb: c_uint) !void {

View file

@ -1,10 +1,13 @@
const std = @import("std"); const std = @import("std");
const Style = @import("theme").Style; const Style = @import("theme").Style;
const ThemeColor = @import("theme").Color;
const FontStyle = @import("theme").FontStyle; const FontStyle = @import("theme").FontStyle;
const StyleBits = @import("style.zig").StyleBits; const StyleBits = @import("style.zig").StyleBits;
const Cell = @import("Cell.zig"); const Cell = @import("Cell.zig");
const vaxis = @import("vaxis"); const vaxis = @import("vaxis");
const Buffer = @import("Buffer"); const Buffer = @import("Buffer");
const color = @import("color");
const RGB = @import("color").RGB;
const Plane = @This(); const Plane = @This();
@ -266,12 +269,20 @@ pub fn off_styles(self: *Plane, stylebits: StyleBits) void {
if (stylebits.italic) self.style.italic = false; if (stylebits.italic) self.style.italic = false;
} }
pub fn set_fg_rgb(self: *Plane, channel: u32) !void { pub fn set_fg_rgb(self: *Plane, col: ThemeColor) !void {
self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(channel)); self.style.fg = to_cell_color(col);
} }
pub fn set_bg_rgb(self: *Plane, channel: u32) !void { pub fn set_fg_rgb_alpha(self: *Plane, alpha_bg: ThemeColor, col: ThemeColor) !void {
self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(channel)); self.style.fg = apply_alpha_theme(alpha_bg, col);
}
pub fn set_bg_rgb(self: *Plane, col: ThemeColor) !void {
self.style.bg = to_cell_color(col);
}
pub fn set_bg_rgb_alpha(self: *Plane, alpha_bg: ThemeColor, col: ThemeColor) !void {
self.style.bg = apply_alpha_theme(alpha_bg, col);
} }
pub fn set_fg_palindex(self: *Plane, idx: c_uint) !void { pub fn set_fg_palindex(self: *Plane, idx: c_uint) !void {
@ -283,38 +294,54 @@ pub fn set_bg_palindex(self: *Plane, idx: c_uint) !void {
} }
pub inline fn set_base_style(self: *Plane, _: [*c]const u8, style_: Style) void { pub inline fn set_base_style(self: *Plane, _: [*c]const u8, style_: Style) void {
self.style_base.fg = if (style_.fg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; self.style_base.fg = if (style_.fg) |col| to_cell_color(col) else .default;
self.style_base.bg = if (style_.bg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; self.style_base.bg = if (style_.bg) |col| to_cell_color(col) else .default;
if (style_.fs) |fs| set_font_style(&self.style, fs); if (style_.fs) |fs| set_font_style(&self.style, fs);
self.set_style(style_); self.set_style(style_);
} }
pub fn set_base_style_transparent(self: *Plane, _: [*:0]const u8, style_: Style) void { pub fn set_base_style_transparent(self: *Plane, _: [*:0]const u8, style_: Style) void {
self.style_base.fg = if (style_.fg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; self.style_base.fg = if (style_.fg) |col| to_cell_color(col) else .default;
self.style_base.bg = if (style_.bg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; self.style_base.bg = if (style_.bg) |col| to_cell_color(col) else .default;
if (style_.fs) |fs| set_font_style(&self.style, fs); if (style_.fs) |fs| set_font_style(&self.style, fs);
self.set_style(style_); self.set_style(style_);
self.transparent = true; self.transparent = true;
} }
pub fn set_base_style_bg_transparent(self: *Plane, _: [*:0]const u8, style_: Style) void { pub fn set_base_style_bg_transparent(self: *Plane, _: [*:0]const u8, style_: Style) void {
self.style_base.fg = if (style_.fg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; self.style_base.fg = if (style_.fg) |col| to_cell_color(col) else .default;
self.style_base.bg = if (style_.bg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; self.style_base.bg = if (style_.bg) |col| to_cell_color(col) else .default;
if (style_.fs) |fs| set_font_style(&self.style, fs); if (style_.fs) |fs| set_font_style(&self.style, fs);
self.set_style(style_); self.set_style(style_);
self.transparent = true; self.transparent = true;
} }
fn apply_alpha(bg: vaxis.Cell.Color, col: ThemeColor) vaxis.Cell.Color {
const alpha = col.alpha;
return if (alpha == 0xFF or bg != .rgb)
.{ .rgb = RGB.to_u8s(RGB.from_u24(col.color)) }
else
.{ .rgb = color.apply_alpha(RGB.from_u8s(bg.rgb), RGB.from_u24(col.color), alpha).to_u8s() };
}
fn apply_alpha_theme(bg: ThemeColor, col: ThemeColor) vaxis.Cell.Color {
const alpha = col.alpha;
return if (alpha == 0xFF)
.{ .rgb = RGB.to_u8s(RGB.from_u24(col.color)) }
else
.{ .rgb = color.apply_alpha(RGB.from_u24(bg.color), RGB.from_u24(col.color), alpha).to_u8s() };
}
pub inline fn set_style(self: *Plane, style_: Style) void { pub inline fn set_style(self: *Plane, style_: Style) void {
if (style_.fg) |color| self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); if (style_.fg) |col| self.style.fg = apply_alpha(self.style_base.bg, col);
if (style_.bg) |color| self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); if (style_.bg) |col| self.style.bg = apply_alpha(self.style_base.bg, col);
if (style_.fs) |fs| set_font_style(&self.style, fs); if (style_.fs) |fs| set_font_style(&self.style, fs);
self.transparent = false; self.transparent = false;
} }
pub inline fn set_style_bg_transparent(self: *Plane, style_: Style) void { pub inline fn set_style_bg_transparent(self: *Plane, style_: Style) void {
if (style_.fg) |color| self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); if (style_.fg) |col| self.style.fg = apply_alpha(self.style_base.bg, col);
if (style_.bg) |color| self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); if (style_.bg) |col| self.style.bg = apply_alpha(self.style_base.bg, col);
if (style_.fs) |fs| set_font_style(&self.style, fs); if (style_.fs) |fs| set_font_style(&self.style, fs);
self.transparent = true; self.transparent = true;
} }
@ -395,3 +422,7 @@ const GraphemeCache = struct {
return self.buf[self.idx .. self.idx + bytes.len]; return self.buf[self.idx .. self.idx + bytes.len];
} }
}; };
fn to_cell_color(col: ThemeColor) vaxis.Cell.Color {
return .{ .rgb = RGB.to_u8s(RGB.from_u24(col.color)) };
}

View file

@ -329,9 +329,9 @@ pub fn set_terminal_title(self: *Self, text: []const u8) void {
pub fn set_terminal_style(self: *Self, style_: Style) void { pub fn set_terminal_style(self: *Self, style_: Style) void {
if (style_.fg) |color| if (style_.fg) |color|
self.vx.setTerminalForegroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color)).rgb) catch {}; self.vx.setTerminalForegroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
if (style_.bg) |color| if (style_.bg) |color|
self.vx.setTerminalBackgroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color)).rgb) catch {}; self.vx.setTerminalBackgroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
} }
pub fn set_terminal_working_directory(self: *Self, absolute_path: []const u8) void { pub fn set_terminal_working_directory(self: *Self, absolute_path: []const u8) void {

View file

@ -960,7 +960,7 @@ pub const Editor = struct {
var cell = self.plane.cell_init(); var cell = self.plane.cell_init();
_ = self.plane.at_cursor_cell(&cell) catch return; _ = self.plane.at_cursor_cell(&cell) catch return;
cell.set_style(.{ .fs = .undercurl }); cell.set_style(.{ .fs = .undercurl });
if (style.fg) |ul_col| cell.set_under_color(ul_col); if (style.fg) |ul_col| cell.set_under_color(ul_col.color);
_ = self.plane.putc(&cell) catch {}; _ = self.plane.putc(&cell) catch {};
} }

View file

@ -161,10 +161,10 @@ pub fn render_linear(self: *Self, theme: *const Widget.Theme) void {
while (rows > 0) : (rows -= 1) { while (rows > 0) : (rows -= 1) {
if (linenum > self.lines) return; if (linenum > self.lines) return;
if (linenum == self.line + 1) { if (linenum == self.line + 1) {
self.plane.set_base_style(" ", theme.editor_gutter_active); self.plane.set_style(theme.editor_gutter_active);
self.plane.on_styles(style.bold); self.plane.on_styles(style.bold);
} else { } else {
self.plane.set_base_style(" ", theme.editor_gutter); self.plane.set_style(theme.editor_gutter);
self.plane.off_styles(style.bold); self.plane.off_styles(style.bold);
} }
_ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{std.fmt.bufPrintZ(&buf, "{d} ", .{linenum}) catch return}) catch {}; _ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{std.fmt.bufPrintZ(&buf, "{d} ", .{linenum}) catch return}) catch {};
@ -187,7 +187,7 @@ pub fn render_relative(self: *Self, theme: *const Widget.Theme) void {
var buf: [31:0]u8 = undefined; var buf: [31:0]u8 = undefined;
while (rows > 0) : (rows -= 1) { while (rows > 0) : (rows -= 1) {
if (pos > self.lines - @as(u32, @intCast(row))) return; if (pos > self.lines - @as(u32, @intCast(row))) return;
self.plane.set_base_style(" ", if (linenum == 0) theme.editor_gutter_active else theme.editor_gutter); self.plane.set_style(if (linenum == 0) theme.editor_gutter_active else theme.editor_gutter);
const val = @abs(if (linenum == 0) line else linenum); const val = @abs(if (linenum == 0) line else linenum);
const fmt = std.fmt.bufPrintZ(&buf, "{d} ", .{val}) catch return; const fmt = std.fmt.bufPrintZ(&buf, "{d} ", .{val}) catch return;
_ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{if (fmt.len > 6) "==> " else fmt}) catch {}; _ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{if (fmt.len > 6) "==> " else fmt}) catch {};

View file

@ -157,9 +157,15 @@ fn show_color(self: *Self, tag: []const u8, c_: ?Widget.Theme.Color) void {
if (c_) |c| { if (c_) |c| {
_ = self.plane.print(" {s}:", .{tag}) catch return; _ = self.plane.print(" {s}:", .{tag}) catch return;
self.plane.set_bg_rgb(c) catch {}; self.plane.set_bg_rgb(c) catch {};
self.plane.set_fg_rgb(color.max_contrast(c, theme.panel.fg orelse 0xFFFFFF, theme.panel.bg orelse 0x000000)) catch {}; self.plane.set_fg_rgb(.{ .color = color.max_contrast(
_ = self.plane.print("#{x}", .{c}) catch return; c.color,
(theme.panel.fg orelse Widget.Theme.Color{ .color = 0xFFFFFF }).color,
(theme.panel.bg orelse Widget.Theme.Color{ .color = 0x000000 }).color,
) }) catch {};
_ = self.plane.print("#{x}", .{c.color}) catch return;
self.reset_style(); self.reset_style();
if (c.alpha != 0xff)
_ = self.plane.print(" ɑ{x}", .{c.alpha}) catch return;
} }
} }

View file

@ -61,8 +61,9 @@ pub fn render(_: *void, self: *Button.State(void), theme: *const Widget.Theme) b
} }
fn render_separator(self: *Button.State(void), theme: *const Widget.Theme) void { fn render_separator(self: *Button.State(void), theme: *const Widget.Theme) void {
if (theme.statusbar_hover.bg) |bg| self.plane.set_fg_rgb(bg) catch {}; const statusbar_bg = theme.statusbar.bg orelse theme.editor.bg.?;
if (theme.statusbar.bg) |bg| self.plane.set_bg_rgb(bg) catch {}; if (theme.statusbar_hover.bg) |bg| self.plane.set_fg_rgb_alpha(statusbar_bg, bg) catch {};
if (theme.statusbar.bg) |bg| self.plane.set_bg_rgb_alpha(statusbar_bg, bg) catch {};
_ = self.plane.putstr("") catch {}; _ = self.plane.putstr("") catch {};
} }