diff --git a/src/cli.zig b/src/cli.zig index f54ce5d..c4bb5a0 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -103,7 +103,7 @@ pub const setup_cmd: CommandT = .{ }, .{ .name = "style", - .description = "Set output style (default, columns, crypto, grid, blocks).", + .description = "Set output style (default, columns, crypto, grid, blocks, rain).", .short_name = 's', .long_name = "style", .val = ValueT.ofType(Style, .{ .name = "style_val", .default_val = Style.default, .alias_child_type = "string" }), diff --git a/src/main.zig b/src/main.zig index 12dccdb..fc8598d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -25,7 +25,7 @@ const FRAME = 39730492; pub const Speed = enum { slow, fast }; pub const Mode = enum { binary, decimal, hexadecimal, textual }; -pub const Style = enum { default, columns, crypto, grid, blocks }; +pub const Style = enum { default, columns, crypto, grid, blocks, rain }; pub const Color = enum(u32) { default = tb.TB_DEFAULT, red = tb.TB_RED, green = tb.TB_GREEN, blue = tb.TB_BLUE, yellow = tb.TB_YELLOW, magenta = tb.TB_MAGENTA }; var sbuf: [2]u8 = undefined; @@ -42,6 +42,7 @@ const Core = struct { bg: u32 = tb.TB_DEFAULT, width: i32 = 0, height: i32 = 0, + columns: ?std.ArrayListAligned(?Column, null) = null, width_gaps: ?std.ArrayListAligned(u32, null) = null, height_gaps: ?std.ArrayListAligned(u32, null) = null, @@ -71,6 +72,15 @@ const Core = struct { self.height_gaps = try getNthValues(self.height, adv, self.allocator); } + fn updateColumns(self: *Core) !void { + for (self.columns.?.items) |column| { + column.?.chars.deinit(); + } + + self.columns.?.clearAndFree(); + try self.columns.?.ensureTotalCapacity(@as(u32, @intCast(self.width))); + } + fn updateStyle(self: *Core, style: Style) !void { if (style == Style.grid) { self.mutex.lock(); @@ -95,6 +105,11 @@ const Core = struct { defer self.mutex.unlock(); try self.updateWidthSec(4); + } else if (style == Style.rain) { + self.mutex.lock(); + defer self.mutex.unlock(); + + try self.updateColumns(); } } @@ -105,6 +120,10 @@ const Core = struct { fn start(self: *Core) void { _ = tb.tb_init(); + if (self.columns == null) { + self.columns = std.ArrayList(?Column).init(self.allocator); + } + if (self.width_gaps == null) { self.width_gaps = std.ArrayList(u32).init(self.allocator); } @@ -123,6 +142,14 @@ const Core = struct { _ = tb.tb_shutdown(); } + if (self.columns) |columns| { + for (columns.items) |column| { + column.?.chars.deinit(); + } + + columns.deinit(); + } + if (self.width_gaps != null) { self.width_gaps.?.deinit(); } @@ -191,74 +218,219 @@ const Handler = struct { } }; +const Char = struct { i: u8, p: u32, b: u32 }; + +const Column = struct { + chars: std.ArrayList(?Char), + printing: bool = false, + + fn init(allocator: std.mem.Allocator) Column { + return .{ + .chars = std.ArrayList(?Char).init(allocator), + }; + } + + fn strLen(self: *Column) usize { + var len: usize = 0; + var target: usize = 0; + var check = true; + + while (check) { + if (self.chars.items[target] != null) { + len += 1; + target += 1; + } else { + check = false; + } + } + + return len; + } + + fn newChar(self: *Column, core: *Core, mode: Mode, rand: std.rand.Random) !void { + const rand_int = switch (mode) { + .binary => rand.int(u1), + .decimal => rand.uintLessThan(u8, 10), + .hexadecimal => rand.int(u4), + .textual => rand.uintLessThan(u8, 76), + }; + + var color = @intFromEnum(core.color); + var pulse = core.bg; + + const bold = rand.boolean(); + + if (core.pulse) { + const blank = @mod(rand.int(u8), 255); + + // small probability + if (blank >= 254) { + pulse = core.bg | tb.TB_REVERSE; + } + } + + if (bold) { + color = color | tb.TB_BOLD; + } + + try self.chars.insert(0, Char{ .i = rand_int, .p = pulse, .b = color }); + + if (core.pulse) { + core.bg = tb.TB_DEFAULT; + } + } + + fn skip(self: *Column) !void { + try self.chars.append(null); + } +}; + fn printCells(core: *Core, handler: *Handler, rand: std.rand.Random) !void { handler.mutex.lock(); defer handler.mutex.unlock(); if (!handler.pause) { - core.setRendering(true); - - for (1..@intCast(core.width)) |w| { - if (handler.style != Style.default) { - if (checkSec(&core.width_gaps.?, w)) { - continue; - } - } + _ = tb.tb_clear(); + if (handler.style != .rain) { + core.setRendering(true); - for (1..@intCast(core.height)) |h| { + for (0..@intCast(core.width)) |w| { if (handler.style != Style.default) { - if (checkSec(&core.height_gaps.?, h)) { + if (checkSec(&core.width_gaps.?, w)) { continue; } } - const rand_int = switch (handler.mode) { - .binary => rand.int(u1), - .decimal => rand.uintLessThan(u8, 10), - .hexadecimal => rand.int(u4), - .textual => rand.uintLessThan(u8, 76), - }; + for (0..@intCast(core.height)) |h| { + if (handler.style != Style.default) { + if (checkSec(&core.height_gaps.?, h)) { + continue; + } + } + + const rand_int = switch (handler.mode) { + .binary => rand.int(u1), + .decimal => rand.uintLessThan(u8, 10), + .hexadecimal => rand.int(u4), + .textual => rand.uintLessThan(u8, 76), + }; + + var color = @intFromEnum(core.color); + + const bold = rand.boolean(); + + if (core.pulse) { + const blank = @mod(rand.int(u8), 255); + + // small probability + if (blank >= 254) { + core.bg = core.bg | tb.TB_REVERSE; + } + } + + if (bold) { + color = color | tb.TB_BOLD; + } - var color = @intFromEnum(core.color); + const char: [:0]u8 = switch (handler.mode) { + .binary, .decimal => try std.fmt.bufPrintZ(&sbuf, "{d}", .{rand_int}), + .hexadecimal => try std.fmt.bufPrintZ(&mbuf, "{c}", .{assets.hex_chars[rand_int]}), + .textual => try std.fmt.bufPrintZ(&lbuf, "{u}", .{assets.tex_chars[rand_int]}), + }; - const bold = rand.boolean(); + tbPrint(w, h, color, core.bg, char); - if (core.pulse) { - const blank = @mod(rand.int(u8), 255); + if (core.pulse) { + core.bg = tb.TB_DEFAULT; + } + } + } + } else { + // init columns + if (core.columns.?.items.len != core.width) { + for (0..@intCast(core.width)) |_| { + var column = Column.init(core.allocator); + try column.chars.append(null); + try core.columns.?.append(column); + } - // small probability - if (blank >= 254) { - core.bg = core.bg | tb.TB_REVERSE; + for (0..@intCast(core.width)) |w| { + for (0..@intCast(core.height)) |_| { + try core.columns.?.items[w].?.chars.append(null); } } + } + + for (0..@intCast(core.width)) |w| { + if (rand.boolean()) continue; + if (core.columns.?.items[w].?.chars.items.len == core.height) { + const old_char = core.columns.?.items[w].?.chars.pop(); + core.allocator.destroy(&old_char); + } + + if (rand.uintLessThan(u3, 7) < 5) { + try core.columns.?.items[w].?.chars.insert(0, null); + continue; + } + + const str_len = core.columns.?.items[w].?.strLen(); + + if ((str_len == 0) and rand.boolean()) { + try core.columns.?.items[w].?.chars.insert(0, null); + continue; + } - if (bold) { - color = color | tb.TB_BOLD; + if (str_len < @as(u32, @intCast(core.height)) / 2) { + if (str_len < 12) { + try core.columns.?.items[w].?.newChar(core, handler.mode, rand); + } else { + try core.columns.?.items[w].?.chars.insert(0, null); + } + + try core.columns.?.items[w].?.newChar(core, handler.mode, rand); + } else { + try core.columns.?.items[w].?.chars.insert(0, null); } + } - const char: [:0]u8 = switch (handler.mode) { - .binary, .decimal => try std.fmt.bufPrintZ(&sbuf, "{d}", .{rand_int}), - .hexadecimal => try std.fmt.bufPrintZ(&mbuf, "{c}", .{assets.hex_chars[rand_int]}), - .textual => try std.fmt.bufPrintZ(&lbuf, "{u}", .{assets.tex_chars[rand_int]}), - }; + for (0..core.columns.?.items.len) |w| { + h_loop: for (0..@intCast(core.height)) |h| { + const column_char = core.columns.?.items[w].?.chars.items[h]; + if (column_char == null) { + continue :h_loop; + } - _ = tb.tb_print(@intCast(w), @intCast(h), @intCast(color), @intCast(core.bg), char); + const char: [:0]u8 = switch (handler.mode) { + .binary, .decimal => try std.fmt.bufPrintZ(&sbuf, "{d}", .{column_char.?.i}), + .hexadecimal => try std.fmt.bufPrintZ(&mbuf, "{c}", .{assets.hex_chars[column_char.?.i]}), + .textual => try std.fmt.bufPrintZ(&lbuf, "{u}", .{assets.tex_chars[column_char.?.i]}), + }; - if (core.pulse) { - core.bg = tb.TB_DEFAULT; + tbPrint(w, h, column_char.?.b, column_char.?.p, char); } } } _ = tb.tb_present(); core.setRendering(false); - std.time.sleep(switch (handler.speed) { - .slow => FRAME * 2, - .fast => FRAME, - }); + if (handler.style != .rain) { + std.time.sleep(switch (handler.speed) { + .slow => FRAME * 2, + .fast => FRAME, + }); + } else { + std.time.sleep(switch (handler.speed) { + .slow => FRAME * 23, + .fast => FRAME * 5, + }); + } } } +fn tbPrint(w: usize, h: usize, c: usize, b: usize, char: [*c]const u8) void { + _ = tb.tb_print(@intCast(w), @intCast(h), @intCast(c), @intCast(b), char); +} + fn animation(handler: *Handler, core: *Core) !void { var prng = std.rand.DefaultPrng.init(@as(u64, @bitCast(std.time.milliTimestamp()))); @@ -277,6 +449,8 @@ fn getNthValues(number: i32, adv: u32, allocator: std.mem.Allocator) !std.ArrayL var array = std.ArrayList(u32).init(allocator); var val = adv; + try array.append(0); + while (val <= @as(u32, @intCast(number))) { try array.append(val); val += adv; @@ -365,6 +539,24 @@ pub fn main() !void { } } +test "column" { + var prng = std.rand.DefaultPrng.init(1337); + const rand = prng.random(); + + var core = Core{ .allocator = std.testing.allocator }; + core.start(); + + const column = Column.init(core.allocator); + try core.columns.?.append(column); + try core.columns.?.items[0].?.newChar(&core, Mode.decimal, rand); + try core.columns.?.items[0].?.newChar(&core, Mode.decimal, rand); + + try std.testing.expect(core.columns.?.items[0].?.chars.items.len == 2); + + core.active = false; + core.shutdown(); +} + test "handler" { var core = Core{ .allocator = std.testing.allocator, .active = true }; var handler = Handler{ .duration = 1 }; @@ -403,8 +595,9 @@ test "sections" { const array = try getNthValues(12, 4, std.testing.allocator); defer array.deinit(); - try std.testing.expect(array.items[0] == 4); - try std.testing.expect(array.items[1] == 8); - try std.testing.expect(array.items[2] == 12); - try std.testing.expect(array.items.len == 3); + try std.testing.expect(array.items[0] == 0); + try std.testing.expect(array.items[1] == 4); + try std.testing.expect(array.items[2] == 8); + try std.testing.expect(array.items[3] == 12); + try std.testing.expect(array.items.len == 4); } diff --git a/test/cli.zig b/test/cli.zig index 5885f3b..a5287af 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -112,3 +112,10 @@ test "style: blocks" { try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); } + +test "style: rain" { + const argv = [_][]const u8{ exe_path, "--time=1", "-s=rain" }; + const term = try runner(argv); + + try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 }); +}