From a4076b83da2602fe69a42f6980f5a71672d29aca Mon Sep 17 00:00:00 2001 From: hynak Date: Sun, 25 Jan 2026 23:08:42 +0100 Subject: [PATCH] [dur] Add support for alignments and negative offsets + Ly logo (#893) ## What are the changes about? Add support for letting a user use a negative offset (#880), alignment, and logo. Below is example of the logo file, I hope it is what was request :). It has no padding so a user can move the alignment and offset to get it how they want on screen. This technically is good to go, except I didn't upload the logo file as I'm not sure where to add the animation file to get it to here: $CONFIG_DIRECTORY/ly/example.dur ![logo-preview](/attachments/5a829dbd-7708-4d0a-9841-d024902ede68) ## What existing issue does this resolve? #880 ## Pre-requisites - [x] I have tested & confirmed the changes work locally Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/893 Reviewed-by: AnErrupTion Co-authored-by: hynak Co-committed-by: hynak --- build.zig | 2 + res/config.ini | 9 ++- res/example.dur | Bin 0 -> 2061 bytes src/animations/DurFile.zig | 119 ++++++++++++++++++++++++++++--------- src/config/Config.zig | 6 +- src/enums.zig | 12 ++++ src/main.zig | 2 +- 7 files changed, 116 insertions(+), 34 deletions(-) create mode 100644 res/example.dur diff --git a/build.zig b/build.zig index 924e245..71508cf 100644 --- a/build.zig +++ b/build.zig @@ -197,6 +197,8 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map); try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 }); + + try installFile("res/example.dur", config_dir, ly_config_directory, "example.dur", .{ .override_mode = 0o755 }); } { diff --git a/res/config.ini b/res/config.ini index 06d6a2f..7a29aa6 100644 --- a/res/config.ini +++ b/res/config.ini @@ -167,10 +167,15 @@ doom_bottom_color = 0x00FFFFFF # Dur file path dur_file_path = $CONFIG_DIRECTORY/ly/example.dur -# Dur offset x direction +# Dur file alignment +# The dur file can be aligned with a direction and centered easily with the flags below +# Available inputs: topleft, topcenter, topright, centerleft, center, centerright, bottomleft, bottomcenter, bottomright +dur_offset_alignment = center + +# Dur offset x direction (value is added to the current position determined by alignment, negatives are supported) dur_x_offset = 0 -# Dur offset y direction +# Dur offset y direction (value is added to the current position determined by alignment, negatives are supported) dur_y_offset = 0 # Set margin to the edges of the DM (useful for curved monitors) diff --git a/res/example.dur b/res/example.dur new file mode 100644 index 0000000000000000000000000000000000000000..00e82062df1126bc4678fae8eb4ec6a86320002d GIT binary patch literal 2061 zcmV+o2=ezIiwFqz%0y`b|7Kxna(OLhY++<&Eo^URZ!<1rb#eggT}yA{HW0q&R}jAV z;u4~Sjp*cB>}`82T4V#rO`O7jV=ok^*mjZsUdgs>J@^<>q(pKRAXZFK!}&Pmh>!Wg zA2$fietpls?El%UXLsmF@x4fS+2^mT!|&_-ZL{AM$?4*h{a{F&R()UPht1oep7N3x zS@PAP%yx^TbL!jXzx5wQ%BQrJ@Mn<_-&RF_Kjf<-VfVh>)fUlys%C1@By1qQM?nyl_5?5-% z)ayyiRHvm(DHS)gL0l1xD&bT{+{|&&02c|Q zt-{yYQHL(>gs(~)Xu>bNEwa?1D6(yovTiT{bH{K*c0*K4_|zJ@jg({{bsA|HWtA-x zP~$!+kP%+@tt-K8@try*?CMBl-RGhT&+`(}Y^!2lkaRo?=TrD-%QfMX7UEY=R#P$M*@UE<(JXglMDj9-l@E;xd1`=^+3mj zKutohY7BD_0QF8CZ%CtceB%&wjX{TXs=CJT3qqvYp6DpAw#bN^8VZ7OW2CH$o;NTz zMl$zYs)nS`NYwxmo)sdwg-lL)sIi8Wf?gf=AW+!MA_ZTVn1`7D%bbs>!U4_7uocu^ z0kFm*%4r>f3`u_o5j)X11TG+52v$83x96sU7St0$IuHbSClv`B-U<=S3d&M=R-m>$ zQUrSg_pkyT%?if)0-Ze4DDQ=#Jd&4hXNzN`$o3d1fRU=t^97%0Z2`=keO|$^xQ?W} zb>Yxz9`$ieb({ss09LjK+h3S=c>>*QGqdCA-lnaW`_2LE{)ZET5l$pbIPpkRVA~WS zIn*xCfPiWMu76j5eJo_Wz_>Rr5Myb$a6Jn<0vVGABZB#p6?w2o3_gkwN-AX>FPJAr z7(Sm=gqJ%OXna?3P~<{71v4lJ09EFss|Y4@h!RZdR+|FW5g{PCH5BXUg^7^y)M6;L zBsSF&P$fhN;W!8=&sfBRStjSI#$su)GDBHR`Q;bHsGwN* zL;_{;u>pyAa;yqfCw|LN8sRB{kSMds)y2vG6L${+HFB9CnB1jv5=oE{Xul2|I0WiyLyG)> zpd82N0tAc3aDg4#6A2)ec+|LoK332}(kB?p;@D3Gkb$T^i%!)f1mj2moglRWA?gnS z791TZ05TqDtfjjoR9YMuVk=->jrkxkhS*+~H{H##`S`{dfV|NI0)=>5TPz@>^dE7r zHVeTL2}|q`ZN<>opu&xAg`R{&s}O!3E4bVj!tqExzZZOdJNrCF3Sgw_^L)YQSz7>e zXP;Lv0$4{1bv4hEHPrxCwte1T2w;~dfW5Y9)7BvfQvicQ5CVvV2`3&YfT*x=V)Md@ zkaWu!J$Z?VE9VuOp*M0)vuQ{4V|eU8SB3->Cy@OT9?xMm^5XCtkF!aQu^u&t5pj;S zXfZVvL2NmON@}HaRmVyk26)kIYGk5LjcjO)ktQ`V=_hKO=JF_D3u^r?CBJP%cqbu1y3XkYJj6PRkIUGB_IL*2 zRFOKQ!i8LtxtI|&=dgogq~*zE{%j&d21ba?6d8VMC+1=KL9K!#Ua=B?OS2FtNeuVk zk789Onqfku9vN;NLL^3#fndTPS*j6S&2a}Y0;Pep!9uA@pfJMYp0G&0S}lp3T6-Q8 z0(E?~B=QP^fjvo=A-HY@IzjM%FT)e+)5M|k3j%euA!$938USQ2>c+q(L24!HX6WFX zf&ZcTFpep8F%FRWvAa6p2JBb?rDk{l14kyqPKg+G)@C$7dDMxg#W3-K|M#w$VEv&W z3{9D6_iEoxShQya08Pm>18S(_3AOs_*bppI={R5qXh_Bgjgu96;Ii8n=;VS~@RYpMaPZ2P>w5Wp@^0DEoIrmaH|rT~U1 r5@w1=3N$J#oY=f@A|$;yVezK=_Hpy^KL7v#|NjF3YFqWid#V5c!Pn9S literal 0 HcmV?d00001 diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index 86b951d..9ec9eaf 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -2,6 +2,8 @@ const std = @import("std"); const Animation = @import("../tui/Animation.zig"); const Cell = @import("../tui/Cell.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); +const enums = @import("../enums.zig"); +const DurOffsetAlignment = enums.DurOffsetAlignment; const Color = TerminalBuffer.Color; const Styling = TerminalBuffer.Styling; const Allocator = std.mem.Allocator; @@ -286,25 +288,74 @@ fn convert_256_to_rgb(color_256: u32) u32 { return rgb_color; } +const UVec2 = @Vector(2, u32); +const IVec2 = @Vector(2, i64); + +const VEC_X = 0; +const VEC_Y = 1; + 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, +offset_alignment: DurOffsetAlignment, +offset: IVec2, -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 { +// 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 calc_start_position(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat, offset_alignment: DurOffsetAlignment, offset: IVec2) IVec2 { + const buf_width: u32 = @intCast(terminal_buffer.width); + const buf_height: u32 = @intCast(terminal_buffer.height); + + var movie_width: u32 = @intCast(dur_movie.columns.?); + var movie_height: u32 = @intCast(dur_movie.lines.?); + + if (movie_width > buf_width) movie_width = buf_width; + if (movie_height > buf_height) movie_height = buf_height; + + const start_pos: IVec2 = switch (offset_alignment) { + DurOffsetAlignment.center => .{ center(buf_width) - center(movie_width), center(buf_height) - center(movie_height) }, + DurOffsetAlignment.topleft => .{ 0, 0 }, + DurOffsetAlignment.topcenter => .{ center(buf_width) - center(movie_width), 0 }, + DurOffsetAlignment.topright => .{ buf_width - movie_width, 0 }, + DurOffsetAlignment.centerleft => .{ 0, center(buf_height) - center(movie_height) }, + DurOffsetAlignment.centerright => .{ buf_width - movie_width, center(buf_height) - center(movie_height) }, + DurOffsetAlignment.bottomleft => .{ 0, buf_height - movie_height }, + DurOffsetAlignment.bottomcenter => .{ center(buf_width) - center(movie_width), buf_height - movie_height }, + DurOffsetAlignment.bottomright => .{ buf_width - movie_width, buf_height - movie_height }, + }; + + return start_pos + offset; +} + +fn calc_frame_size(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat) UVec2 { + const buf_width: u32 = @intCast(terminal_buffer.width); + const buf_height: u32 = @intCast(terminal_buffer.height); + + const movie_width: u32 = @intCast(dur_movie.columns.?); + const movie_height: u32 = @intCast(dur_movie.lines.?); + + // 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; + + return .{ frame_width, frame_height }; +} + +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: *std.io.Writer, file_path: []const u8, offset_alignment: DurOffsetAlignment, 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 +375,10 @@ 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 offset: IVec2 = .{ x_offset, y_offset }; - const movie_width: u32 = @intCast(dur_movie.columns.?); - const movie_height: u32 = @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); - - // 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; + const start_pos = calc_start_position(terminal_buffer, &dur_movie, offset_alignment, offset); + const frame_size = calc_frame_size(terminal_buffer, &dur_movie); // Convert dur fps to frames per ms const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); @@ -346,14 +388,14 @@ 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 = frame_size, + .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"), + .offset_alignment = offset_alignment, + .offset = offset, }; } @@ -365,15 +407,34 @@ fn deinit(self: *DurFile) void { self.dur_movie.deinit(); } -fn realloc(_: *DurFile) anyerror!void {} +fn realloc(self: *DurFile) anyerror!void { + // when terminal size changes, we need to recalculate the start_pos and frame_size based on the new size + self.start_pos = calc_start_position(self.terminal_buffer, &self.dur_movie, self.offset_alignment, self.offset); + self.frame_size = calc_frame_size(self.terminal_buffer, &self.dur_movie); +} fn draw(self: *DurFile) void { const current_frame = self.dur_movie.frames.items[self.frames]; - for (0..self.frame_height) |y| { + const buf_width: u32 = @intCast(self.terminal_buffer.width); + const buf_height: u32 = @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 = @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 = @as(i32, @intCast(x)) + self.start_pos[VEC_X]; + // skip pass, same as y but also increment 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 +451,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..3e0cf1e 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -5,6 +5,7 @@ const Animation = enums.Animation; const Input = enums.Input; const ViMode = enums.ViMode; const Bigclock = enums.Bigclock; +const DurOffsetAlignment = enums.DurOffsetAlignment; allow_empty_password: bool = true, animation: Animation = .none, @@ -43,8 +44,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: DurOffsetAlignment = .center, +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/enums.zig b/src/enums.zig index 2cec482..47771da 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -54,3 +54,15 @@ pub const Bigclock = enum { en, fa, }; + +pub const DurOffsetAlignment = enum { + topleft, + topcenter, + topright, + centerleft, + center, + centerright, + bottomleft, + bottomcenter, + bottomright, +}; 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(); }, }