mirror of
https://codeberg.org/fairyglade/ly.git
synced 2026-08-24 06:24:20 +02:00
241 lines
7.1 KiB
Zig
241 lines
7.1 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const Random = std.Random;
|
|
|
|
const ly_ui = @import("ly-ui");
|
|
const Cell = ly_ui.Cell;
|
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
|
const Widget = ly_ui.Widget;
|
|
|
|
const ly_core = ly_ui.ly_core;
|
|
const interop = ly_core.interop;
|
|
const TimeOfDay = interop.TimeOfDay;
|
|
|
|
pub const FRAME_DELAY: usize = 8;
|
|
|
|
// Characters change mid-scroll
|
|
pub const MID_SCROLL_CHANGE = true;
|
|
|
|
const Matrix = @This();
|
|
|
|
pub const Dot = struct {
|
|
value: ?usize,
|
|
is_head: bool,
|
|
};
|
|
|
|
pub const Line = struct {
|
|
space: usize,
|
|
length: usize,
|
|
update: usize,
|
|
};
|
|
|
|
instance: ?Widget = null,
|
|
start_time: TimeOfDay,
|
|
allocator: Allocator,
|
|
terminal_buffer: *TerminalBuffer,
|
|
dots: []Dot,
|
|
lines: []Line,
|
|
frame: usize,
|
|
count: usize,
|
|
fg: u32,
|
|
head_col: u32,
|
|
min_codepoint: u16,
|
|
max_codepoint: u16,
|
|
animate: *bool,
|
|
timeout_sec: u12,
|
|
frame_delay: u16,
|
|
default_cell: Cell,
|
|
|
|
pub fn init(
|
|
allocator: Allocator,
|
|
terminal_buffer: *TerminalBuffer,
|
|
fg: u32,
|
|
head_col: u32,
|
|
min_codepoint: u16,
|
|
max_codepoint: u16,
|
|
animate: *bool,
|
|
timeout_sec: u12,
|
|
frame_delay: u16,
|
|
) !Matrix {
|
|
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
|
|
const lines = try allocator.alloc(Line, terminal_buffer.width);
|
|
|
|
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
|
|
|
|
return .{
|
|
.instance = null,
|
|
.start_time = try interop.getTimeOfDay(),
|
|
.allocator = allocator,
|
|
.terminal_buffer = terminal_buffer,
|
|
.dots = dots,
|
|
.lines = lines,
|
|
.frame = 3,
|
|
.count = 0,
|
|
.fg = fg,
|
|
.head_col = head_col,
|
|
.min_codepoint = min_codepoint,
|
|
.max_codepoint = max_codepoint - min_codepoint,
|
|
.animate = animate,
|
|
.timeout_sec = timeout_sec,
|
|
.frame_delay = frame_delay,
|
|
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
|
|
};
|
|
}
|
|
|
|
pub fn widget(self: *Matrix) *Widget {
|
|
if (self.instance) |*instance| return instance;
|
|
self.instance = Widget.init(
|
|
"Matrix",
|
|
null,
|
|
self,
|
|
deinit,
|
|
realloc,
|
|
draw,
|
|
update,
|
|
null,
|
|
calculateTimeout,
|
|
);
|
|
return &self.instance.?;
|
|
}
|
|
|
|
fn deinit(self: *Matrix) void {
|
|
self.allocator.free(self.dots);
|
|
self.allocator.free(self.lines);
|
|
}
|
|
|
|
fn realloc(self: *Matrix) !void {
|
|
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1));
|
|
const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);
|
|
|
|
initBuffers(dots, lines, self.terminal_buffer.width, self.terminal_buffer.height, self.terminal_buffer.random);
|
|
|
|
self.dots = dots;
|
|
self.lines = lines;
|
|
}
|
|
|
|
fn draw(self: *Matrix) void {
|
|
if (!self.animate.*) return;
|
|
|
|
const buf_height = self.terminal_buffer.height;
|
|
const buf_width = self.terminal_buffer.width;
|
|
self.count += 1;
|
|
if (self.count > FRAME_DELAY) {
|
|
self.frame += 1;
|
|
if (self.frame > 4) self.frame = 1;
|
|
self.count = 0;
|
|
|
|
var x: usize = 0;
|
|
while (x < buf_width) : (x += 2) {
|
|
var tail: usize = 0;
|
|
var line = &self.lines[x];
|
|
if (self.frame <= line.update) continue;
|
|
|
|
if (self.dots[x].value == null and self.dots[buf_width + x].value == ' ') {
|
|
if (line.space > 0) {
|
|
line.space -= 1;
|
|
} else {
|
|
const randint = self.terminal_buffer.random.int(u16);
|
|
const h = buf_height;
|
|
line.length = @mod(randint, h - 3) + 3;
|
|
self.dots[x].value = @mod(randint, self.max_codepoint) + self.min_codepoint;
|
|
line.space = @mod(randint, h + 1);
|
|
}
|
|
}
|
|
|
|
var y: usize = 0;
|
|
var first_col = true;
|
|
var seg_len: u64 = 0;
|
|
height_it: while (y <= buf_height) : (y += 1) {
|
|
var dot = &self.dots[buf_width * y + x];
|
|
// Skip over spaces
|
|
while (y <= buf_height and (dot.value == ' ' or dot.value == null)) {
|
|
y += 1;
|
|
if (y > buf_height) break :height_it;
|
|
dot = &self.dots[buf_width * y + x];
|
|
}
|
|
|
|
// Find the head of this column
|
|
tail = y;
|
|
seg_len = 0;
|
|
while (y <= buf_height and dot.value != ' ' and dot.value != null) {
|
|
dot.is_head = false;
|
|
if (MID_SCROLL_CHANGE) {
|
|
const randint = self.terminal_buffer.random.int(u16);
|
|
if (@mod(randint, 8) == 0) {
|
|
dot.value = @mod(randint, self.max_codepoint) + self.min_codepoint;
|
|
}
|
|
}
|
|
|
|
y += 1;
|
|
seg_len += 1;
|
|
// Head's down offscreen
|
|
if (y > buf_height) {
|
|
self.dots[buf_width * tail + x].value = ' ';
|
|
break :height_it;
|
|
}
|
|
dot = &self.dots[buf_width * y + x];
|
|
}
|
|
|
|
const randint = self.terminal_buffer.random.int(u16);
|
|
dot.value = @mod(randint, self.max_codepoint) + self.min_codepoint;
|
|
dot.is_head = true;
|
|
|
|
if (seg_len > line.length or !first_col) {
|
|
self.dots[buf_width * tail + x].value = ' ';
|
|
self.dots[x].value = null;
|
|
}
|
|
first_col = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
var x: usize = 0;
|
|
while (x < buf_width) : (x += 2) {
|
|
var y: usize = 1;
|
|
while (y <= buf_height) : (y += 1) {
|
|
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) self.head_col else self.fg,
|
|
.bg = self.terminal_buffer.bg,
|
|
};
|
|
|
|
cell.put(x, y - 1) catch {};
|
|
// Fill background in between columns
|
|
self.default_cell.put(x + 1, y - 1) catch {};
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update(self: *Matrix, _: *anyopaque) !void {
|
|
const time = try interop.getTimeOfDay();
|
|
|
|
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
|
|
self.animate.* = false;
|
|
}
|
|
}
|
|
|
|
fn calculateTimeout(self: *Matrix, _: *anyopaque) !?usize {
|
|
return self.frame_delay;
|
|
}
|
|
|
|
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
|
|
var y: usize = 0;
|
|
while (y <= height) : (y += 1) {
|
|
var x: usize = 0;
|
|
while (x < width) : (x += 2) {
|
|
dots[y * width + x].value = null;
|
|
}
|
|
}
|
|
|
|
var x: usize = 0;
|
|
while (x < width) : (x += 2) {
|
|
var line = lines[x];
|
|
line.space = @mod(random.int(u16), height) + 1;
|
|
line.length = @mod(random.int(u16), height - 3) + 3;
|
|
line.update = @mod(random.int(u16), 3) + 1;
|
|
lines[x] = line;
|
|
|
|
dots[width + x].value = ' ';
|
|
}
|
|
}
|