diff --git a/src/main.zig b/src/main.zig index 49d2544..3b51e22 100644 --- a/src/main.zig +++ b/src/main.zig @@ -102,11 +102,15 @@ pub const Scales = struct { item_title_font_size: f32, item_subtitle_font_size: f32, + column_icon_padding_top_curve: Curve, column_item_spacing_curve: Curve, + column_selected_item_icon_scale_curve: Curve, column_title_font_size: f32, column_position_center: raylib.Vector2, column_position_spacing: f32, column_item_spacing: f32, + column_selected_item_icon_scale: f32, + column_icon_padding_top: f32, column_item_after_start: f32, column_item_before_start: f32, @@ -115,6 +119,7 @@ pub const Scales = struct { self.font_size_curve = Curve{ .points = &[_]Curve.Point{ .{ .x = 0, .y = 18 }, + .{ .x = 0.25, .y = 18 }, .{ .x = 1, .y = 26 }, } }; @@ -123,12 +128,26 @@ pub const Scales = struct { .{ .x = 1, .y = 80 }, } }; + self.column_icon_padding_top_curve = Curve{ .points = &[_]Curve.Point{ + .{ .x = 0, .y = 0 }, + .{ .x = 0.25, .y = 0 }, + .{ .x = 1, .y = 16 }, + } }; + self.column_item_spacing_curve = Curve{ .points = &[_]Curve.Point{ - .{ .x = 0, .y = 16, .right_tangent = -90 }, + .{ .x = 0, .y = 10, .right_tangent = -90 }, .{ .x = 0.25, .y = 0 }, .{ .x = 1, .y = 0 }, } }; + self.column_selected_item_icon_scale_curve = Curve{ + .points = &[_]Curve.Point{ + .{ .x = 0, .y = 1 }, + .{ .x = 0.25, .y = 1.5 }, + .{ .x = 1, .y = 2 }, + }, + }; + self.recalculate(); return self; } @@ -152,12 +171,13 @@ pub const Scales = struct { }; self.column_position_spacing = 64; self.column_item_spacing = self.column_item_spacing_curve.sample(height); + self.column_selected_item_icon_scale = self.column_selected_item_icon_scale_curve.sample(height); + self.column_icon_padding_top = self.column_icon_padding_top_curve.sample(height); self.column_item_after_start = self.column_position_center.y + self.icon_height + self.column_title_font_size + 12; - self.column_item_before_start = self.column_position_center.y; + self.column_item_before_start = self.column_position_center.y - self.column_icon_padding_top; } }; -// TODO selected item scale up // TODO item actions // TODO item groups // TODO item group sort @@ -205,7 +225,6 @@ pub const Column = struct { .align_h = .center, }; - for (self.items.items) |item| item.update(); for (self.items.items) |item| item.draw(); icon.draw(); @@ -239,22 +258,24 @@ pub const Column = struct { fn refresh(self: *Column, comptime animate: bool) void { var y = scales.column_item_before_start - (scales.icon_height + scales.column_item_spacing) * @as(f32, @floatFromInt(self.selected)); for (self.items.items[0..self.selected]) |item| { - item.setPosition(animate, .{ .x = scales.column_position_center.x, .y = y }); + item.position.set(animate, .{ .x = scales.column_position_center.x, .y = y }); + item.icon_scale.set(animate, 1.0); y += scales.icon_height + scales.column_item_spacing; } y = scales.column_item_after_start; - for (self.items.items[self.selected..]) |item| { - item.setPosition(animate, .{ .x = scales.column_position_center.x, .y = y }); - y += scales.icon_height + scales.column_item_spacing; + for (self.items.items[self.selected..], self.selected..) |item, i| { + const icon_scale = if (i == self.selected) scales.column_selected_item_icon_scale else 1.0; + item.position.set(animate, .{ .x = scales.column_position_center.x, .y = y }); + item.icon_scale.set(animate, icon_scale); + y += scales.icon_height * icon_scale + scales.column_item_spacing + (std.math.pow(f32, 4.0, icon_scale) - 4.0); } } }; -// TODO animated item icon scale // TODO animated text fade in/out pub const Item = struct { - position: raylib.Vector2 = .{ .x = 0, .y = 0 }, - animator: Animator, + position: Tweener(raylib.Vector2, .{}) = .{}, + icon_scale: Tweener(f32, 1.0) = .{}, icon: raylib.Texture2D, title: []const u8, @@ -266,21 +287,20 @@ pub const Item = struct { .icon = texture, .title = title, .subtitle = subtitle, - .animator = .{ - .start_position = .{}, - .target_position = .{}, - }, }; } pub fn draw(self: *Item) void { + const position = self.position.update(); + const icon_scale = self.icon_scale.update(); + var icon = Image{ .texture = self.icon, .box = .{ - .x = self.position.x - scales.icon_width / 2.0 + 67.0 / 2.0, - .y = self.position.y, - .w = scales.icon_width, - .h = scales.icon_height, + .x = position.x - scales.icon_width / 2.0 * (icon_scale - 1), + .y = position.y, + .w = scales.icon_width * icon_scale, + .h = scales.icon_height * icon_scale, }, }; @@ -314,38 +334,47 @@ pub const Item = struct { title.draw(); subtitle.draw(); } - - pub const Animator = struct { - time: f32 = 0, - start_position: raylib.Vector2, - target_position: raylib.Vector2, - - 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, comptime 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; - } - } }; +pub fn Tweener(comptime T: type, comptime default: T) type { + return struct { + time: f32 = 0.0, + length: f32 = 0.333, + + start: T = default, + target: T = default, + current: T = default, + + fn set(self: *Self, comptime animate: bool, value: T) void { + if (animate) { + self.time = 0; + self.start = self.current; + self.target = value; + } else { + self.start = value; + self.target = value; + self.current = value; + } + } + + fn update(self: *Self) T { + self.time += raylib.GetFrameTime(); + self.current = switch (T) { + f32 => std.math.lerp(self.start, self.target, self.easeOutExpo()), + raylib.Vector2 => raylib.Vector2Lerp(self.start, self.target, self.easeOutExpo()), + else => @compileError("no lerp function for type " ++ @typeName(T)), + }; + return self.current; + } + + fn easeOutExpo(self: Self) f32 { + return 1.0 - std.math.pow(f32, 2, -10 * std.math.clamp(self.time / self.length, 0.0, 1.0)); + } + + const Self = @This(); + }; +} + /// Draws the dynamic gradient background. // TODO shift based on time of day // TODO image wallpaper