diff --git a/res/config.ini b/res/config.ini index 06d6a2f..b9c912c 100644 --- a/res/config.ini +++ b/res/config.ini @@ -167,6 +167,16 @@ doom_bottom_color = 0x00FFFFFF # Dur file path dur_file_path = $CONFIG_DIRECTORY/ly/example.dur +# Dur file alignment (center is the default for example) +# |-----------------| +# |1 2 3| +# | | +# |4 0 5| +# | | +# |6 7 8| +# |-----------------| +dur_offset_alignment = 0 + # Dur offset x direction dur_x_offset = 0 diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index 86b951d..d1b08ef 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -286,25 +286,41 @@ fn convert_256_to_rgb(color_256: u32) u32 { return rgb_color; } +const UVec2 = @Vector(2, u16); +const IVec2 = @Vector(2, i32); + +const VEC_X = 0; +const VEC_Y = 1; + +const Alignment = struct { + pub const CENTER = 0; // Center is the default so we have it out of order + pub const TOP_LEFT = 1; + pub const TOP_CENTER = 2; + pub const TOP_RIGHT = 3; + pub const CENTER_LEFT = 4; + pub const CENTER_RIGHT = 5; + pub const BOTTOM_LEFT = 6; + pub const BOTTOM_CENTER = 7; + pub const BOTTOM_RIGHT = 8; +}; + + const DurFile = @This(); allocator: Allocator, terminal_buffer: *TerminalBuffer, -frames: u64, -time_previous: i64, -x_offset: u32, -y_offset: u32, -full_color: bool, dur_movie: DurFormat, -frame_width: u32, -frame_height: u32, +frames: u64, +frame_size: UVec2, +start_pos: IVec2, +full_color: bool, frame_time: u32, +time_previous: i64, is_color_format_16: bool, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: *std.io.Writer, file_path: []const u8, x_offset: u32, y_offset: u32, full_color: bool) !DurFile { +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: *std.io.Writer, file_path: []const u8, offset_alignment: u32, x_offset: i32, y_offset: i32, full_color: bool) !DurFile { var dur_movie: DurFormat = .init(allocator); - // error state is recoverable when thrown to main and results in no background with Dummy in main dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) { error.FileNotFound => { try log_writer.print("error: dur_file was not found at: {s}\n", .{file_path}); @@ -324,19 +340,35 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: return error.InvalidColorFormat; } - const buf_width: u32 = @intCast(terminal_buffer.width); - const buf_height: u32 = @intCast(terminal_buffer.height); + const buf_width: u16 = @intCast(terminal_buffer.width); + const buf_height: u16 = @intCast(terminal_buffer.height); - const movie_width: u32 = @intCast(dur_movie.columns.?); - const movie_height: u32 = @intCast(dur_movie.lines.?); + const movie_width: u16 = @intCast(dur_movie.columns.?); + const movie_height: u16 = @intCast(dur_movie.lines.?); - // Clamp to prevent user from exceeding draw window - const x_offset_clamped = std.math.clamp(x_offset, 0, buf_width - 1); - const y_offset_clamped = std.math.clamp(y_offset, 0, buf_height - 1); + // 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..) + const center = struct { + fn center(v: u16) i32 { return @intCast((v / 2) + (v % 2)); } + }.center; - // Ensure if user offsets and frame goes offscreen, it will not overflow draw - const frame_width = if ((movie_width + x_offset_clamped) < buf_width) movie_width else buf_width - x_offset_clamped; - const frame_height = if ((movie_height + y_offset_clamped) < buf_height) movie_height else buf_height - y_offset_clamped; + var start_pos = switch (offset_alignment) { + Alignment.CENTER => IVec2{ center(buf_width) - center(movie_width), center(buf_height) - center(movie_height) }, + Alignment.TOP_LEFT => IVec2{ 0, 0 }, + Alignment.TOP_CENTER => IVec2{ center(buf_width) - center(movie_width), 0 }, + Alignment.TOP_RIGHT => IVec2{ buf_width - movie_width, 0 }, + Alignment.CENTER_LEFT => IVec2{ 0, center(buf_width) - center(movie_width) }, + Alignment.CENTER_RIGHT => IVec2{ buf_width - movie_width, center(buf_width) - center(movie_width) }, + Alignment.BOTTOM_LEFT => IVec2{ 0, buf_width - movie_height }, + Alignment.BOTTOM_CENTER => IVec2{ center(buf_width) - center(movie_width), buf_width - movie_height }, + Alignment.BOTTOM_RIGHT => IVec2{ buf_width - movie_width, buf_height - movie_height }, + else => return error.InvalidDurOffsetAlignment, + }; + + start_pos += IVec2{ x_offset, y_offset }; + + // Draw only the needed amount if movie smaller than screen. If movie is bigger, we will just draw entire screen + const frame_width = if (movie_width < buf_width) movie_width else buf_width; + const frame_height = if (movie_height < buf_height) movie_height else buf_height; // Convert dur fps to frames per ms const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); @@ -346,12 +378,10 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: .terminal_buffer = terminal_buffer, .frames = 0, .time_previous = std.time.milliTimestamp(), - .x_offset = x_offset_clamped, - .y_offset = y_offset_clamped, + .frame_size = UVec2{ frame_width, frame_height }, + .start_pos = start_pos, .full_color = full_color, .dur_movie = dur_movie, - .frame_width = frame_width, - .frame_height = frame_height, .frame_time = frame_time, .is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"), }; @@ -370,10 +400,25 @@ fn realloc(_: *DurFile) anyerror!void {} fn draw(self: *DurFile) void { const current_frame = self.dur_movie.frames.items[self.frames]; - for (0..self.frame_height) |y| { + const buf_width: u16 = @intCast(self.terminal_buffer.width); + const buf_height: u16 = @intCast(self.terminal_buffer.height); + + // y is used as an iterator in the durformat, while cell_y gives us the correct placement for the cell (same for x) + for (0..self.frame_size[VEC_Y]) |y| { + const y_offset_i: i32 = @as(i32, @intCast(y)) + self.start_pos[VEC_Y]; + // we skip the pass if it falls outside of the draw window (ensure no int underflow) + const cell_y: u32 = if (y_offset_i >= 0 and y_offset_i < buf_height) @intCast(y_offset_i) else continue; + var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator(); - for (0..self.frame_width) |x| { + for (0..self.frame_size[VEC_X]) |x| { + const x_offset_i: i32 = @as(i32, @intCast(x)) + self.start_pos[VEC_X]; + // skip pass, same as y but also incriment the codepoint iter to fetch correct values in later passes + const cell_x: u32 = if (x_offset_i >= 0 and x_offset_i < buf_width) @intCast(x_offset_i) else { + _ = iter.nextCodepoint().?; + continue; + }; + const codepoint: u21 = iter.nextCodepoint().?; const color_map = current_frame.colorMap[x][y]; @@ -390,7 +435,7 @@ fn draw(self: *DurFile) void { const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color }; - cell.put(x + self.x_offset, y + self.y_offset); + cell.put(cell_x, cell_y); } } diff --git a/src/config/Config.zig b/src/config/Config.zig index e9169bd..b1b45b8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -43,8 +43,9 @@ 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", -dur_x_offset: u32 = 0, -dur_y_offset: u32 = 0, +dur_offset_alignment: u32 = 0, +dur_x_offset: i32 = 0, +dur_y_offset: i32 = 0, edge_margin: u8 = 0, error_bg: u32 = 0x00000000, error_fg: u32 = 0x01FF0000, diff --git a/src/main.zig b/src/main.zig index 1faae80..1b9dc2f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -580,7 +580,7 @@ pub fn main() !void { animation = game_of_life.animation(); }, .dur_file => { - var dur = try DurFile.init(allocator, &buffer, log_writer, config.dur_file_path, config.dur_x_offset, config.dur_y_offset, config.full_color); + var dur = try DurFile.init(allocator, &buffer, log_writer, config.dur_file_path, config.dur_offset_alignment, config.dur_x_offset, config.dur_y_offset, config.full_color); animation = dur.animation(); }, }