refactor(gui): move RGBA to color module

This commit is contained in:
CJ van den Berg 2026-04-10 12:17:47 +02:00
parent 76ed87f87b
commit 347ce61f5d
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
8 changed files with 109 additions and 67 deletions

View file

@ -106,6 +106,47 @@ pub const RGBf = struct {
const GAMMA = 2.4;
};
// Packed RGBA color type used by GPUs
pub const RGBA = packed struct(u32) {
a: u8,
b: u8,
g: u8,
r: u8,
pub fn init(r: u8, g: u8, b: u8, a: u8) RGBA {
return .{ .r = r, .g = g, .b = b, .a = a };
}
pub fn from_RGB(v: RGB) RGBA {
return .{ .r = v.r, .g = v.g, .b = v.b, .a = 255 };
}
pub inline fn from_u32(v: u32) RGBA {
return @bitCast(v);
}
pub inline fn from_u24(v: u24) RGBA {
return from_RGB(RGB.from_u24(v));
}
pub inline fn from_u8s(v: [3]u8) RGBA {
return .{ .r = v[0], .g = v[1], .b = v[2], .a = 255 };
}
pub inline fn to_u32(v: RGBA) u32 {
return @bitCast(v);
}
pub fn to_vec4(c: RGBA) [4]f32 {
return .{
@as(f32, @floatFromInt(c.r)) / 255.0,
@as(f32, @floatFromInt(c.g)) / 255.0,
@as(f32, @floatFromInt(c.b)) / 255.0,
@as(f32, @floatFromInt(c.a)) / 255.0,
};
}
};
pub fn u24_to_u8s(v: u24) [3]u8 {
return .{ @truncate(v >> 16), @truncate(v >> 8), @truncate(v) };
}

View file

@ -1,20 +0,0 @@
// Shared GPU cell types used by all GPU renderer backends.
pub const Rgba8 = packed struct(u32) {
a: u8,
b: u8,
g: u8,
r: u8,
pub fn initRgb(r: u8, g: u8, b: u8) Rgba8 {
return .{ .r = r, .g = g, .b = b, .a = 255 };
}
pub fn initRgba(r: u8, g: u8, b: u8, a: u8) Rgba8 {
return .{ .r = r, .g = g, .b = b, .a = a };
}
};
pub const Cell = extern struct {
glyph_index: u32,
background: Rgba8,
foreground: Rgba8,
};

9
src/gui/cell.zig Normal file
View file

@ -0,0 +1,9 @@
// Shared GPU cell types used by all GPU renderer backends.
const RGBA = @import("color").RGBA;
pub const Cell = extern struct {
glyph_index: u32,
background: RGBA,
foreground: RGBA,
};

View file

