From 707fbd98bddee5d5d7ad8ac416a7680b5f6463b2 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Fri, 30 May 2025 19:18:50 -0600 Subject: [PATCH] Animatable generic --- src/main.zig | 158 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 122 insertions(+), 36 deletions(-) diff --git a/src/main.zig b/src/main.zig index 7b310b7..b3e0879 100644 --- a/src/main.zig +++ b/src/main.zig @@ -22,26 +22,26 @@ pub fn main() !void { ); defer column.deinit(); - var item1 = Item.init( + var item1 = AnimatableItem{ .a = .{ .v = .init( raylib.LoadTexture("menu/game/CometCrash/ICON0.PNG"), "Comet Crash", "3/1/2025 23:11", - ); - var item2 = Item.init( + ) } }; + var item2 = AnimatableItem{ .a = .{ .v = .init( raylib.LoadTexture("menu/game/LBP1/ICON0.PNG"), "LittleBigPlanet", "3/1/2025 23:15", - ); - var item3 = Item.init( + ) } }; + var item3 = AnimatableItem{ .a = .{ .v = .init( raylib.LoadTexture("menu/game/LBP2/ICON0.PNG"), "LittleBigPlanet 2", "3/1/2025 23:26", - ); - var item4 = Item.init( + ) } }; + var item4 = AnimatableItem{ .a = .{ .v = .init( raylib.LoadTexture("menu/game/LBP3/ICON0.PNG"), "LittleBigPlanet 3", "3/1/2025 23:48", - ); + ) } }; try column.appendItem(&item1); try column.appendItem(&item2); try column.appendItem(&item3); @@ -55,7 +55,7 @@ pub fn main() !void { scales.recalculate(); } if (raylib.IsKeyPressed('D')) debug_draw = !debug_draw; - if (raylib.IsKeyPressed('Z')) item1.setLarge(!item1.large); + // if (raylib.IsKeyPressed('Z')) item1.setLarge(!item1.large); if (raylib.IsKeyPressed('S')) raylib.TakeScreenshot("screenshot.png"); column.updatePositions(); @@ -131,9 +131,8 @@ pub const Column = struct { icon: raylib.Texture2D, title: []const u8, - items: std.ArrayList(*Item), + items: std.ArrayList(*AnimatableItem), selected: usize = 0, - start_y: f32 = undefined, pub fn init(allocator: Allocator, icon: raylib.Texture2D, title: []const u8) Column { raylib.SetTextureFilter(icon, raylib.TEXTURE_FILTER_BILINEAR); @@ -173,18 +172,19 @@ pub const Column = struct { }; // self.start_y = scales.column_position_center.y + icon.box.h + title.box.h + scales.column_item_spacing; - for (self.items.items) |item| item.draw(); + for (self.items.items) |item| item.update(); + for (self.items.items) |item| item.a.v.draw(); icon.draw(); title.draw(); } - pub fn appendItem(self: *Column, item: *Item) !void { + pub fn appendItem(self: *Column, item: *AnimatableItem) !void { try self.items.append(item); self.refresh(); } - pub fn insertItem(self: *Column, idx: usize, item: *Item) !void { + pub fn insertItem(self: *Column, idx: usize, item: *AnimatableItem) !void { try self.items.insert(idx, item); self.refresh(); } @@ -206,18 +206,106 @@ pub const Column = struct { fn refresh(self: *Column) void { var y = scales.column_item_start; for (self.items.items[self.selected..]) |item| { - item.setPosition(.{ .x = scales.column_position_center.x, .y = y }); + item.set("position", raylib.Vector2{ .x = scales.column_position_center.x, .y = y }); y += scales.item_icon_small_height + scales.column_item_spacing; } } }; +pub fn Animatable(comptime T: type, comptime props: anytype) type { + const Type = @typeInfo(T); + + var fields: [props.len * 2 + 2]std.builtin.Type.StructField = undefined; + inline for (props, 0..) |prop, i| { + const field = blk: inline for (Type.@"struct".fields) |f| { + if (std.mem.eql(u8, f.name, prop)) break :blk f; + }; + // const default_value = switch (field.type) { + // f32 => 0, + // raylib.Vector2 => .{ .x = 0, .y = 0 }, + // else => @compileError("no default value for type " ++ @typeName(field.type)), + // }; + fields[i * 2] = .{ + .name = "start_" ++ prop, + .type = field.type, + .default_value_ptr = field.default_value_ptr, + .is_comptime = false, + .alignment = 0, + }; + fields[i * 2 + 1] = .{ + .name = "target_" ++ prop, + .type = field.type, + .default_value_ptr = field.default_value_ptr, + .is_comptime = false, + .alignment = 0, + }; + } + fields[props.len * 2] = .{ + .name = "time", + .type = f32, + .default_value_ptr = &@as(f32, 0.0), + .is_comptime = false, + .alignment = 0, + }; + fields[props.len * 2 + 1] = .{ + .name = "v", + .type = T, + .default_value_ptr = null, + .is_comptime = false, + .alignment = 0, + }; + + const Self = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + .is_tuple = false, + } }); + return struct { + a: Self, + + pub fn set(self: *@This(), comptime field_name: []const u8, value: anytype) void { + self.a.time = 0; + @field(self.a, "start_" ++ field_name) = @field(self.a.v, field_name); + @field(self.a, "target_" ++ field_name) = value; + } + + pub fn update(self: *@This()) void { + self.a.time += raylib.GetFrameTime(); + inline for (props) |prop| { + const field = comptime blk: for (@typeInfo(T).@"struct".fields) |f| { + if (std.mem.eql(u8, f.name, prop)) break :blk f; + }; + @field(self.a.v, field.name) = switch (field.type) { + f32 => std.math.lerp( + @field(self.a, "start_" ++ field.name), + @field(self.a, "target_" ++ field.name), + easeOutExpo(self.a.time / 0.333), + ), + raylib.Vector2 => raylib.Vector2Lerp( + @field(self.a, "start_" ++ field.name), + @field(self.a, "target_" ++ field.name), + easeOutExpo(self.a.time / 0.333), + ), + else => @compileError("cannot animate type " ++ @typeName(field.type)), + }; + } + } + + fn easeOutExpo(x: f32) f32 { + return 1.0 - std.math.pow(f32, 2, -10 * std.math.clamp(x, 0.0, 1.0)); + } + }; +} + +pub const AnimatableItem = Animatable(Item, .{"position"}); + pub const Item = struct { position: raylib.Vector2 = .{ .x = 0, .y = 0 }, // icon_scale: f32, // start_scale: f32, - time: f32 = 0.0, - start_position: raylib.Vector2 = .{ .x = 0, .y = 0 }, + // time: f32 = 0.0, + // start_position: raylib.Vector2 = .{ .x = 0, .y = 0 }, icon: raylib.Texture2D, title: []const u8, @@ -236,17 +324,18 @@ pub const Item = struct { } pub fn draw(self: *Item) void { - self.time += raylib.GetFrameTime(); + // self.time += raylib.GetFrameTime(); // self.icon_scale = std.math.lerp( // self.start_scale, // if (self.large) scales.item_icon_large_scale else scales.item_icon_small_scale, // easeOutExpo(self.time / 0.333), // ); - const position = raylib.Vector2Lerp( - self.start_position, - self.position, - easeOutExpo(self.time / 0.333), - ); + // const position = raylib.Vector2Lerp( + // self.start_position, + // self.position, + // easeOutExpo(self.time / 0.333), + // ); + const position = self.position; var icon = Image{ .texture = self.icon, @@ -289,21 +378,18 @@ pub const Item = struct { subtitle.draw(); } - pub fn setLarge(self: *Item, large: bool) void { - self.large = large; - // self.time = 0; - // self.start_scale = self.icon_scale; - } + // pub fn setLarge(self: *Item, large: bool) void { + // self.large = large; + // // self.time = 0; + // // self.start_scale = self.icon_scale; + // } - pub fn setPosition(self: *Item, position: raylib.Vector2) void { - self.start_position = self.position; - self.position = position; - self.time = 0; - } + // pub fn setPosition(self: *Item, position: raylib.Vector2) void { + // self.start_position = self.position; + // self.position = position; + // self.time = 0; + // } - fn easeOutExpo(x: f32) f32 { - return 1.0 - std.math.pow(f32, 2, -10 * std.math.clamp(x, 0.0, 1.0)); - } }; /// Draws the dynamic gradient background.