mirror of https://github.com/fairyglade/ly.git
Add option for eight-color terminal output (#802)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/802 Reviewed-by: AnErrupTion <anerruption@disroot.org> Co-authored-by: Matthew Rothlisberger <mattjrothlis@gmail.com> Co-committed-by: Matthew Rothlisberger <mattjrothlis@gmail.com>
This commit is contained in:
parent
c3d180c213
commit
a7ff18aa16
|
@ -1,14 +1,14 @@
|
|||
# Ly supports 24-bit true color with styling, which means each color is a 32-bit value.
|
||||
# The format is 0xSSRRGGBB, where SS is the styling, RR is red, GG is green, and BB is blue.
|
||||
# Here are the possible styling options:
|
||||
#define TB_BOLD 0x01000000
|
||||
#define TB_UNDERLINE 0x02000000
|
||||
#define TB_REVERSE 0x04000000
|
||||
#define TB_ITALIC 0x08000000
|
||||
#define TB_BLINK 0x10000000
|
||||
#define TB_HI_BLACK 0x20000000
|
||||
#define TB_BRIGHT 0x40000000
|
||||
#define TB_DIM 0x80000000
|
||||
# TB_BOLD 0x01000000
|
||||
# TB_UNDERLINE 0x02000000
|
||||
# TB_REVERSE 0x04000000
|
||||
# TB_ITALIC 0x08000000
|
||||
# TB_BLINK 0x10000000
|
||||
# TB_HI_BLACK 0x20000000
|
||||
# TB_BRIGHT 0x40000000
|
||||
# TB_DIM 0x80000000
|
||||
# Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's
|
||||
# configuration doesn't support using it, you have to manually compute the color value.
|
||||
# Note that, if you want to use the default color value of the terminal, you can use the
|
||||
|
@ -89,6 +89,9 @@ clock = null
|
|||
# CMatrix animation foreground color id
|
||||
cmatrix_fg = 0x0000FF00
|
||||
|
||||
# CMatrix animation character string head color id
|
||||
cmatrix_head_col = 0x01FFFFFF
|
||||
|
||||
# CMatrix animation minimum codepoint. It uses a 16-bit integer
|
||||
# For Japanese characters for example, you can use 0x3000 here
|
||||
cmatrix_min_codepoint = 0x21
|
||||
|
@ -140,6 +143,22 @@ error_fg = 0x01FF0000
|
|||
# Foreground color id
|
||||
fg = 0x00FFFFFF
|
||||
|
||||
# Render true colors (if supported)
|
||||
# If false, output will be in eight-color mode
|
||||
# All eight-color mode color codes:
|
||||
# TB_DEFAULT 0x0000
|
||||
# TB_BLACK 0x0001
|
||||
# TB_RED 0x0002
|
||||
# TB_GREEN 0x0003
|
||||
# TB_YELLOW 0x0004
|
||||
# TB_BLUE 0x0005
|
||||
# TB_MAGENTA 0x0006
|
||||
# TB_CYAN 0x0007
|
||||
# TB_WHITE 0x0008
|
||||
# If full color is off, the styling options still work. The colors are
|
||||
# always 32-bit values with the styling in the most significant byte.
|
||||
full_color = true
|
||||
|
||||
# Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations)
|
||||
# 0 -> Pure Conway's Game of Life (will eventually stabilize)
|
||||
# 10 -> Add entropy every 10 generations (recommended for continuous activity)
|
||||
|
|
|
@ -23,11 +23,11 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
|
|||
|
||||
const levels =
|
||||
[_]Cell{
|
||||
Cell.init(' ', TerminalBuffer.Color.DEFAULT, TerminalBuffer.Color.DEFAULT),
|
||||
Cell.init(0x2591, top_color, TerminalBuffer.Color.DEFAULT),
|
||||
Cell.init(0x2592, top_color, TerminalBuffer.Color.DEFAULT),
|
||||
Cell.init(0x2593, top_color, TerminalBuffer.Color.DEFAULT),
|
||||
Cell.init(0x2588, top_color, TerminalBuffer.Color.DEFAULT),
|
||||
Cell.init(' ', terminal_buffer.bg, terminal_buffer.bg),
|
||||
Cell.init(0x2591, top_color, terminal_buffer.bg),
|
||||
Cell.init(0x2592, top_color, terminal_buffer.bg),
|
||||
Cell.init(0x2593, top_color, terminal_buffer.bg),
|
||||
Cell.init(0x2588, top_color, terminal_buffer.bg),
|
||||
Cell.init(0x2591, middle_color, top_color),
|
||||
Cell.init(0x2592, middle_color, top_color),
|
||||
Cell.init(0x2593, middle_color, top_color),
|
||||
|
|
|
@ -11,8 +11,6 @@ pub const FRAME_DELAY: usize = 8;
|
|||
// Characters change mid-scroll
|
||||
pub const MID_SCROLL_CHANGE = true;
|
||||
|
||||
const DOT_HEAD_COLOR: u32 = @intCast(TerminalBuffer.Color.WHITE | TerminalBuffer.Styling.BOLD);
|
||||
|
||||
const Matrix = @This();
|
||||
|
||||
pub const Dot = struct {
|
||||
|
@ -33,11 +31,12 @@ lines: []Line,
|
|||
frame: usize,
|
||||
count: usize,
|
||||
fg: u32,
|
||||
head_col: u32,
|
||||
min_codepoint: u16,
|
||||
max_codepoint: u16,
|
||||
default_cell: Cell,
|
||||
|
||||
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min_codepoint: u16, max_codepoint: u16) !Matrix {
|
||||
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16) !Matrix {
|
||||
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
|
||||
const lines = try allocator.alloc(Line, terminal_buffer.width);
|
||||
|
||||
|
@ -51,6 +50,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min
|
|||
.frame = 3,
|
||||
.count = 0,
|
||||
.fg = fg,
|
||||
.head_col = head_col,
|
||||
.min_codepoint = min_codepoint,
|
||||
.max_codepoint = max_codepoint - min_codepoint,
|
||||
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
|
||||
|
@ -157,11 +157,13 @@ fn draw(self: *Matrix) void {
|
|||
const dot = self.dots[buf_width * y + x];
|
||||
const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{
|
||||
.ch = @intCast(dot.value.?),
|
||||
.fg = if (dot.is_head) DOT_HEAD_COLOR else self.fg,
|
||||
.fg = if (dot.is_head) self.head_col else self.fg,
|
||||
.bg = self.terminal_buffer.bg,
|
||||
};
|
||||
|
||||
cell.put(x, y - 1);
|
||||
// Fill background in between columns
|
||||
self.default_cell.put(x + 1, y - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ brightness_up_key: ?[]const u8 = "F6",
|
|||
clear_password: bool = false,
|
||||
clock: ?[:0]const u8 = null,
|
||||
cmatrix_fg: u32 = 0x0000FF00,
|
||||
cmatrix_head_col: u32 = 0x01FFFFFF,
|
||||
cmatrix_min_codepoint: u16 = 0x21,
|
||||
cmatrix_max_codepoint: u16 = 0x7B,
|
||||
colormix_col1: u32 = 0x00FF0000,
|
||||
|
@ -40,6 +41,7 @@ doom_bottom_color: u32 = 0x00FFFFFF,
|
|||
error_bg: u32 = 0x00000000,
|
||||
error_fg: u32 = 0x01FF0000,
|
||||
fg: u32 = 0x00FFFFFF,
|
||||
full_color: bool = true,
|
||||
gameoflife_fg: u32 = 0x0000FF00,
|
||||
gameoflife_entropy_interval: usize = 10,
|
||||
gameoflife_frame_delay: usize = 6,
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
// The migrator ensures compatibility with <=0.6.0 configuration files
|
||||
// The migrator ensures compatibility with older configuration files
|
||||
// Properties removed or changed since 0.6.0
|
||||
// Color codes interpreted differently since 1.1.0
|
||||
|
||||
const std = @import("std");
|
||||
const ini = @import("zigini");
|
||||
const Config = @import("Config.zig");
|
||||
const Save = @import("Save.zig");
|
||||
const enums = @import("../enums.zig");
|
||||
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
|
||||
|
||||
const Color = TerminalBuffer.Color;
|
||||
const Styling = TerminalBuffer.Styling;
|
||||
|
||||
const color_properties = [_][]const u8{
|
||||
"bg",
|
||||
|
@ -16,6 +22,10 @@ const color_properties = [_][]const u8{
|
|||
"error_fg",
|
||||
"fg",
|
||||
};
|
||||
|
||||
var set_color_properties =
|
||||
[_]bool{ false, false, false, false, false, false, false, false, false };
|
||||
|
||||
const removed_properties = [_][]const u8{
|
||||
"wayland_specifier",
|
||||
"max_desktop_len",
|
||||
|
@ -32,6 +42,8 @@ const removed_properties = [_][]const u8{
|
|||
var temporary_allocator = std.heap.page_allocator;
|
||||
var buffer = std.mem.zeroes([10 * color_properties.len]u8);
|
||||
|
||||
pub var auto_eight_colors: bool = true;
|
||||
|
||||
pub var maybe_animate: ?bool = null;
|
||||
pub var maybe_save_file: ?[]const u8 = null;
|
||||
|
||||
|
@ -62,15 +74,26 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||
return mapped_field;
|
||||
}
|
||||
|
||||
inline for (color_properties) |property| {
|
||||
inline for (color_properties, &set_color_properties) |property, *status| {
|
||||
if (std.mem.eql(u8, field.key, property)) {
|
||||
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
|
||||
const color = std.fmt.parseInt(u16, field.value, 0) catch return field;
|
||||
var mapped_field = field;
|
||||
// Color has been set; it won't be overwritten if we default to eight-color output
|
||||
status.* = true;
|
||||
|
||||
mapped_field.value = mapColor(color) catch return field;
|
||||
mapped_config_fields = true;
|
||||
return mapped_field;
|
||||
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
|
||||
// If they're all using eight-color codes, we start in eight-color mode
|
||||
const color = std.fmt.parseInt(u16, field.value, 0) catch {
|
||||
auto_eight_colors = false;
|
||||
return field;
|
||||
};
|
||||
|
||||
const color_no_styling = color & 0x00FF;
|
||||
const styling_only = color & 0xFF00;
|
||||
|
||||
// If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM,
|
||||
// we have an invalid color, so do not use eight-color mode
|
||||
if (color_no_styling > 0x0008 or styling_only > 0x8000) auto_eight_colors = false;
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,14 +154,45 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||
return mapped_field;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "full_color")) {
|
||||
// If color mode is defined, definitely don't set it automatically
|
||||
auto_eight_colors = false;
|
||||
return field;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
// This is the stuff we only handle after reading the config.
|
||||
// For example, the "animate" field could come after "animation"
|
||||
pub fn lateConfigFieldHandler(animation: *enums.Animation) void {
|
||||
pub fn lateConfigFieldHandler(config: *Config) void {
|
||||
if (maybe_animate) |animate| {
|
||||
if (!animate) animation.* = .none;
|
||||
if (!animate) config.*.animation = .none;
|
||||
}
|
||||
|
||||
if (auto_eight_colors) {
|
||||
// Valid config file predates true-color mode
|
||||
// Will use eight-color output instead
|
||||
config.full_color = false;
|
||||
|
||||
// We cannot rely on Config defaults when in eight-color mode,
|
||||
// because they will appear as undesired colors.
|
||||
// Instead set color properties to matching eight-color codes
|
||||
config.doom_top_color = Color.ECOL_RED;
|
||||
config.doom_middle_color = Color.ECOL_YELLOW;
|
||||
config.doom_bottom_color = Color.ECOL_WHITE;
|
||||
config.cmatrix_head_col = Styling.BOLD | Color.ECOL_WHITE;
|
||||
|
||||
// These may be in the config, so only change those which were not set
|
||||
if (!set_color_properties[0]) config.bg = Color.DEFAULT;
|
||||
if (!set_color_properties[1]) config.border_fg = Color.ECOL_WHITE;
|
||||
if (!set_color_properties[2]) config.cmatrix_fg = Color.ECOL_GREEN;
|
||||
if (!set_color_properties[3]) config.colormix_col1 = Color.ECOL_RED;
|
||||
if (!set_color_properties[4]) config.colormix_col2 = Color.ECOL_BLUE;
|
||||
if (!set_color_properties[5]) config.colormix_col3 = Color.ECOL_BLACK;
|
||||
if (!set_color_properties[6]) config.error_bg = Color.DEFAULT;
|
||||
if (!set_color_properties[7]) config.error_fg = Styling.BOLD | Color.ECOL_RED;
|
||||
if (!set_color_properties[8]) config.fg = Color.ECOL_WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,33 +226,3 @@ pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
|
|||
|
||||
return save;
|
||||
}
|
||||
|
||||
fn mapColor(color: u16) ![]const u8 {
|
||||
const color_no_styling = color & 0x00FF;
|
||||
const styling_only = color & 0xFF00;
|
||||
|
||||
// If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM,
|
||||
// we have an invalid color, so return an error
|
||||
if (color_no_styling > 0x0008 or styling_only > 0x8000) return error.InvalidColor;
|
||||
|
||||
var new_color: u32 = switch (color_no_styling) {
|
||||
0x0000 => 0x00000000, // Default
|
||||
0x0001 => 0x20000000, // "Hi-black" styling
|
||||
0x0002 => 0x00FF0000, // Red
|
||||
0x0003 => 0x0000FF00, // Green
|
||||
0x0004 => 0x00FFFF00, // Yellow
|
||||
0x0005 => 0x000000FF, // Blue
|
||||
0x0006 => 0x00FF00FF, // Magenta
|
||||
0x0007 => 0x0000FFFF, // Cyan
|
||||
0x0008 => 0x00FFFFFF, // White
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// Only applying styling if color isn't black and styling isn't also black
|
||||
if (!(new_color == 0x20000000 and styling_only == 0x20000000)) {
|
||||
// Shift styling by 16 to the left to apply it to the new 32-bit color
|
||||
new_color |= @as(u32, @intCast(styling_only)) << 16;
|
||||
}
|
||||
|
||||
return try std.fmt.bufPrint(&buffer, "0x{X}", .{new_color});
|
||||
}
|
||||
|
|
23
src/main.zig
23
src/main.zig
|
@ -170,7 +170,9 @@ pub fn main() !void {
|
|||
}) catch migrator.tryMigrateSaveFile(&user_buf);
|
||||
}
|
||||
|
||||
migrator.lateConfigFieldHandler(&config.animation);
|
||||
if (!config_load_failed) {
|
||||
migrator.lateConfigFieldHandler(&config);
|
||||
}
|
||||
} else {
|
||||
const config_path = build_options.config_directory ++ "/ly/config.ini";
|
||||
|
||||
|
@ -198,7 +200,9 @@ pub fn main() !void {
|
|||
}) catch migrator.tryMigrateSaveFile(&user_buf);
|
||||
}
|
||||
|
||||
migrator.lateConfigFieldHandler(&config.animation);
|
||||
if (!config_load_failed) {
|
||||
migrator.lateConfigFieldHandler(&config);
|
||||
}
|
||||
}
|
||||
|
||||
var log_file: std.fs.File = undefined;
|
||||
|
@ -250,7 +254,13 @@ pub fn main() !void {
|
|||
};
|
||||
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
||||
|
||||
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
|
||||
if (config.full_color) {
|
||||
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
|
||||
try log_writer.writeAll("termbox2 set to 24-bit color output mode\n");
|
||||
} else {
|
||||
try log_writer.writeAll("termbox2 set to eight-color output mode\n");
|
||||
}
|
||||
|
||||
_ = termbox.tb_clear();
|
||||
|
||||
// Let's take some precautions here and clear the back buffer as well
|
||||
|
@ -431,7 +441,7 @@ pub fn main() !void {
|
|||
animation = doom.animation();
|
||||
},
|
||||
.matrix => {
|
||||
var matrix = try Matrix.init(allocator, &buffer, config.cmatrix_fg, config.cmatrix_min_codepoint, config.cmatrix_max_codepoint);
|
||||
var matrix = try Matrix.init(allocator, &buffer, config.cmatrix_fg, config.cmatrix_head_col, config.cmatrix_min_codepoint, config.cmatrix_max_codepoint);
|
||||
animation = matrix.animation();
|
||||
},
|
||||
.colormix => {
|
||||
|
@ -866,7 +876,10 @@ pub fn main() !void {
|
|||
|
||||
// Take back control of the TTY
|
||||
_ = termbox.tb_init();
|
||||
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
|
||||
|
||||
if (config.full_color) {
|
||||
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
|
||||
}
|
||||
|
||||
const auth_err = shared_err.readError();
|
||||
if (auth_err) |err| {
|
||||
|
|
|
@ -31,14 +31,22 @@ pub const Styling = struct {
|
|||
|
||||
pub const Color = struct {
|
||||
pub const DEFAULT = 0x00000000;
|
||||
pub const BLACK = Styling.HI_BLACK;
|
||||
pub const RED = 0x00FF0000;
|
||||
pub const GREEN = 0x0000FF00;
|
||||
pub const YELLOW = 0x00FFFF00;
|
||||
pub const BLUE = 0x000000FF;
|
||||
pub const MAGENTA = 0x00FF00FF;
|
||||
pub const CYAN = 0x0000FFFF;
|
||||
pub const WHITE = 0x00FFFFFF;
|
||||
pub const TRUE_BLACK = Styling.HI_BLACK;
|
||||
pub const TRUE_RED = 0x00FF0000;
|
||||
pub const TRUE_GREEN = 0x0000FF00;
|
||||
pub const TRUE_YELLOW = 0x00FFFF00;
|
||||
pub const TRUE_BLUE = 0x000000FF;
|
||||
pub const TRUE_MAGENTA = 0x00FF00FF;
|
||||
pub const TRUE_CYAN = 0x0000FFFF;
|
||||
pub const TRUE_WHITE = 0x00FFFFFF;
|
||||
pub const ECOL_BLACK = 1;
|
||||
pub const ECOL_RED = 2;
|
||||
pub const ECOL_GREEN = 3;
|
||||
pub const ECOL_YELLOW = 4;
|
||||
pub const ECOL_BLUE = 5;
|
||||
pub const ECOL_MAGENTA = 6;
|
||||
pub const ECOL_CYAN = 7;
|
||||
pub const ECOL_WHITE = 8;
|
||||
};
|
||||
|
||||
random: Random,
|
||||
|
|
Loading…
Reference in New Issue