non-generic animation

This commit is contained in:
Jeeves 2025-05-30 20:27:21 -06:00
parent 707fbd98bd
commit 35dd123504

View file

@ -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.