mirror of
https://codeberg.org/fairyglade/ly.git
synced 2026-08-20 12:34:19 +02:00
feat(TbdfFile): add custom animation format support (tbdf)
Adds support for a custom animation format designed with longer animations in mind, minimising memory and processing requirements, compared to dur files.
This commit is contained in:
parent
a22805d44b
commit
b9be2e73b1
4 changed files with 472 additions and 0 deletions
449
src/animations/TbdfFile.zig
Normal file
449
src/animations/TbdfFile.zig
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Json = std.json;
|
||||
const eql = std.mem.eql;
|
||||
const flate = std.compress.flate;
|
||||
|
||||
const ly_ui = @import("ly-ui");
|
||||
const Cell = ly_ui.Cell;
|
||||
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||
const Color = TerminalBuffer.Color;
|
||||
const Styling = TerminalBuffer.Styling;
|
||||
const Widget = ly_ui.Widget;
|
||||
|
||||
const ly_core = ly_ui.ly_core;
|
||||
const interop = ly_core.interop;
|
||||
const TimeOfDay = interop.TimeOfDay;
|
||||
const LogFile = ly_core.LogFile;
|
||||
|
||||
const enums = @import("../enums.zig");
|
||||
const TbdfOffsetAlignment = enums.TbdfOffsetAlignment;
|
||||
|
||||
fn readDecompressFile(allocator: Allocator, io: std.Io, file_path: []const u8) ![]u8 {
|
||||
const file_buffer = std.Io.Dir.cwd().openFile(io, file_path, .{}) catch {
|
||||
return error.FileNotFound;
|
||||
};
|
||||
defer file_buffer.close(io);
|
||||
|
||||
var file_reader_buffer: [4096]u8 = undefined;
|
||||
var decompress_buffer: [flate.max_window_len]u8 = undefined;
|
||||
|
||||
var file_reader = file_buffer.reader(io, &file_reader_buffer);
|
||||
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer);
|
||||
|
||||
const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch {
|
||||
return error.NotValidFile;
|
||||
};
|
||||
|
||||
return file_decompressed;
|
||||
}
|
||||
|
||||
const Frame = struct {
|
||||
/// Every 3 bytes represent fg, bg and char index.
|
||||
contents: []u24,
|
||||
|
||||
pub fn deinit(self: *const Frame, allocator: Allocator) void {
|
||||
allocator.free(self.contents);
|
||||
}
|
||||
};
|
||||
|
||||
const TbdfFormat = struct {
|
||||
allocator: Allocator,
|
||||
formatVersion: u8,
|
||||
flags: u8,
|
||||
columns: u16,
|
||||
lines: u16,
|
||||
fps: u8,
|
||||
charMap: []u21,
|
||||
movie_data: []u24,
|
||||
frames: std.ArrayList(Frame) = undefined,
|
||||
|
||||
fn parseFromRawData(allocator: Allocator, data: []u8) !TbdfFormat {
|
||||
var iter = ByteIter{ .buf = data };
|
||||
|
||||
if (!(eql(u8, iter.next(4).?, "tbdf"))) return error.NotValidFile;
|
||||
const formatVersion = (iter.next(1) orelse return error.NotValidFile)[0];
|
||||
const flags = (iter.next(1) orelse return error.NotValidFile)[0];
|
||||
const columns = iter.nextU16() orelse return error.NotValidFile;
|
||||
const lines = iter.nextU16() orelse return error.NotValidFile;
|
||||
const frame_count = iter.nextU16() orelse return error.NotValidFile;
|
||||
const fps = (iter.next(1) orelse return error.NotValidFile)[0];
|
||||
|
||||
const char_map_len = (iter.next(1) orelse return error.NotValidFile)[0];
|
||||
var utf8 = std.unicode.Utf8Iterator{ .bytes = data, .i = iter.pos };
|
||||
var char_map: []u21 = try allocator.alloc(u21, char_map_len);
|
||||
errdefer allocator.free(char_map);
|
||||
for (0..char_map_len) |i| {
|
||||
const codepoint: u21 = utf8.nextCodepoint() orelse return error.NotValidFile;
|
||||
char_map[i] = codepoint;
|
||||
}
|
||||
|
||||
// skip the char map unicode codepoints
|
||||
iter.pos = utf8.i;
|
||||
const frame_data = iter.rest() orelse return error.NotValidFile;
|
||||
std.debug.print("data = {}\n", .{frame_data.len});
|
||||
// if (frame_data.len % 4 != 0) return error.NotValidFile;
|
||||
var frames = try std.ArrayList(Frame).initCapacity(allocator, frame_count);
|
||||
errdefer frames.deinit(allocator);
|
||||
|
||||
const cells_per_frame: u32 = @as(u32, lines) * @as(u32, columns);
|
||||
const total_cells = cells_per_frame * @as(u32, frame_count);
|
||||
var movie_data = try allocator.alloc(u24, total_cells);
|
||||
errdefer allocator.free(movie_data);
|
||||
|
||||
{
|
||||
var i: usize = 0;
|
||||
var pos: usize = 0;
|
||||
|
||||
// decompress all data into a buffer
|
||||
while (i + 3 < frame_data.len) : (i += 4) {
|
||||
const count = frame_data[i];
|
||||
const fg = frame_data[i + 1];
|
||||
const bg = frame_data[i + 2];
|
||||
const char_index = frame_data[i + 3];
|
||||
|
||||
const value =
|
||||
(@as(u24, fg) << 16) |
|
||||
(@as(u24, bg) << 8) |
|
||||
@as(u24, char_index);
|
||||
|
||||
if (pos + count > movie_data.len)
|
||||
return error.NotValidFile;
|
||||
@memset(movie_data[pos .. pos + count], value);
|
||||
pos += count;
|
||||
}
|
||||
if (pos != movie_data.len)
|
||||
return error.NotValidFile;
|
||||
}
|
||||
// create frames
|
||||
for (0..frame_count) |i| {
|
||||
const frame: Frame = .{ .contents = movie_data[i * cells_per_frame .. (i + 1) * cells_per_frame] };
|
||||
try frames.append(allocator, frame);
|
||||
}
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.formatVersion = formatVersion,
|
||||
.flags = flags,
|
||||
.columns = columns,
|
||||
.lines = lines,
|
||||
.fps = fps,
|
||||
.charMap = char_map,
|
||||
.movie_data = movie_data,
|
||||
.frames = frames,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createFromFile(allocator: Allocator, io: std.Io, file_path: []const u8) !TbdfFormat {
|
||||
const file_decompressed = try readDecompressFile(allocator, io, file_path);
|
||||
defer allocator.free(file_decompressed);
|
||||
|
||||
return parseFromRawData(allocator, file_decompressed);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *TbdfFormat) void {
|
||||
self.allocator.free(self.movie_data);
|
||||
self.allocator.free(self.charMap);
|
||||
self.frames.deinit(self.allocator);
|
||||
}
|
||||
};
|
||||
|
||||
const ByteIter = struct {
|
||||
buf: []const u8,
|
||||
pos: usize = 0,
|
||||
|
||||
pub fn next(self: *ByteIter, n: usize) ?[]const u8 {
|
||||
if (self.pos + n > self.buf.len) return null;
|
||||
const slice = self.buf[self.pos .. self.pos + n];
|
||||
self.pos += n;
|
||||
return slice;
|
||||
}
|
||||
|
||||
pub fn nextU16(self: *ByteIter) ?u16 {
|
||||
const bytes = self.next(2) orelse return null;
|
||||
return @as(u16, bytes[0]) |
|
||||
(@as(u16, bytes[1]) << 8);
|
||||
}
|
||||
|
||||
pub fn rest(self: *ByteIter) ?[]const u8 {
|
||||
return self.next(self.buf.len - self.pos);
|
||||
}
|
||||
};
|
||||
|
||||
const tb_color_16 = [16]u32{
|
||||
Color.ECOL_BLACK,
|
||||
Color.ECOL_RED,
|
||||
Color.ECOL_GREEN,
|
||||
Color.ECOL_YELLOW,
|
||||
Color.ECOL_BLUE,
|
||||
Color.ECOL_MAGENTA,
|
||||
Color.ECOL_CYAN,
|
||||
Color.ECOL_WHITE,
|
||||
Color.ECOL_BLACK | Styling.BOLD,
|
||||
Color.ECOL_RED | Styling.BOLD,
|
||||
Color.ECOL_GREEN | Styling.BOLD,
|
||||
Color.ECOL_YELLOW | Styling.BOLD,
|
||||
Color.ECOL_BLUE | Styling.BOLD,
|
||||
Color.ECOL_MAGENTA | Styling.BOLD,
|
||||
Color.ECOL_CYAN | Styling.BOLD,
|
||||
Color.ECOL_WHITE | Styling.BOLD,
|
||||
};
|
||||
|
||||
// Using bold for bright colors allows for all 16 colors to be rendered on tty term
|
||||
const rgb_color_16 = [16]u32{
|
||||
Color.DEFAULT, // DEFAULT instead of TRUE_BLACK to not break compositors (the latter ignores transparency)
|
||||
Color.TRUE_DIM_RED,
|
||||
Color.TRUE_DIM_GREEN,
|
||||
Color.TRUE_DIM_YELLOW,
|
||||
Color.TRUE_DIM_BLUE,
|
||||
Color.TRUE_DIM_MAGENTA,
|
||||
Color.TRUE_DIM_CYAN,
|
||||
Color.TRUE_DIM_WHITE,
|
||||
Color.DEFAULT | Styling.BOLD,
|
||||
Color.TRUE_RED | Styling.BOLD,
|
||||
Color.TRUE_GREEN | Styling.BOLD,
|
||||
Color.TRUE_YELLOW | Styling.BOLD,
|
||||
Color.TRUE_BLUE | Styling.BOLD,
|
||||
Color.TRUE_MAGENTA | Styling.BOLD,
|
||||
Color.TRUE_CYAN | Styling.BOLD,
|
||||
Color.TRUE_WHITE | Styling.BOLD,
|
||||
};
|
||||
|
||||
fn sixCubeToChannel(sixcube: u32) u32 {
|
||||
// Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF,
|
||||
// so we use 0xF0 instead with a scaler
|
||||
const equal_divisions = 0xF0 / 6;
|
||||
|
||||
// Since the range is to 0xFF but 6 isn't divisible, we must add a scaler to get it to 0xFF at the last index (5)
|
||||
const scaler = 0xFF - (equal_divisions * 5);
|
||||
|
||||
return if (sixcube > 0) (sixcube * equal_divisions) + scaler else 0;
|
||||
}
|
||||
|
||||
fn convert256ToRgb(color_256: u32) u32 {
|
||||
var rgb_color: u32 = 0;
|
||||
|
||||
// 0 - 15 is the standard color range, map to array table
|
||||
if (color_256 < 16) {
|
||||
rgb_color = rgb_color_16[color_256];
|
||||
}
|
||||
// 16 - 231 is the extended range
|
||||
else if (color_256 < 232) {
|
||||
|
||||
// For extended term range we subtract by 16 to get it in a 0..(6x6x6) cube (range of 216)
|
||||
// divide by 36 gets the depth of the cube (6x6x1)
|
||||
// divide by 6 gets the width of the cube (6x1)
|
||||
// divide by 1 gets the height of the cube (divide 1 for clarity for what we are doing)
|
||||
// each channel can be 6 levels of brightness hence remander operation of 6
|
||||
// finally bitshift to correct rgb channel (16 for red, 8 for green, 0 for blue)
|
||||
rgb_color |= sixCubeToChannel(((color_256 - 16) / 36) % 6) << 16;
|
||||
rgb_color |= sixCubeToChannel(((color_256 - 16) / 6) % 6) << 8;
|
||||
rgb_color |= sixCubeToChannel(((color_256 - 16) / 1) % 6);
|
||||
}
|
||||
// 232 - 255 is the grayscale range
|
||||
else {
|
||||
|
||||
// For grayscale we have a space of 232 - 255 (24)
|
||||
// subtract by 232 to get it into the 0..23 range
|
||||
// standard colors will contain white and black, so we do not use them in the grayscale range (0 is 0x08, 23 is 0xEE)
|
||||
// this results in a skip of 0x08 for the first color and divisions of 0x0A
|
||||
// example: term_col 232 = scaler + equal_divisions * (232 - 232) which becomes (scaler + 0x00) == 0x08
|
||||
// example: term_col 255 = scaler + equal_divisions * (255 - 232) which becomes (scaler + 0xE6) == 0xEE
|
||||
const scaler = 0x08;
|
||||
|
||||
// to get equal parts, the equation is:
|
||||
// 0xEE = equal_divisions * 23 + scaler | top of range is 0xEE, 23 is last element value (255 minus 232)
|
||||
// reordered to solve for equal_divisions:
|
||||
const equal_divisions = (0xEE - scaler) / 23; // evals to 0x0A
|
||||
|
||||
const channel = scaler + equal_divisions * (color_256 - 232);
|
||||
|
||||
// gray is equal value of same channel color in rgb
|
||||
rgb_color = channel | (channel << 8) | (channel << 16);
|
||||
}
|
||||
|
||||
return rgb_color;
|
||||
}
|
||||
|
||||
const UVec2 = @Vector(2, u32);
|
||||
const IVec2 = @Vector(2, i64);
|
||||
|
||||
const VEC_X = 0;
|
||||
const VEC_Y = 1;
|
||||
|
||||
const TbdfFile = @This();
|
||||
|
||||
instance: ?Widget = null,
|
||||
start_time: TimeOfDay,
|
||||
allocator: Allocator,
|
||||
io: std.Io,
|
||||
terminal_buffer: *TerminalBuffer,
|
||||
movie: TbdfFormat,
|
||||
current_frame_index: u64,
|
||||
start_pos: IVec2,
|
||||
full_color: bool,
|
||||
animate: *bool,
|
||||
timeout_sec: u12,
|
||||
frame_delay: u16,
|
||||
frame_time: u32,
|
||||
time_previous: i64,
|
||||
offset_alignment: TbdfOffsetAlignment,
|
||||
offset: IVec2,
|
||||
|
||||
// if the user has an even number of columns or rows, we will default to the left or higher position (e.g. 4 columns center = .x..)
|
||||
fn center(v: u32) i64 {
|
||||
return @intCast((v / 2) + (v % 2));
|
||||
}
|
||||
|
||||
fn calculateStartPos(terminal_buffer: *TerminalBuffer, tbdf_movie: *TbdfFormat, offset_alignment: TbdfOffsetAlignment, offset: IVec2) IVec2 {
|
||||
const buf_width: u32 = @intCast(terminal_buffer.width);
|
||||
const buf_height: u32 = @intCast(terminal_buffer.height);
|
||||
|
||||
const movie_width: u32 = @intCast(tbdf_movie.columns);
|
||||
const movie_height: u32 = @intCast(tbdf_movie.lines);
|
||||
|
||||
const start_pos: IVec2 = switch (offset_alignment) {
|
||||
TbdfOffsetAlignment.center => .{ center(buf_width) - center(movie_width), center(buf_height) - center(movie_height) },
|
||||
TbdfOffsetAlignment.topleft => .{ 0, 0 },
|
||||
TbdfOffsetAlignment.topcenter => .{ center(buf_width) - center(movie_width), 0 },
|
||||
TbdfOffsetAlignment.topright => .{ buf_width - movie_width, 0 },
|
||||
TbdfOffsetAlignment.centerleft => .{ 0, center(buf_height) - center(movie_height) },
|
||||
TbdfOffsetAlignment.centerright => .{ buf_width - movie_width, center(buf_height) - center(movie_height) },
|
||||
TbdfOffsetAlignment.bottomleft => .{ 0, buf_height - movie_height },
|
||||
TbdfOffsetAlignment.bottomcenter => .{ center(buf_width) - center(movie_width), buf_height - movie_height },
|
||||
TbdfOffsetAlignment.bottomright => .{ buf_width - movie_width, buf_height - movie_height },
|
||||
};
|
||||
|
||||
return start_pos + offset;
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
io: std.Io,
|
||||
terminal_buffer: *TerminalBuffer,
|
||||
log_file: *LogFile,
|
||||
file_path: []const u8,
|
||||
offset_alignment: TbdfOffsetAlignment,
|
||||
x_offset: i32,
|
||||
y_offset: i32,
|
||||
full_color: bool,
|
||||
animate: *bool,
|
||||
timeout_sec: u12,
|
||||
frame_delay: u16,
|
||||
) !TbdfFile {
|
||||
var movie: TbdfFormat = TbdfFormat.createFromFile(allocator, io, file_path) catch |err| switch (err) {
|
||||
error.FileNotFound => {
|
||||
try log_file.err(io, "tui", "tbdf_file was not found at: {s}", .{file_path});
|
||||
return err;
|
||||
},
|
||||
error.NotValidFile => {
|
||||
try log_file.err(io, "tui", "tbdf_file loaded was invalid or not a tbdf file!", .{});
|
||||
return err;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
|
||||
const offset: IVec2 = .{ x_offset, y_offset };
|
||||
|
||||
const start_pos = calculateStartPos(terminal_buffer, &movie, offset_alignment, offset);
|
||||
|
||||
// Convert tbdf fps to ms per frames
|
||||
const frame_time: u32 = @as(u32, 1000) / movie.fps;
|
||||
|
||||
return .{
|
||||
.instance = null,
|
||||
.start_time = try interop.getTimeOfDay(),
|
||||
.allocator = allocator,
|
||||
.io = io,
|
||||
.terminal_buffer = terminal_buffer,
|
||||
.current_frame_index = 0,
|
||||
.time_previous = std.Io.Timestamp.now(io, .real).toMilliseconds(),
|
||||
.start_pos = start_pos,
|
||||
.full_color = full_color,
|
||||
.animate = animate,
|
||||
.timeout_sec = timeout_sec,
|
||||
.frame_delay = frame_delay,
|
||||
.movie = movie,
|
||||
.frame_time = frame_time,
|
||||
.offset_alignment = offset_alignment,
|
||||
.offset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn widget(self: *TbdfFile) *Widget {
|
||||
if (self.instance) |*instance| return instance;
|
||||
self.instance = Widget.init(
|
||||
"TbdfFile",
|
||||
null,
|
||||
self,
|
||||
deinit,
|
||||
realloc,
|
||||
draw,
|
||||
update,
|
||||
null,
|
||||
calculateTimeout,
|
||||
);
|
||||
return &self.instance.?;
|
||||
}
|
||||
|
||||
fn deinit(self: *TbdfFile) void {
|
||||
self.movie.deinit();
|
||||
}
|
||||
|
||||
fn realloc(self: *TbdfFile) !void {
|
||||
// when terminal size changes, we need to recalculate the start_pos based on the new size
|
||||
self.start_pos = calculateStartPos(self.terminal_buffer, &self.movie, self.offset_alignment, self.offset);
|
||||
}
|
||||
|
||||
fn draw(self: *TbdfFile) void {
|
||||
if (!self.animate.*) return;
|
||||
|
||||
const current_frame = self.movie.frames.items[self.current_frame_index];
|
||||
|
||||
// y is used as an iterator in the tbdf format, while cell_y gives us the correct placement for the cell (same for x)
|
||||
for (0..@intCast(self.movie.lines)) |y| {
|
||||
const cell_y = @as(i32, @intCast(y)) + self.start_pos[VEC_Y];
|
||||
|
||||
for (0..@intCast(self.movie.columns)) |x| {
|
||||
const cell_x = @as(i32, @intCast(x)) + self.start_pos[VEC_X];
|
||||
|
||||
const index = y * self.movie.columns + x;
|
||||
const cell_value = current_frame.contents[index];
|
||||
|
||||
const char_index: u8 = @intCast(cell_value & 0xff);
|
||||
const codepoint = self.movie.charMap[char_index];
|
||||
|
||||
const cell_fg: u8 = @intCast(cell_value >> 16);
|
||||
const fg_color = if (self.full_color) convert256ToRgb(cell_fg) else tb_color_16[cell_fg];
|
||||
const cell_bg: u8 = @intCast((cell_value >> 8) & 0xff);
|
||||
const bg_color = if (self.full_color) convert256ToRgb(cell_bg) else tb_color_16[cell_bg];
|
||||
|
||||
const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color };
|
||||
|
||||
self.terminal_buffer.setCellBoundsChecked(cell_x, cell_y, cell) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
const time_current = std.Io.Timestamp.now(self.io, .real).toMilliseconds();
|
||||
const delta_time = time_current - self.time_previous;
|
||||
|
||||
// Convert delay from sec to ms
|
||||
if (delta_time > (self.frame_time)) {
|
||||
self.time_previous = time_current;
|
||||
|
||||
const frame_count = self.movie.frames.items.len;
|
||||
self.current_frame_index = (self.current_frame_index + 1) % frame_count;
|
||||
}
|
||||
}
|
||||
|
||||
fn update(self: *TbdfFile, _: *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: *TbdfFile, _: *anyopaque) !?usize {
|
||||
return self.frame_delay;
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ const Input = enums.Input;
|
|||
const ViMode = enums.ViMode;
|
||||
const Bigclock = enums.Bigclock;
|
||||
const DurOffsetAlignment = enums.DurOffsetAlignment;
|
||||
const TbdfOffsetAlignment = enums.TbdfOffsetAlignment;
|
||||
|
||||
allow_empty_password: bool = true,
|
||||
animation: Animation = .none,
|
||||
|
|
@ -52,9 +53,13 @@ doom_top_color: u32 = 0x00FF0000,
|
|||
doom_middle_color: u32 = 0x00FFFF00,
|
||||
doom_bottom_color: u32 = 0x00FFFFFF,
|
||||
dur_file_path: []const u8 = build_options.config_directory ++ "/ly/example.dur",
|
||||
tbdf_file_path: []const u8 = build_options.config_directory ++ "/ly/example.tbdf",
|
||||
dur_offset_alignment: DurOffsetAlignment = .center,
|
||||
dur_x_offset: i32 = 0,
|
||||
dur_y_offset: i32 = 0,
|
||||
tbdf_offset_alignment: TbdfOffsetAlignment = .center,
|
||||
tbdf_x_offset: i32 = 0,
|
||||
tbdf_y_offset: i32 = 0,
|
||||
edge_margin: u8 = 0,
|
||||
error_bg: u32 = 0x00000000,
|
||||
error_fg: u32 = 0x01FF0000,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub const Animation = enum {
|
|||
gameoflife,
|
||||
dur_file,
|
||||
lua,
|
||||
tbdf_file,
|
||||
};
|
||||
|
||||
pub const DisplayServer = enum {
|
||||
|
|
@ -67,3 +68,15 @@ pub const DurOffsetAlignment = enum {
|
|||
bottomcenter,
|
||||
bottomright,
|
||||
};
|
||||
|
||||
pub const TbdfOffsetAlignment = enum {
|
||||
topleft,
|
||||
topcenter,
|
||||
topright,
|
||||
centerleft,
|
||||
center,
|
||||
centerright,
|
||||
bottomleft,
|
||||
bottomcenter,
|
||||
bottomright,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ const DurFile = @import("animations/DurFile.zig");
|
|||
const GameOfLife = @import("animations/GameOfLife.zig");
|
||||
const Matrix = @import("animations/Matrix.zig");
|
||||
const Lua = @import("animations/Lua.zig");
|
||||
const TbdfFile = @import("animations/TbdfFile.zig");
|
||||
const auth = @import("auth.zig");
|
||||
const InfoLine = @import("components/InfoLine.zig");
|
||||
const Session = @import("components/Session.zig");
|
||||
|
|
@ -1100,6 +1101,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
);
|
||||
animation = lua.widget();
|
||||
},
|
||||
.tbdf_file => {
|
||||
var tbdf = try TbdfFile.init(state.allocator, state.io, &state.buffer, &state.log_file, state.config.tbdf_file_path, state.config.tbdf_offset_alignment, state.config.tbdf_x_offset, state.config.tbdf_y_offset, state.config.full_color, &state.animate, state.config.animation_timeout_sec, state.config.animation_frame_delay);
|
||||
animation = tbdf.widget();
|
||||
},
|
||||
}
|
||||
defer if (animation) |a| a.deinit();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue