mirror of
https://github.com/fairyglade/ly.git
synced 2026-09-21 11:30:21 +00:00
## What are the changes about? Add optional `bigclock_outline_fg`. When set, each big-clock digit gets a 1-cell outline drawn under the glyph so it stays readable over busy animations. Default is `null` (previous behavior). When the outline is enabled, the gap under the clock is slightly larger so the stroke is not glued to the login box. ## What existing issue(s) does this resolve? Fixes #1030 ## Pre-requisites - [x] I have tested & confirmed the changes work locally - [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md` ### Testing - `zig build -Doptimize=ReleaseSafe` (zig 0.16.0) - Greeter with `bigclock = en` and `bigclock_outline_fg = 0x20000000` over a `dur_file` animation - Default `null` leaves stock look unchanged ### AI usage (per contributing.md) Assisted by an LLM (xAI Grok) for exploration and drafting. I directed the design (optional outline only, no panel/box, stock layout otherwise), reviewed the Zig, fixed an `i2` overflow in the outline loop, verified the build/local greeter, and wrote this PR text. Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1031 Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
295 lines
7.7 KiB
Zig
295 lines
7.7 KiB
Zig
const BigLabel = @This();
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const ly_core = @import("ly-core");
|
|
const interop = ly_core.interop;
|
|
|
|
const en = @import("bigLabelLocales/en.zig");
|
|
const fa = @import("bigLabelLocales/fa.zig");
|
|
const Cell = @import("../Cell.zig");
|
|
const Position = @import("../Position.zig");
|
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
|
const Widget = @import("../Widget.zig");
|
|
|
|
pub const CHAR_WIDTH = 5;
|
|
pub const CHAR_HEIGHT = 5;
|
|
pub const CHAR_SIZE = CHAR_WIDTH * CHAR_HEIGHT;
|
|
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
|
|
pub const O: u32 = 0;
|
|
|
|
const OUTLINE_DIRS = [_][2]i8{
|
|
.{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 },
|
|
.{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 },
|
|
.{ 1, 0 }, .{ 1, 1 },
|
|
};
|
|
|
|
// zig fmt: off
|
|
pub const LocaleChars = struct {
|
|
ZERO: [CHAR_SIZE]u21,
|
|
ONE: [CHAR_SIZE]u21,
|
|
TWO: [CHAR_SIZE]u21,
|
|
THREE: [CHAR_SIZE]u21,
|
|
FOUR: [CHAR_SIZE]u21,
|
|
FIVE: [CHAR_SIZE]u21,
|
|
SIX: [CHAR_SIZE]u21,
|
|
SEVEN: [CHAR_SIZE]u21,
|
|
EIGHT: [CHAR_SIZE]u21,
|
|
NINE: [CHAR_SIZE]u21,
|
|
S: [CHAR_SIZE]u21,
|
|
E: [CHAR_SIZE]u21,
|
|
P: [CHAR_SIZE]u21,
|
|
A: [CHAR_SIZE]u21,
|
|
M: [CHAR_SIZE]u21,
|
|
};
|
|
// zig fmt: on
|
|
|
|
pub const BigLabelLocale = enum {
|
|
en,
|
|
fa,
|
|
};
|
|
|
|
instance: ?Widget = null,
|
|
allocator: ?Allocator = null,
|
|
buffer: *TerminalBuffer,
|
|
text: []const u8,
|
|
max_width: ?usize,
|
|
fg: u32,
|
|
bg: u32,
|
|
outline_fg: ?u32 = null,
|
|
locale: BigLabelLocale,
|
|
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
|
|
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
|
|
component_pos: Position,
|
|
children_pos: Position,
|
|
|
|
pub fn init(
|
|
buffer: *TerminalBuffer,
|
|
text: []const u8,
|
|
max_width: ?usize,
|
|
fg: u32,
|
|
bg: u32,
|
|
outline_fg: ?u32,
|
|
locale: BigLabelLocale,
|
|
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
|
|
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
|
|
) BigLabel {
|
|
return .{
|
|
.instance = null,
|
|
.allocator = null,
|
|
.buffer = buffer,
|
|
.text = text,
|
|
.max_width = max_width,
|
|
.fg = fg,
|
|
.bg = bg,
|
|
.outline_fg = outline_fg,
|
|
.locale = locale,
|
|
.update_fn = update_fn,
|
|
.calculate_timeout_fn = calculate_timeout_fn,
|
|
.component_pos = TerminalBuffer.START_POSITION,
|
|
.children_pos = TerminalBuffer.START_POSITION,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *BigLabel) void {
|
|
if (self.allocator) |allocator| allocator.free(self.text);
|
|
}
|
|
|
|
pub fn widget(self: *BigLabel) *Widget {
|
|
if (self.instance) |*instance| return instance;
|
|
self.instance = Widget.init(
|
|
"BigLabel",
|
|
null,
|
|
self,
|
|
deinit,
|
|
null,
|
|
draw,
|
|
update,
|
|
null,
|
|
calculateTimeout,
|
|
);
|
|
return &self.instance.?;
|
|
}
|
|
|
|
pub fn setTextAlloc(
|
|
self: *BigLabel,
|
|
allocator: Allocator,
|
|
comptime fmt: []const u8,
|
|
args: anytype,
|
|
) !void {
|
|
self.text = try std.fmt.allocPrint(allocator, fmt, args);
|
|
self.allocator = allocator;
|
|
}
|
|
|
|
pub fn setTextBuf(
|
|
self: *BigLabel,
|
|
buffer: []u8,
|
|
comptime fmt: []const u8,
|
|
args: anytype,
|
|
) !void {
|
|
self.text = try std.fmt.bufPrint(buffer, fmt, args);
|
|
self.allocator = null;
|
|
}
|
|
|
|
pub fn setText(self: *BigLabel, text: []const u8) void {
|
|
self.text = text;
|
|
self.allocator = null;
|
|
}
|
|
|
|
pub fn positionX(self: *BigLabel, original_pos: Position) void {
|
|
self.component_pos = original_pos;
|
|
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text) * CHAR_WIDTH);
|
|
}
|
|
|
|
pub fn positionY(self: *BigLabel, original_pos: Position) void {
|
|
self.component_pos = original_pos;
|
|
self.children_pos = original_pos.addY(CHAR_HEIGHT);
|
|
}
|
|
|
|
pub fn positionXY(self: *BigLabel, original_pos: Position) void {
|
|
self.component_pos = original_pos;
|
|
self.children_pos = Position.init(
|
|
TerminalBuffer.strWidth(self.text) * CHAR_WIDTH,
|
|
CHAR_HEIGHT,
|
|
).add(original_pos);
|
|
}
|
|
|
|
pub fn childrenPosition(self: BigLabel) Position {
|
|
return self.children_pos;
|
|
}
|
|
|
|
fn draw(self: *BigLabel) void {
|
|
for (self.text, 0..) |c, i| {
|
|
const x = self.component_pos.x + i * (CHAR_WIDTH + 1);
|
|
const y = self.component_pos.y;
|
|
|
|
if (self.outline_fg) |outline_fg| {
|
|
drawDigitOutline(self, c, x, y, outline_fg);
|
|
}
|
|
|
|
alphaBlit(
|
|
x,
|
|
y,
|
|
self.buffer.width,
|
|
self.buffer.height,
|
|
clockCell(c, self.fg, self.bg, self.locale),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn drawDigitOutline(
|
|
self: *BigLabel,
|
|
char: u8,
|
|
base_x: usize,
|
|
base_y: usize,
|
|
outline_fg: u32,
|
|
) void {
|
|
const pattern = toBigNumber(char, self.locale);
|
|
const stroke = Cell.init(X, outline_fg, TerminalBuffer.Color.DEFAULT);
|
|
|
|
for (0..CHAR_HEIGHT) |y| {
|
|
for (0..CHAR_WIDTH) |x| {
|
|
if (pattern[y * CHAR_WIDTH + x] == O) continue;
|
|
|
|
for (OUTLINE_DIRS) |dir| {
|
|
if (offsetBy(x, dir[0])) |nx| {
|
|
if (offsetBy(y, dir[1])) |ny| {
|
|
if (nx < CHAR_WIDTH and ny < CHAR_HEIGHT and
|
|
pattern[ny * CHAR_WIDTH + nx] != O)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
const sx = offsetBy(base_x + x, dir[0]) orelse continue;
|
|
const sy = offsetBy(base_y + y, dir[1]) orelse continue;
|
|
if (sx >= self.buffer.width or
|
|
sy >= self.buffer.height)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stroke.put(sx, sy) catch {};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn offsetBy(pos: usize, delta: i8) ?usize {
|
|
if (delta < 0) {
|
|
const abs: usize = @intCast(-delta);
|
|
if (pos < abs) return null;
|
|
return pos - abs;
|
|
}
|
|
return pos + @as(usize, @intCast(delta));
|
|
}
|
|
|
|
fn update(self: *BigLabel, context: *anyopaque) !void {
|
|
if (self.update_fn) |update_fn| {
|
|
return @call(
|
|
.auto,
|
|
update_fn,
|
|
.{ self, context },
|
|
);
|
|
}
|
|
}
|
|
|
|
fn calculateTimeout(self: *BigLabel, ctx: *anyopaque) !?usize {
|
|
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
|
|
return @call(
|
|
.auto,
|
|
calculate_timeout_fn,
|
|
.{ self, ctx },
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn clockCell(char: u8, fg: u32, bg: u32, locale: BigLabelLocale) [CHAR_SIZE]Cell {
|
|
var cells: [CHAR_SIZE]Cell = undefined;
|
|
|
|
//@divTrunc(time.microseconds, 500000) != 0)
|
|
const clock_chars = toBigNumber(char, locale);
|
|
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
|
|
|
|
return cells;
|
|
}
|
|
|
|
fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [CHAR_SIZE]Cell) void {
|
|
if (x + CHAR_WIDTH >= tb_width or y + CHAR_HEIGHT >= tb_height) return;
|
|
|
|
for (0..CHAR_HEIGHT) |yy| {
|
|
for (0..CHAR_WIDTH) |xx| {
|
|
const cell = cells[yy * CHAR_WIDTH + xx];
|
|
cell.put(x + xx, y + yy) catch {};
|
|
}
|
|
}
|
|
}
|
|
|
|
fn toBigNumber(char: u8, locale: BigLabelLocale) [CHAR_SIZE]u21 {
|
|
const locale_chars = switch (locale) {
|
|
.fa => fa.locale_chars,
|
|
.en => en.locale_chars,
|
|
};
|
|
return switch (char) {
|
|
'0' => locale_chars.ZERO,
|
|
'1' => locale_chars.ONE,
|
|
'2' => locale_chars.TWO,
|
|
'3' => locale_chars.THREE,
|
|
'4' => locale_chars.FOUR,
|
|
'5' => locale_chars.FIVE,
|
|
'6' => locale_chars.SIX,
|
|
'7' => locale_chars.SEVEN,
|
|
'8' => locale_chars.EIGHT,
|
|
'9' => locale_chars.NINE,
|
|
'p', 'P' => locale_chars.P,
|
|
'a', 'A' => locale_chars.A,
|
|
'm', 'M' => locale_chars.M,
|
|
':' => locale_chars.S,
|
|
else => locale_chars.E,
|
|
};
|
|
}
|