diff --git a/build.zig.zon b/build.zig.zon index b84bc81..bb101c3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -22,8 +22,8 @@ .hash = "122011f0b0bd6798c0575b7c8f5ec8903fe19509976aedffa5f17c6d98a4dc786de3", }, .themes = .{ - .url = "https://github.com/neurocyte/flow-themes/releases/download/master-d49b4cddde8b33e5a4a60a42a424e836023feb74/flow-themes.tar.gz", - .hash = "12206f8cf37aa2b69c30697f06127773899e4155612f4fbf56d9198515513de1cee0", + .url = "https://github.com/neurocyte/flow-themes/releases/download/master-5f1ca2fd3c784d430306a5cd1df237681a196333/flow-themes.tar.gz", + .hash = "122095b1a6110b920571c7e49e61c124cd9a164fe9b1b0faa1bd11d04d89822d3304", }, .fuzzig = .{ .url = "https://github.com/fjebaker/fuzzig/archive/0fd156d5097365151e85a85eef9d8cf0eebe7b00.tar.gz", diff --git a/src/color.zig b/src/color.zig index dec4117..c3bb124 100644 --- a/src/color.zig +++ b/src/color.zig @@ -19,6 +19,14 @@ pub const RGB = struct { 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 { const a = RGBf.from_RGB(a_).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 { 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); +} diff --git a/src/renderer/vaxis/Cell.zig b/src/renderer/vaxis/Cell.zig index 88a1de1..b680eff 100644 --- a/src/renderer/vaxis/Cell.zig +++ b/src/renderer/vaxis/Cell.zig @@ -1,32 +1,37 @@ const vaxis = @import("vaxis"); const Style = @import("theme").Style; +const Color = @import("theme").Color; +const FontStyle = @import("theme").FontStyle; +const color = @import("color"); const Cell = @This(); cell: vaxis.Cell = .{}, pub inline fn set_style(self: *Cell, style_: Style) void { - if (style_.fg) |fg| self.cell.style.fg = vaxis.Cell.Color.rgbFromUint(fg); - if (style_.bg) |bg| self.cell.style.bg = vaxis.Cell.Color.rgbFromUint(bg); - if (style_.fs) |fs| { - self.cell.style.ul = .default; - self.cell.style.ul_style = .off; - self.cell.style.bold = false; - self.cell.style.dim = false; - self.cell.style.italic = false; - self.cell.style.blink = false; - self.cell.style.reverse = false; - self.cell.style.invisible = false; - self.cell.style.strikethrough = false; + self.set_style_fg(style_); + self.set_style_bg(style_); + if (style_.fs) |fs| self.set_font_style(fs); +} - switch (fs) { - .normal => {}, - .bold => self.cell.style.bold = true, - .italic => self.cell.style.italic = true, - .underline => self.cell.style.ul_style = .single, - .undercurl => self.cell.style.ul_style = .curly, - .strikethrough => self.cell.style.strikethrough = true, - } +pub inline fn set_font_style(self: *Cell, fs: FontStyle) void { + self.cell.style.ul = .default; + self.cell.style.ul_style = .off; + self.cell.style.bold = false; + self.cell.style.dim = false; + self.cell.style.italic = false; + self.cell.style.blink = false; + self.cell.style.reverse = false; + self.cell.style.invisible = false; + self.cell.style.strikethrough = false; + + switch (fs) { + .normal => {}, + .bold => self.cell.style.bold = true, + .italic => self.cell.style.italic = true, + .underline => self.cell.style.ul_style = .single, + .undercurl => self.cell.style.ul_style = .curly, + .strikethrough => self.cell.style.strikethrough = true, } } @@ -34,12 +39,24 @@ pub inline fn set_under_color(self: *Cell, arg_rgb: c_uint) void { 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 { - 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 { - 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 { diff --git a/src/renderer/vaxis/Plane.zig b/src/renderer/vaxis/Plane.zig index 59c7234..0379001 100644 --- a/src/renderer/vaxis/Plane.zig +++ b/src/renderer/vaxis/Plane.zig @@ -1,10 +1,13 @@ const std = @import("std"); const Style = @import("theme").Style; +const ThemeColor = @import("theme").Color; const FontStyle = @import("theme").FontStyle; const StyleBits = @import("style.zig").StyleBits; const Cell = @import("Cell.zig"); const vaxis = @import("vaxis"); const Buffer = @import("Buffer"); +const color = @import("color"); +const RGB = @import("color").RGB; const Plane = @This(); @@ -266,12 +269,20 @@ pub fn off_styles(self: *Plane, stylebits: StyleBits) void { if (stylebits.italic) self.style.italic = false; } -pub fn set_fg_rgb(self: *Plane, channel: u32) !void { - self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(channel)); +pub fn set_fg_rgb(self: *Plane, col: ThemeColor) !void { + self.style.fg = to_cell_color(col); } -pub fn set_bg_rgb(self: *Plane, channel: u32) !void { - self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(channel)); +pub fn set_fg_rgb_alpha(self: *Plane, alpha_bg: ThemeColor, col: ThemeColor) !void { + 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 { @@ -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 { - self.style_base.fg = if (style_.fg) |color| vaxis.Cell.Color.rgbFromUint(@intCast(color)) else .default; - self.style_base.bg = if (style_.bg) |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) |col| to_cell_color(col) else .default; if (style_.fs) |fs| set_font_style(&self.style, fs); self.set_style(style_); } 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.bg = if (style_.bg) |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) |col| to_cell_color(col) else .default; if (style_.fs) |fs| set_font_style(&self.style, fs); self.set_style(style_); self.transparent = true; } 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.bg = if (style_.bg) |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) |col| to_cell_color(col) else .default; if (style_.fs) |fs| set_font_style(&self.style, fs); self.set_style(style_); 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 { - if (style_.fg) |color| self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); - if (style_.bg) |color| self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); + if (style_.fg) |col| self.style.fg = apply_alpha(self.style_base.bg, col); + if (style_.bg) |col| self.style.bg = apply_alpha(self.style_base.bg, col); if (style_.fs) |fs| set_font_style(&self.style, fs); self.transparent = false; } 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_.bg) |color| self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(color)); + if (style_.fg) |col| self.style.fg = apply_alpha(self.style_base.bg, col); + if (style_.bg) |col| self.style.bg = apply_alpha(self.style_base.bg, col); if (style_.fs) |fs| set_font_style(&self.style, fs); self.transparent = true; } @@ -395,3 +422,7 @@ const GraphemeCache = struct { 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)) }; +} diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index f9a6ce5..f60d53e 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -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 { 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| - 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 { diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 93e6995..7b69402 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -960,7 +960,7 @@ pub const Editor = struct { var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return; 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 {}; } diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index d120a90..c76a584 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -161,10 +161,10 @@ pub fn render_linear(self: *Self, theme: *const Widget.Theme) void { while (rows > 0) : (rows -= 1) { if (linenum > self.lines) return; 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); } else { - self.plane.set_base_style(" ", theme.editor_gutter); + self.plane.set_style(theme.editor_gutter); self.plane.off_styles(style.bold); } _ = 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; while (rows > 0) : (rows -= 1) { 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 fmt = std.fmt.bufPrintZ(&buf, "{d} ", .{val}) catch return; _ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{if (fmt.len > 6) "==> " else fmt}) catch {}; diff --git a/src/tui/inspector_view.zig b/src/tui/inspector_view.zig index 86390be..6ff2092 100644 --- a/src/tui/inspector_view.zig +++ b/src/tui/inspector_view.zig @@ -157,9 +157,15 @@ fn show_color(self: *Self, tag: []const u8, c_: ?Widget.Theme.Color) void { if (c_) |c| { _ = self.plane.print(" {s}:", .{tag}) catch return; 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.print("#{x}", .{c}) catch return; + self.plane.set_fg_rgb(.{ .color = color.max_contrast( + 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(); + if (c.alpha != 0xff) + _ = self.plane.print(" ɑ{x}", .{c.alpha}) catch return; } } diff --git a/src/tui/status/modestate.zig b/src/tui/status/modestate.zig index 201603c..f47ff79 100644 --- a/src/tui/status/modestate.zig +++ b/src/tui/status/modestate.zig @@ -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 { - if (theme.statusbar_hover.bg) |bg| self.plane.set_fg_rgb(bg) catch {}; - if (theme.statusbar.bg) |bg| self.plane.set_bg_rgb(bg) catch {}; + const statusbar_bg = theme.statusbar.bg orelse theme.editor.bg.?; + 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 {}; }