@ -7,16 +7,14 @@ const std = @import("std");
const sg = @import("sokol").gfx;
const Rasterizer = @import("rasterizer");
const GlyphIndexCache = @import("GlyphIndexCache");
const gui_cell = @import("Cell");
const XY = @import("xy").XY;
const builtin_shader = @import("builtin.glsl.zig");
pub const Font = Rasterizer.Font;
pub const GlyphKind = Rasterizer.GlyphKind;
pub const RasterizerBackend = Rasterizer.Backend;
pub const Cell = gui_cell.Cell;
pub const Color = gui_cell.Rgba8;
const Rgba8 = gui_cell.Rgba8;
pub const Cell = @import("cell").Cell;
pub const RGBA = @import("color").RGBA;
pub const CursorShape = enum(i32) { block = 0, beam = 1, underline = 2 };
@ -25,7 +23,7 @@ pub const CursorInfo = struct {
row: u16 = 0,
col: u16 = 0,
shape: CursorShape = .block,
color: Color = Color.initRgb(255, 255, 255),
color: RGBA = .init(255, 255, 255, 255),
};
const log = std.log.scoped(.gpu);
@ -44,7 +42,7 @@ fn getAtlasCellCount(cell_size: XY(u16)) XY(u16) {
// Shader cell layout for the RGBA32UI cell texture.
// Each texel encodes one terminal cell:
// .r = glyph_index (u32)
// .g = bg packed (Rgba8 bit-cast to u32: r<<24|g<<16|b<<8|a)
// .g = bg packed (RGBA bit-cast to u32: r<<24|g<<16|b<<8|a)
// .b = fg packed (same)
// .a = 0 (reserved)
const ShaderCell = extern struct {
@ -61,7 +59,7 @@ const global = struct {
var glyph_sampler: sg.Sampler = .{};
var cell_sampler: sg.Sampler = .{};
var glyph_cache_arena: std.heap.ArenaAllocator = undefined;
var background: Rgba8 = .{ .r = 19, .g = 19, .b = 19, .a = 255 };
var background: RGBA = .init(0, 255, 255, 255); // default is warning yellow
};
pub fn init(allocator: std.mem.Allocator) !void {
@ -118,7 +116,7 @@ pub fn setFontWeight(font: *Font, w: u8) void {
Rasterizer.setFontWeight(font, w);
}
pub fn setBackground(color: Rgba8) void {
pub fn setBackground(color: RGBA) void {
global.background = color;
}
@ -387,15 +385,15 @@ pub fn paint(
const src = cells[src_row_offset + ci];
shader_cells[dst_row_offset + ci] = .{
.glyph_index = src.glyph_index,
.bg = @bitCast(src.background),
.fg = @bitCast(src.foreground),
.bg = src.background.to_u32(),
.fg = src.foreground.to_u32(),
};
}
for (copy_len..shader_col_count) |ci| {
shader_cells[dst_row_offset + ci] = .{
.glyph_index = blank_glyph_index,
.bg = @bitCast(global.background),
.fg = @bitCast(global.background),
.bg = global.background.to_u32(),
.fg = global.background.to_u32(),
};
}
}
@ -448,10 +446,10 @@ pub fn paint(
bindings.samplers[1] = global.cell_sampler;
sg.applyBindings(bindings);
const sec_color: Color = if (secondary_cursors.len > 0)
const sec_color: RGBA = if (secondary_cursors.len > 0)
secondary_cursors[0].color
else
Color.initRgb(255, 255, 255);
.init(255, 255, 255, 255);
const fs_params = builtin_shader.FsParams{
.cell_size_x = font.cell_size.x,
@ -463,8 +461,8 @@ pub fn paint(
.cursor_row = cursor.row,
.cursor_shape = @intFromEnum(cursor.shape),
.cursor_vis = if (cursor.vis) 1 else 0,
.cursor_color = colorToVec4(cursor.color),
.sec_cursor_color = colorToVec4(sec_color),
.cursor_color = cursor.color.to_vec4(),
.sec_cursor_color = sec_color.to_vec4(),
};
sg.applyUniforms(0, .{
.ptr = &fs_params,
@ -476,15 +474,6 @@ pub fn paint(
// Note: caller (app.zig) calls sg.commit() and window.swapBuffers()
}
fn colorToVec4(c: Color) [4]f32 {
return .{
@as(f32, @floatFromInt(c.r)) / 255.0,
@as(f32, @floatFromInt(c.g)) / 255.0,
@as(f32, @floatFromInt(c.b)) / 255.0,
@as(f32, @floatFromInt(c.a)) / 255.0,
};
}
fn oom(e: error{OutOfMemory}) noreturn {
@panic(@errorName(e));
}

View file

@ -15,6 +15,7 @@ const gpu = @import("gpu");
const thespian = @import("thespian");
const cbor = @import("cbor");
const vaxis = @import("vaxis");
const RGBA = @import("color").RGBA;
const input_translate = @import("input.zig");
const root = @import("soft_root").root;
@ -26,7 +27,6 @@ const log = std.log.scoped(.wio_app);
// can use them without a direct dependency on the gpu module.
pub const CursorInfo = gpu.CursorInfo;
pub const CursorShape = gpu.CursorShape;
pub const GpuColor = gpu.Color;
// Shared state (protected by screen_mutex)
@ -57,7 +57,7 @@ var stop_requested: std.atomic.Value(bool) = .init(false);
// Background color (written from TUI thread, applied by wio thread on each paint).
// Stored as packed RGBA u32 to allow atomic reads/writes.
var background_color: std.atomic.Value(u32) = .init(0x131313ff); // matches gpu.zig default
var background_color: std.atomic.Value(u32) = .init(RGBA.init(0, 255, 255, 255).to_u32()); // warning yellow, we should never see the default
var background_dirty: std.atomic.Value(bool) = .init(false);
var config_arena_instance: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@ -316,7 +316,7 @@ fn saveConfig() void {
log.err("failed to write gui config file", .{});
}
pub fn setBackground(color: gpu.Color) void {
pub fn setBackground(color: RGBA) void {
const color_u32: u32 = (@as(u32, color.r) << 24) | (@as(u32, color.g) << 16) | (@as(u32, color.b) << 8) | color.a;
background_color.store(color_u32, .release);
background_dirty.store(true, .release);
@ -371,19 +371,11 @@ fn maybeReloadFont(win_size: wio.Size, state: *gpu.WindowState, cell_width: *u16
}
}
fn colorFromVaxis(color: vaxis.Cell.Color) gpu.Color {
fn colorFromVaxis(color: vaxis.Cell.Color) RGBA {
return switch (color) {
.default => gpu.Color.initRgb(0, 0, 0),
.index => |idx| blk: {
const xterm = @import("xterm");
const rgb24 = xterm.colors[idx];
break :blk gpu.Color.initRgb(
@truncate(rgb24 >> 16),
@truncate(rgb24 >> 8),
@truncate(rgb24),
);
},
.rgb => |rgb| gpu.Color.initRgb(rgb[0], rgb[1], rgb[2]),
.default => .init(0, 0, 0, 255),
.index => |idx| .from_u24(@import("xterm").colors[idx]),
.rgb => |rgb| .from_u8s(rgb),
};
}

View file

@ -3,6 +3,7 @@ pub const log_name = "renderer";
const std = @import("std");
const cbor = @import("cbor");
const RGBA = @import("color").RGBA;
pub const vaxis = @import("vaxis");
const Style = @import("theme").Style;
const Color = @import("theme").Color;
@ -57,9 +58,9 @@ thread: ?std.Thread = null,
window_ready: bool = false,
cursor_info: app.CursorInfo = .{},
cursor_color: app.GpuColor = app.GpuColor.initRgb(255, 255, 255),
cursor_color: RGBA = .init(255, 255, 255, 255),
secondary_cursors: std.ArrayListUnmanaged(app.CursorInfo) = .{},
secondary_color: app.GpuColor = app.GpuColor.initRgb(255, 255, 255),
secondary_color: RGBA = .init(255, 255, 255, 255),
cursor_blink: bool = false,
blink_on: bool = true,
@ -545,7 +546,7 @@ pub fn show_multi_cursor_yx(self: *Self, y: i32, x: i32) !void {
});
}
fn themeColorToGpu(color: Color) app.GpuColor {
fn themeColorToGpu(color: Color) RGBA {
return .{
.r = @truncate(color.color >> 16),
.g = @truncate(color.color >> 8),