Add the cascade animation as a separate widget

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion
2026-02-10 17:43:55 +01:00
parent f320d3f666
commit d268d5bb45
15 changed files with 276 additions and 148 deletions

View File

@@ -0,0 +1,81 @@
const std = @import("std");
const math = std.math;
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
const Cascade = @This();
buffer: *TerminalBuffer,
current_auth_fails: *usize,
max_auth_fails: usize,
pub fn init(
buffer: *TerminalBuffer,
current_auth_fails: *usize,
max_auth_fails: usize,
) Cascade {
return .{
.buffer = buffer,
.current_auth_fails = current_auth_fails,
.max_auth_fails = max_auth_fails,
};
}
pub fn widget(self: *Cascade) Widget {
return Widget.init(
self,
null,
null,
draw,
null,
null,
);
}
fn draw(self: *Cascade) void {
while (self.current_auth_fails.* >= self.max_auth_fails) {
std.Thread.sleep(std.time.ns_per_ms * 10);
var changed = false;
var y = self.buffer.height - 2;
while (y > 0) : (y -= 1) {
for (0..self.buffer.width) |x| {
const cell = TerminalBuffer.getCell(x, y - 1);
const cell_under = TerminalBuffer.getCell(x, y);
// This shouldn't happen under normal circumstances, but because
// this is a *secret* animation, there's no need to care that much
if (cell == null or cell_under == null) continue;
const char: u8 = @truncate(cell.?.ch);
if (std.ascii.isWhitespace(char)) continue;
const char_under: u8 = @truncate(cell_under.?.ch);
if (!std.ascii.isWhitespace(char_under)) continue;
changed = true;
if ((self.buffer.random.int(u16) % 10) > 7) continue;
cell.?.put(x, y);
var space = Cell.init(
' ',
cell_under.?.fg,
cell_under.?.bg,
);
space.put(x, y - 1);
}
}
if (!changed) {
std.Thread.sleep(std.time.ns_per_s * 7);
self.current_auth_fails.* = 0;
}
TerminalBuffer.presentBuffer();
}
}

View File

@@ -17,14 +17,22 @@ fn length(vec: Vec2) f32 {
}
terminal_buffer: *TerminalBuffer,
timeout: *bool,
frames: u64,
pattern_cos_mod: f32,
pattern_sin_mod: f32,
palette: [palette_len]Cell,
pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) ColorMix {
pub fn init(
terminal_buffer: *TerminalBuffer,
col1: u32,
col2: u32,
col3: u32,
timeout: *bool,
) ColorMix {
return .{
.terminal_buffer = terminal_buffer,
.timeout = timeout,
.frames = 0,
.pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
.pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
@@ -57,6 +65,8 @@ pub fn widget(self: *ColorMix) Widget {
}
fn draw(self: *ColorMix) void {
if (self.timeout.*) return;
self.frames +%= 1;
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;

View File

@@ -13,12 +13,22 @@ pub const SPREAD_MAX = 4;
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
timeout: *bool,
buffer: []u8,
height: u8,
spread: u8,
fire: [STEPS + 1]Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32, fire_height: u8, fire_spread: u8) !Doom {
pub fn init(
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
top_color: u32,
middle_color: u32,
bottom_color: u32,
fire_height: u8,
fire_spread: u8,
timeout: *bool,
) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width);
@@ -42,6 +52,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.timeout = timeout,
.buffer = buffer,
.height = @min(HEIGHT_MAX, fire_height),
.spread = @min(SPREAD_MAX, fire_spread),
@@ -71,6 +82,8 @@ fn realloc(self: *Doom) !void {
}
fn draw(self: *Doom) void {
if (self.timeout.*) return;
for (0..self.terminal_buffer.width) |x| {
// We start from 1 so that we always have the topmost line when spreading fire
for (1..self.terminal_buffer.height) |y| {

View File

@@ -307,6 +307,7 @@ frames: u64,
frame_size: UVec2,
start_pos: IVec2,
full_color: bool,
timeout: *bool,
frame_time: u32,
time_previous: i64,
is_color_format_16: bool,
@@ -357,7 +358,17 @@ fn calc_frame_size(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat) UVec
return .{ frame_width, frame_height };
}
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_file: *LogFile, file_path: []const u8, offset_alignment: DurOffsetAlignment, x_offset: i32, y_offset: i32, full_color: bool) !DurFile {
pub fn init(
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
log_file: *LogFile,
file_path: []const u8,
offset_alignment: DurOffsetAlignment,
x_offset: i32,
y_offset: i32,
full_color: bool,
timeout: *bool,
) !DurFile {
var dur_movie: DurFormat = .init(allocator);
dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) {
@@ -395,6 +406,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_file: *L
.frame_size = frame_size,
.start_pos = start_pos,
.full_color = full_color,
.timeout = timeout,
.dur_movie = dur_movie,
.frame_time = frame_time,
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"),
@@ -425,6 +437,8 @@ fn realloc(self: *DurFile) !void {
}
fn draw(self: *DurFile) void {
if (self.timeout.*) return;
const current_frame = self.dur_movie.frames.items[self.frames];
const buf_width: u32 = @intCast(self.terminal_buffer.width);

View File

@@ -26,11 +26,20 @@ fg_color: u32,
entropy_interval: usize,
frame_delay: usize,
initial_density: f32,
timeout: *bool,
dead_cell: Cell,
width: usize,
height: usize,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32) !GameOfLife {
pub fn init(
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
fg_color: u32,
entropy_interval: usize,
frame_delay: usize,
initial_density: f32,
timeout: *bool,
) !GameOfLife {
const width = terminal_buffer.width;
const height = terminal_buffer.height;
const grid_size = width * height;
@@ -49,6 +58,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3
.entropy_interval = entropy_interval,
.frame_delay = frame_delay,
.initial_density = initial_density,
.timeout = timeout,
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
.width = width,
.height = height,
@@ -94,6 +104,8 @@ fn realloc(self: *GameOfLife) !void {
}
fn draw(self: *GameOfLife) void {
if (self.timeout.*) return;
// Update game state at controlled frame rate
self.frame_counter += 1;
if (self.frame_counter >= self.frame_delay) {

View File

@@ -34,9 +34,18 @@ fg: u32,
head_col: u32,
min_codepoint: u16,
max_codepoint: u16,
timeout: *bool,
default_cell: Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: 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,
timeout: *bool,
) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width);
@@ -53,6 +62,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, hea
.head_col = head_col,
.min_codepoint = min_codepoint,
.max_codepoint = max_codepoint - min_codepoint,
.timeout = timeout,
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
};
}
@@ -84,6 +94,8 @@ fn realloc(self: *Matrix) !void {
}
fn draw(self: *Matrix) void {
if (self.timeout.*) return;
const buf_height = self.terminal_buffer.height;
const buf_width = self.terminal_buffer.width;
self.count += 1;