diff --git a/src/main.zig b/src/main.zig index b3e0879..1106b36 100644 --- a/src/main.zig +++ b/src/main.zig @@ -22,30 +22,31 @@ pub fn main() !void { ); defer column.deinit(); - var item1 = AnimatableItem{ .a = .{ .v = .init( + var item1 = Item.init( raylib.LoadTexture("menu/game/CometCrash/ICON0.PNG"), "Comet Crash", "3/1/2025 23:11", - ) } }; - var item2 = AnimatableItem{ .a = .{ .v = .init( + ); + var item2 = Item.init( raylib.LoadTexture("menu/game/LBP1/ICON0.PNG"), "LittleBigPlanet", "3/1/2025 23:15", - ) } }; - var item3 = AnimatableItem{ .a = .{ .v = .init( + ); + var item3 = Item.init( raylib.LoadTexture("menu/game/LBP2/ICON0.PNG"), "LittleBigPlanet 2", "3/1/2025 23:26", - ) } }; - var item4 = AnimatableItem{ .a = .{ .v = .init( + ); + var item4 = Item.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); try column.appendItem(&item4); + column.refresh(false); raylib.SetTargetFPS(120); while (!raylib.WindowShouldClose()) { @@ -55,7 +56,6 @@ 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('S')) raylib.TakeScreenshot("screenshot.png"); column.updatePositions(); @@ -131,7 +131,7 @@ pub const Column = struct { icon: raylib.Texture2D, title: []const u8, - items: std.ArrayList(*AnimatableItem), + items: std.ArrayList(*Item), selected: usize = 0, pub fn init(allocator: Allocator, icon: raylib.Texture2D, title: []const u8) Column { @@ -173,25 +173,25 @@ 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.update(); - for (self.items.items) |item| item.a.v.draw(); + for (self.items.items) |item| item.draw(); icon.draw(); title.draw(); } - pub fn appendItem(self: *Column, item: *AnimatableItem) !void { + pub fn appendItem(self: *Column, item: *Item) !void { try self.items.append(item); - self.refresh(); + self.refresh(false); } - pub fn insertItem(self: *Column, idx: usize, item: *AnimatableItem) !void { + pub fn insertItem(self: *Column, idx: usize, item: *Item) !void { try self.items.insert(idx, item); - self.refresh(); + self.refresh(false); } pub fn removeItem(self: *Column, idx: usize) void { _ = try self.items.orderedRemove(idx); - self.refresh(); + self.refresh(false); } fn updatePositions(self: *Column) void { @@ -200,112 +200,21 @@ pub const Column = struct { if (up and self.selected > 0) self.selected -= 1; if (down and self.selected < self.items.items.len - 1) self.selected += 1; - if (up or down) self.refresh(); + if (up or down) self.refresh(true); } - fn refresh(self: *Column) void { + fn refresh(self: *Column, animate: bool) void { var y = scales.column_item_start; for (self.items.items[self.selected..]) |item| { - item.set("position", raylib.Vector2{ .x = scales.column_position_center.x, .y = y }); + item.setPosition(animate, .{ .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 }, + animator: Animator, icon: raylib.Texture2D, title: []const u8, @@ -318,30 +227,19 @@ pub const Item = struct { .icon = texture, .title = title, .subtitle = subtitle, - // .icon_scale = scales.item_icon_small_scale, - // .start_scale = scales.item_icon_small_scale, + .animator = .{ + .start_position = .{}, + .target_position = .{}, + }, }; } pub fn draw(self: *Item) void { - // 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 = self.position; - var icon = Image{ .texture = self.icon, .box = .{ - .x = position.x - scales.item_icon_small_width / 2.0 + 67.0 / 2.0, - .y = position.y, + .x = self.position.x - scales.item_icon_small_width / 2.0 + 67.0 / 2.0, + .y = self.position.y, .w = scales.item_icon_small_width, .h = scales.item_icon_small_height, }, @@ -378,18 +276,35 @@ 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 const Animator = struct { + time: f32 = 0, + start_position: raylib.Vector2, + target_position: raylib.Vector2, - // pub fn setPosition(self: *Item, position: raylib.Vector2) void { - // self.start_position = self.position; - // self.position = position; - // self.time = 0; - // } + fn easeOutExpo(self: Animator, length: f32) f32 { + return 1.0 - std.math.pow(f32, 2, -10 * std.math.clamp(self.time / length, 0.0, 1.0)); + } + }; + fn update(self: *Item) void { + self.animator.time += raylib.GetFrameTime(); + self.position = raylib.Vector2Lerp( + self.animator.start_position, + self.animator.target_position, + self.animator.easeOutExpo(0.333), + ); + } + + pub fn setPosition(self: *Item, animate: bool, position: raylib.Vector2) void { + if (animate) { + self.animator.time = 0; + self.animator.start_position = self.position; + self.animator.target_position = position; + } else { + self.animator.start_position = position; + self.animator.target_position = position; + } + } }; /// Draws the dynamic gradient background.