Sistema de rendering dual para zcatgui: Core: - RenderMode enum (simple/fancy) en style.zig - global_render_mode con helpers: isFancy(), setRenderMode() - fillRoundedRect con edge-fade AA en framebuffer.zig (~350 LOC) - Nuevos comandos: rounded_rect, rounded_rect_outline Widgets actualizados: - Button: corner_radius=4, usa roundedRect en fancy mode - Panel: corner_radius=6, show_shadow=true, sombra offset 4px - TextInput: corner_radius=3 - Select: corner_radius=3 - Modal: corner_radius=8, show_shadow=true, sombra offset 6px - Botones y input field del modal también redondeados Técnica edge-fade (de DVUI): - Anti-aliasing por gradiente alfa en bordes - Sin supersampling, mínimo impacto en rendimiento - Bordes suaves sin multisampling +589 líneas, 9 archivos modificados 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
634 lines
20 KiB
Zig
634 lines
20 KiB
Zig
//! Style - Colors and visual styling
|
|
//!
|
|
//! Based on zcatui's style system, adapted for GUI with RGBA colors.
|
|
|
|
const std = @import("std");
|
|
|
|
// =============================================================================
|
|
// Render Mode - Simple vs Fancy
|
|
// =============================================================================
|
|
|
|
/// Render mode controls visual quality vs performance tradeoff
|
|
pub const RenderMode = enum {
|
|
/// Fast rendering: rectangles, no AA on shapes, no shadows
|
|
/// Best for: low-end hardware, SSH, WASM with limited resources
|
|
simple,
|
|
|
|
/// Pretty rendering: rounded corners, edge-fade AA, shadows
|
|
/// Best for: desktop with good CPU, visual polish needed
|
|
fancy,
|
|
};
|
|
|
|
/// Global render mode - widgets check this to decide how to render
|
|
var global_render_mode: RenderMode = .fancy;
|
|
|
|
/// Get current render mode
|
|
pub fn getRenderMode() RenderMode {
|
|
return global_render_mode;
|
|
}
|
|
|
|
/// Set render mode
|
|
pub fn setRenderMode(mode: RenderMode) void {
|
|
global_render_mode = mode;
|
|
}
|
|
|
|
/// Check if fancy rendering is enabled
|
|
pub fn isFancy() bool {
|
|
return global_render_mode == .fancy;
|
|
}
|
|
|
|
/// RGBA Color
|
|
pub const Color = struct {
|
|
r: u8,
|
|
g: u8,
|
|
b: u8,
|
|
a: u8 = 255,
|
|
|
|
const Self = @This();
|
|
|
|
/// Create a color from RGB values
|
|
pub fn rgb(r: u8, g: u8, b: u8) Self {
|
|
return .{ .r = r, .g = g, .b = b, .a = 255 };
|
|
}
|
|
|
|
/// Create a color from RGBA values
|
|
pub fn rgba(r: u8, g: u8, b: u8, a: u8) Self {
|
|
return .{ .r = r, .g = g, .b = b, .a = a };
|
|
}
|
|
|
|
/// Convert to u32 (RGBA format)
|
|
pub fn toU32(self: Self) u32 {
|
|
return (@as(u32, self.r) << 24) |
|
|
(@as(u32, self.g) << 16) |
|
|
(@as(u32, self.b) << 8) |
|
|
@as(u32, self.a);
|
|
}
|
|
|
|
/// Convert to u32 (ABGR format for SDL)
|
|
pub fn toABGR(self: Self) u32 {
|
|
return (@as(u32, self.a) << 24) |
|
|
(@as(u32, self.b) << 16) |
|
|
(@as(u32, self.g) << 8) |
|
|
@as(u32, self.r);
|
|
}
|
|
|
|
/// Blend this color over another
|
|
pub fn blend(self: Self, bg_color: Self) Self {
|
|
if (self.a == 255) return self;
|
|
if (self.a == 0) return bg_color;
|
|
|
|
const alpha = @as(u16, self.a);
|
|
const inv_alpha = 255 - alpha;
|
|
|
|
return .{
|
|
.r = @intCast((@as(u16, self.r) * alpha + @as(u16, bg_color.r) * inv_alpha) / 255),
|
|
.g = @intCast((@as(u16, self.g) * alpha + @as(u16, bg_color.g) * inv_alpha) / 255),
|
|
.b = @intCast((@as(u16, self.b) * alpha + @as(u16, bg_color.b) * inv_alpha) / 255),
|
|
.a = 255,
|
|
};
|
|
}
|
|
|
|
/// Darken color by percentage (0-100)
|
|
pub fn darken(self: Self, percent: u8) Self {
|
|
const factor = @as(u16, 100 - @min(percent, 100));
|
|
return .{
|
|
.r = @intCast((@as(u16, self.r) * factor) / 100),
|
|
.g = @intCast((@as(u16, self.g) * factor) / 100),
|
|
.b = @intCast((@as(u16, self.b) * factor) / 100),
|
|
.a = self.a,
|
|
};
|
|
}
|
|
|
|
/// Lighten color by percentage (0-100)
|
|
pub fn lighten(self: Self, percent: u8) Self {
|
|
const factor = @as(u16, @min(percent, 100));
|
|
return .{
|
|
.r = @intCast(@as(u16, self.r) + ((@as(u16, 255) - self.r) * factor) / 100),
|
|
.g = @intCast(@as(u16, self.g) + ((@as(u16, 255) - self.g) * factor) / 100),
|
|
.b = @intCast(@as(u16, self.b) + ((@as(u16, 255) - self.b) * factor) / 100),
|
|
.a = self.a,
|
|
};
|
|
}
|
|
|
|
/// Return same color with different alpha
|
|
pub fn withAlpha(self: Self, alpha: u8) Self {
|
|
return .{
|
|
.r = self.r,
|
|
.g = self.g,
|
|
.b = self.b,
|
|
.a = alpha,
|
|
};
|
|
}
|
|
|
|
// =========================================================================
|
|
// Predefined colors
|
|
// =========================================================================
|
|
|
|
pub const transparent = Color.rgba(0, 0, 0, 0);
|
|
pub const black = Color.rgb(0, 0, 0);
|
|
pub const white = Color.rgb(255, 255, 255);
|
|
pub const red = Color.rgb(255, 0, 0);
|
|
pub const green = Color.rgb(0, 255, 0);
|
|
pub const blue = Color.rgb(0, 0, 255);
|
|
pub const yellow = Color.rgb(255, 255, 0);
|
|
pub const cyan = Color.rgb(0, 255, 255);
|
|
pub const magenta = Color.rgb(255, 0, 255);
|
|
pub const gray = Color.rgb(128, 128, 128);
|
|
pub const dark_gray = Color.rgb(64, 64, 64);
|
|
pub const light_gray = Color.rgb(192, 192, 192);
|
|
|
|
// UI colors
|
|
pub const background = Color.rgb(30, 30, 30);
|
|
pub const foreground = Color.rgb(220, 220, 220);
|
|
pub const primary = Color.rgb(66, 135, 245);
|
|
pub const secondary = Color.rgb(100, 100, 100);
|
|
pub const success = Color.rgb(76, 175, 80);
|
|
pub const warning = Color.rgb(255, 152, 0);
|
|
pub const danger = Color.rgb(244, 67, 54);
|
|
pub const border = Color.rgb(80, 80, 80);
|
|
};
|
|
|
|
/// Visual style for widgets
|
|
pub const Style = struct {
|
|
foreground: Color = Color.foreground,
|
|
background: Color = Color.background,
|
|
border: ?Color = null,
|
|
border_radius: u8 = 0,
|
|
|
|
const Self = @This();
|
|
|
|
/// Set foreground color
|
|
pub fn fg(self: Self, color: Color) Self {
|
|
var s = self;
|
|
s.foreground = color;
|
|
return s;
|
|
}
|
|
|
|
/// Set background color
|
|
pub fn bg(self: Self, color: Color) Self {
|
|
var s = self;
|
|
s.background = color;
|
|
return s;
|
|
}
|
|
|
|
/// Set border color
|
|
pub fn withBorder(self: Self, color: Color) Self {
|
|
var s = self;
|
|
s.border = color;
|
|
return s;
|
|
}
|
|
};
|
|
|
|
// =============================================================================
|
|
// Theme
|
|
// =============================================================================
|
|
|
|
/// A theme defines colors for all UI elements
|
|
pub const Theme = struct {
|
|
/// Theme name
|
|
name: []const u8 = "custom",
|
|
|
|
// Base colors
|
|
background: Color,
|
|
foreground: Color,
|
|
primary: Color,
|
|
secondary: Color,
|
|
success: Color,
|
|
warning: Color,
|
|
danger: Color,
|
|
border: Color,
|
|
|
|
// Surface colors (panels, cards)
|
|
surface: Color,
|
|
surface_variant: Color,
|
|
|
|
// Text variants
|
|
text_primary: Color,
|
|
text_secondary: Color,
|
|
text_disabled: Color,
|
|
|
|
// Button colors
|
|
button_bg: Color,
|
|
button_fg: Color,
|
|
button_hover: Color,
|
|
button_active: Color,
|
|
button_disabled_bg: Color,
|
|
button_disabled_fg: Color,
|
|
|
|
// Input colors
|
|
input_bg: Color,
|
|
input_fg: Color,
|
|
input_border: Color,
|
|
input_focus_border: Color,
|
|
input_placeholder: Color,
|
|
|
|
// Selection colors
|
|
selection_bg: Color,
|
|
selection_fg: Color,
|
|
|
|
// Header/menu bar
|
|
header_bg: Color,
|
|
header_fg: Color,
|
|
|
|
// Table colors
|
|
table_header_bg: Color,
|
|
table_row_even: Color,
|
|
table_row_odd: Color,
|
|
table_row_hover: Color,
|
|
table_row_selected: Color,
|
|
|
|
// Scrollbar
|
|
scrollbar_track: Color,
|
|
scrollbar_thumb: Color,
|
|
scrollbar_thumb_hover: Color,
|
|
|
|
// Modal/dialog
|
|
modal_overlay: Color,
|
|
modal_bg: Color,
|
|
|
|
const Self = @This();
|
|
|
|
/// Dark theme (default)
|
|
pub const dark = Self{
|
|
.name = "dark",
|
|
.background = Color.rgb(30, 30, 30),
|
|
.foreground = Color.rgb(220, 220, 220),
|
|
.primary = Color.rgb(66, 135, 245),
|
|
.secondary = Color.rgb(100, 100, 100),
|
|
.success = Color.rgb(76, 175, 80),
|
|
.warning = Color.rgb(255, 152, 0),
|
|
.danger = Color.rgb(244, 67, 54),
|
|
.border = Color.rgb(80, 80, 80),
|
|
|
|
.surface = Color.rgb(40, 40, 40),
|
|
.surface_variant = Color.rgb(50, 50, 50),
|
|
|
|
.text_primary = Color.rgb(220, 220, 220),
|
|
.text_secondary = Color.rgb(160, 160, 160),
|
|
.text_disabled = Color.rgb(100, 100, 100),
|
|
|
|
.button_bg = Color.rgb(60, 60, 60),
|
|
.button_fg = Color.rgb(220, 220, 220),
|
|
.button_hover = Color.rgb(80, 80, 80),
|
|
.button_active = Color.rgb(50, 50, 50),
|
|
.button_disabled_bg = Color.rgb(45, 45, 45),
|
|
.button_disabled_fg = Color.rgb(100, 100, 100),
|
|
|
|
.input_bg = Color.rgb(45, 45, 45),
|
|
.input_fg = Color.rgb(220, 220, 220),
|
|
.input_border = Color.rgb(80, 80, 80),
|
|
.input_focus_border = Color.rgb(66, 135, 245),
|
|
.input_placeholder = Color.rgb(120, 120, 120),
|
|
|
|
.selection_bg = Color.rgb(66, 135, 245),
|
|
.selection_fg = Color.rgb(255, 255, 255),
|
|
|
|
.header_bg = Color.rgb(35, 35, 40),
|
|
.header_fg = Color.rgb(200, 200, 200),
|
|
|
|
.table_header_bg = Color.rgb(50, 50, 50),
|
|
.table_row_even = Color.rgb(35, 35, 35),
|
|
.table_row_odd = Color.rgb(40, 40, 40),
|
|
.table_row_hover = Color.rgb(50, 50, 60),
|
|
.table_row_selected = Color.rgb(66, 135, 245),
|
|
|
|
.scrollbar_track = Color.rgb(40, 40, 40),
|
|
.scrollbar_thumb = Color.rgb(80, 80, 80),
|
|
.scrollbar_thumb_hover = Color.rgb(100, 100, 100),
|
|
|
|
.modal_overlay = Color.rgba(0, 0, 0, 180),
|
|
.modal_bg = Color.rgb(45, 45, 50),
|
|
};
|
|
|
|
/// Light theme
|
|
pub const light = Self{
|
|
.name = "light",
|
|
.background = Color.rgb(245, 245, 245),
|
|
.foreground = Color.rgb(30, 30, 30),
|
|
.primary = Color.rgb(33, 150, 243),
|
|
.secondary = Color.rgb(158, 158, 158),
|
|
.success = Color.rgb(76, 175, 80),
|
|
.warning = Color.rgb(255, 152, 0),
|
|
.danger = Color.rgb(244, 67, 54),
|
|
.border = Color.rgb(200, 200, 200),
|
|
|
|
.surface = Color.rgb(255, 255, 255),
|
|
.surface_variant = Color.rgb(240, 240, 240),
|
|
|
|
.text_primary = Color.rgb(30, 30, 30),
|
|
.text_secondary = Color.rgb(100, 100, 100),
|
|
.text_disabled = Color.rgb(180, 180, 180),
|
|
|
|
.button_bg = Color.rgb(230, 230, 230),
|
|
.button_fg = Color.rgb(30, 30, 30),
|
|
.button_hover = Color.rgb(210, 210, 210),
|
|
.button_active = Color.rgb(190, 190, 190),
|
|
.button_disabled_bg = Color.rgb(240, 240, 240),
|
|
.button_disabled_fg = Color.rgb(180, 180, 180),
|
|
|
|
.input_bg = Color.rgb(255, 255, 255),
|
|
.input_fg = Color.rgb(30, 30, 30),
|
|
.input_border = Color.rgb(180, 180, 180),
|
|
.input_focus_border = Color.rgb(33, 150, 243),
|
|
.input_placeholder = Color.rgb(160, 160, 160),
|
|
|
|
.selection_bg = Color.rgb(33, 150, 243),
|
|
.selection_fg = Color.rgb(255, 255, 255),
|
|
|
|
.header_bg = Color.rgb(255, 255, 255),
|
|
.header_fg = Color.rgb(50, 50, 50),
|
|
|
|
.table_header_bg = Color.rgb(240, 240, 240),
|
|
.table_row_even = Color.rgb(255, 255, 255),
|
|
.table_row_odd = Color.rgb(248, 248, 248),
|
|
.table_row_hover = Color.rgb(235, 245, 255),
|
|
.table_row_selected = Color.rgb(33, 150, 243),
|
|
|
|
.scrollbar_track = Color.rgb(240, 240, 240),
|
|
.scrollbar_thumb = Color.rgb(200, 200, 200),
|
|
.scrollbar_thumb_hover = Color.rgb(180, 180, 180),
|
|
|
|
.modal_overlay = Color.rgba(0, 0, 0, 120),
|
|
.modal_bg = Color.rgb(255, 255, 255),
|
|
};
|
|
|
|
/// High contrast dark theme
|
|
pub const high_contrast_dark = Self{
|
|
.name = "high_contrast_dark",
|
|
.background = Color.rgb(0, 0, 0),
|
|
.foreground = Color.rgb(255, 255, 255),
|
|
.primary = Color.rgb(0, 200, 255),
|
|
.secondary = Color.rgb(180, 180, 180),
|
|
.success = Color.rgb(0, 255, 0),
|
|
.warning = Color.rgb(255, 255, 0),
|
|
.danger = Color.rgb(255, 0, 0),
|
|
.border = Color.rgb(255, 255, 255),
|
|
|
|
.surface = Color.rgb(20, 20, 20),
|
|
.surface_variant = Color.rgb(40, 40, 40),
|
|
|
|
.text_primary = Color.rgb(255, 255, 255),
|
|
.text_secondary = Color.rgb(200, 200, 200),
|
|
.text_disabled = Color.rgb(128, 128, 128),
|
|
|
|
.button_bg = Color.rgb(40, 40, 40),
|
|
.button_fg = Color.rgb(255, 255, 255),
|
|
.button_hover = Color.rgb(60, 60, 60),
|
|
.button_active = Color.rgb(20, 20, 20),
|
|
.button_disabled_bg = Color.rgb(30, 30, 30),
|
|
.button_disabled_fg = Color.rgb(100, 100, 100),
|
|
|
|
.input_bg = Color.rgb(0, 0, 0),
|
|
.input_fg = Color.rgb(255, 255, 255),
|
|
.input_border = Color.rgb(255, 255, 255),
|
|
.input_focus_border = Color.rgb(0, 200, 255),
|
|
.input_placeholder = Color.rgb(150, 150, 150),
|
|
|
|
.selection_bg = Color.rgb(0, 200, 255),
|
|
.selection_fg = Color.rgb(0, 0, 0),
|
|
|
|
.header_bg = Color.rgb(0, 0, 0),
|
|
.header_fg = Color.rgb(255, 255, 255),
|
|
|
|
.table_header_bg = Color.rgb(30, 30, 30),
|
|
.table_row_even = Color.rgb(0, 0, 0),
|
|
.table_row_odd = Color.rgb(20, 20, 20),
|
|
.table_row_hover = Color.rgb(40, 40, 60),
|
|
.table_row_selected = Color.rgb(0, 200, 255),
|
|
|
|
.scrollbar_track = Color.rgb(20, 20, 20),
|
|
.scrollbar_thumb = Color.rgb(150, 150, 150),
|
|
.scrollbar_thumb_hover = Color.rgb(200, 200, 200),
|
|
|
|
.modal_overlay = Color.rgba(0, 0, 0, 200),
|
|
.modal_bg = Color.rgb(20, 20, 20),
|
|
};
|
|
|
|
/// Solarized Dark theme
|
|
pub const solarized_dark = Self{
|
|
.name = "solarized_dark",
|
|
.background = Color.rgb(0, 43, 54), // base03
|
|
.foreground = Color.rgb(131, 148, 150), // base0
|
|
.primary = Color.rgb(38, 139, 210), // blue
|
|
.secondary = Color.rgb(88, 110, 117), // base01
|
|
.success = Color.rgb(133, 153, 0), // green
|
|
.warning = Color.rgb(181, 137, 0), // yellow
|
|
.danger = Color.rgb(220, 50, 47), // red
|
|
.border = Color.rgb(88, 110, 117), // base01
|
|
|
|
.surface = Color.rgb(7, 54, 66), // base02
|
|
.surface_variant = Color.rgb(0, 43, 54), // base03
|
|
|
|
.text_primary = Color.rgb(147, 161, 161), // base1
|
|
.text_secondary = Color.rgb(131, 148, 150), // base0
|
|
.text_disabled = Color.rgb(88, 110, 117), // base01
|
|
|
|
.button_bg = Color.rgb(7, 54, 66),
|
|
.button_fg = Color.rgb(147, 161, 161),
|
|
.button_hover = Color.rgb(88, 110, 117),
|
|
.button_active = Color.rgb(0, 43, 54),
|
|
.button_disabled_bg = Color.rgb(0, 43, 54),
|
|
.button_disabled_fg = Color.rgb(88, 110, 117),
|
|
|
|
.input_bg = Color.rgb(0, 43, 54),
|
|
.input_fg = Color.rgb(147, 161, 161),
|
|
.input_border = Color.rgb(88, 110, 117),
|
|
.input_focus_border = Color.rgb(38, 139, 210),
|
|
.input_placeholder = Color.rgb(88, 110, 117),
|
|
|
|
.selection_bg = Color.rgb(38, 139, 210),
|
|
.selection_fg = Color.rgb(253, 246, 227),
|
|
|
|
.header_bg = Color.rgb(7, 54, 66),
|
|
.header_fg = Color.rgb(147, 161, 161),
|
|
|
|
.table_header_bg = Color.rgb(7, 54, 66),
|
|
.table_row_even = Color.rgb(0, 43, 54),
|
|
.table_row_odd = Color.rgb(7, 54, 66),
|
|
.table_row_hover = Color.rgb(88, 110, 117),
|
|
.table_row_selected = Color.rgb(38, 139, 210),
|
|
|
|
.scrollbar_track = Color.rgb(0, 43, 54),
|
|
.scrollbar_thumb = Color.rgb(88, 110, 117),
|
|
.scrollbar_thumb_hover = Color.rgb(101, 123, 131),
|
|
|
|
.modal_overlay = Color.rgba(0, 0, 0, 180),
|
|
.modal_bg = Color.rgb(7, 54, 66),
|
|
};
|
|
|
|
/// Solarized Light theme
|
|
pub const solarized_light = Self{
|
|
.name = "solarized_light",
|
|
.background = Color.rgb(253, 246, 227), // base3
|
|
.foreground = Color.rgb(101, 123, 131), // base00
|
|
.primary = Color.rgb(38, 139, 210), // blue
|
|
.secondary = Color.rgb(147, 161, 161), // base1
|
|
.success = Color.rgb(133, 153, 0), // green
|
|
.warning = Color.rgb(181, 137, 0), // yellow
|
|
.danger = Color.rgb(220, 50, 47), // red
|
|
.border = Color.rgb(147, 161, 161), // base1
|
|
|
|
.surface = Color.rgb(238, 232, 213), // base2
|
|
.surface_variant = Color.rgb(253, 246, 227), // base3
|
|
|
|
.text_primary = Color.rgb(88, 110, 117), // base01
|
|
.text_secondary = Color.rgb(101, 123, 131), // base00
|
|
.text_disabled = Color.rgb(147, 161, 161), // base1
|
|
|
|
.button_bg = Color.rgb(238, 232, 213),
|
|
.button_fg = Color.rgb(88, 110, 117),
|
|
.button_hover = Color.rgb(147, 161, 161),
|
|
.button_active = Color.rgb(253, 246, 227),
|
|
.button_disabled_bg = Color.rgb(253, 246, 227),
|
|
.button_disabled_fg = Color.rgb(147, 161, 161),
|
|
|
|
.input_bg = Color.rgb(253, 246, 227),
|
|
.input_fg = Color.rgb(88, 110, 117),
|
|
.input_border = Color.rgb(147, 161, 161),
|
|
.input_focus_border = Color.rgb(38, 139, 210),
|
|
.input_placeholder = Color.rgb(147, 161, 161),
|
|
|
|
.selection_bg = Color.rgb(38, 139, 210),
|
|
.selection_fg = Color.rgb(253, 246, 227),
|
|
|
|
.header_bg = Color.rgb(238, 232, 213),
|
|
.header_fg = Color.rgb(88, 110, 117),
|
|
|
|
.table_header_bg = Color.rgb(238, 232, 213),
|
|
.table_row_even = Color.rgb(253, 246, 227),
|
|
.table_row_odd = Color.rgb(238, 232, 213),
|
|
.table_row_hover = Color.rgb(147, 161, 161),
|
|
.table_row_selected = Color.rgb(38, 139, 210),
|
|
|
|
.scrollbar_track = Color.rgb(253, 246, 227),
|
|
.scrollbar_thumb = Color.rgb(147, 161, 161),
|
|
.scrollbar_thumb_hover = Color.rgb(131, 148, 150),
|
|
|
|
.modal_overlay = Color.rgba(0, 0, 0, 120),
|
|
.modal_bg = Color.rgb(238, 232, 213),
|
|
};
|
|
};
|
|
|
|
// =============================================================================
|
|
// Theme Manager
|
|
// =============================================================================
|
|
|
|
/// Global theme manager
|
|
pub const ThemeManager = struct {
|
|
/// Current theme
|
|
current: *const Theme,
|
|
|
|
const Self = @This();
|
|
|
|
/// Initialize with default dark theme
|
|
pub fn init() Self {
|
|
return Self{
|
|
.current = &Theme.dark,
|
|
};
|
|
}
|
|
|
|
/// Set current theme
|
|
pub fn setTheme(self: *Self, theme: *const Theme) void {
|
|
self.current = theme;
|
|
}
|
|
|
|
/// Get current theme
|
|
pub fn getTheme(self: Self) *const Theme {
|
|
return self.current;
|
|
}
|
|
|
|
/// Switch to dark theme
|
|
pub fn setDark(self: *Self) void {
|
|
self.current = &Theme.dark;
|
|
}
|
|
|
|
/// Switch to light theme
|
|
pub fn setLight(self: *Self) void {
|
|
self.current = &Theme.light;
|
|
}
|
|
|
|
/// Toggle between dark and light
|
|
pub fn toggle(self: *Self) void {
|
|
if (std.mem.eql(u8, self.current.name, "dark")) {
|
|
self.current = &Theme.light;
|
|
} else {
|
|
self.current = &Theme.dark;
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Global theme manager instance
|
|
var global_theme_manager: ?ThemeManager = null;
|
|
|
|
/// Get global theme manager
|
|
pub fn getThemeManager() *ThemeManager {
|
|
if (global_theme_manager == null) {
|
|
global_theme_manager = ThemeManager.init();
|
|
}
|
|
return &global_theme_manager.?;
|
|
}
|
|
|
|
/// Get current theme (convenience function)
|
|
pub fn currentTheme() *const Theme {
|
|
return getThemeManager().current;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Tests
|
|
// =============================================================================
|
|
|
|
test "Color creation" {
|
|
const c = Color.rgb(100, 150, 200);
|
|
try std.testing.expectEqual(@as(u8, 100), c.r);
|
|
try std.testing.expectEqual(@as(u8, 150), c.g);
|
|
try std.testing.expectEqual(@as(u8, 200), c.b);
|
|
try std.testing.expectEqual(@as(u8, 255), c.a);
|
|
}
|
|
|
|
test "Color darken" {
|
|
const white = Color.white;
|
|
const darkened = white.darken(50);
|
|
try std.testing.expectEqual(@as(u8, 127), darkened.r);
|
|
}
|
|
|
|
test "Color blend" {
|
|
const fg = Color.rgba(255, 0, 0, 128);
|
|
const bg = Color.rgb(0, 0, 255);
|
|
const blended = fg.blend(bg);
|
|
|
|
// Should be purple-ish
|
|
try std.testing.expect(blended.r > 100);
|
|
try std.testing.expect(blended.b > 100);
|
|
}
|
|
|
|
test "Theme dark" {
|
|
const theme = Theme.dark;
|
|
try std.testing.expect(std.mem.eql(u8, theme.name, "dark"));
|
|
try std.testing.expectEqual(@as(u8, 30), theme.background.r);
|
|
}
|
|
|
|
test "Theme light" {
|
|
const theme = Theme.light;
|
|
try std.testing.expect(std.mem.eql(u8, theme.name, "light"));
|
|
try std.testing.expectEqual(@as(u8, 245), theme.background.r);
|
|
}
|
|
|
|
test "ThemeManager toggle" {
|
|
var tm = ThemeManager.init();
|
|
try std.testing.expect(std.mem.eql(u8, tm.current.name, "dark"));
|
|
|
|
tm.toggle();
|
|
try std.testing.expect(std.mem.eql(u8, tm.current.name, "light"));
|
|
|
|
tm.toggle();
|
|
try std.testing.expect(std.mem.eql(u8, tm.current.name, "dark"));
|
|
}
|
|
|
|
test "ThemeManager setTheme" {
|
|
var tm = ThemeManager.init();
|
|
tm.setTheme(&Theme.solarized_dark);
|
|
try std.testing.expect(std.mem.eql(u8, tm.current.name, "solarized_dark"));
|
|
|
|
tm.setTheme(&Theme.high_contrast_dark);
|
|
try std.testing.expect(std.mem.eql(u8, tm.current.name, "high_contrast_dark"));
|
|
}
|