Animatable generic

This commit is contained in:
Jeeves 2025-05-30 19:18:50 -06:00
parent 0eb71cc4e6
commit 707fbd98bd

View file

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