summaryrefslogtreecommitdiff
path: root/src/animation.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/animation.zig')
-rw-r--r--src/animation.zig33
1 files changed, 29 insertions, 4 deletions
diff --git a/src/animation.zig b/src/animation.zig
index 12d352d1..855fef03 100644
--- a/src/animation.zig
+++ b/src/animation.zig
@@ -136,6 +136,10 @@ pub const Track = extern struct {
frame_count: u16 = 0,
from: Box = .{},
to: Box = .{},
+ /// The move's speed as it starts, in its lengths per unit of progress:
+ /// 0 sets off as the flavour does; a move retargeted mid-way carries
+ /// the old one's speed on (Presentation.sync), so it never stops dead.
+ launch: f32 = 0,
pub fn active(track: Track) bool {
return track.effect != .off and track.frame < track.frames();
@@ -151,7 +155,7 @@ pub const Track = extern struct {
if (track.phase == .closing and track.effect == .vertical)
return progressEased(.in_cubic, track.frames(), track.frame);
if (track.flavour()) |motion|
- return motion.ease(track.progress());
+ return motion.easeFrom(track.progress(), track.launch);
return progressEased(track.effect.easing(), track.frames(), track.frame);
}
@@ -163,6 +167,16 @@ pub const Track = extern struct {
return Motion.of(which);
}
+ /// How fast its presented box moves now, in cells per frame of its
+ /// own (x, y, w, h); zero when it follows no flavour or has landed.
+ pub fn velocity(track: Track) Box {
+ const motion = track.flavour() orelse return .{};
+ const u = track.progress();
+ if (u >= 1 or track.frames() <= 1) return .{};
+ const rate = motion.speedFrom(u, track.launch) / @as(f32, @floatFromInt(track.frames() - 1));
+ return .{ .x = (track.to.x - track.from.x) * rate, .y = (track.to.y - track.from.y) * rate, .w = (track.to.w - track.from.w) * rate, .h = (track.to.h - track.from.h) * rate };
+ }
+
fn progress(track: Track) f32 {
if (track.frames() <= 1 or track.frame >= track.frames() - 1) return 1;
return @as(f32, @floatFromInt(track.frame)) / @as(f32, @floatFromInt(track.frames() - 1));
@@ -180,7 +194,7 @@ pub const Track = extern struct {
// box, so a click lands on the glyph it is drawn as).
const motion = track.flavour() orelse return box;
if (motion.stretch == 0) return box;
- const factor = 1 + motion.stretch * std.math.clamp(motion.speed(track.progress()) / 4, -1, 1);
+ const factor = 1 + motion.stretch * std.math.clamp(motion.speedFrom(track.progress(), track.launch) / 4, -1, 1);
if (@abs(track.to.x - track.from.x) >= @abs(track.to.y - track.from.y)) {
const w = box.w * factor;
box.x -= (w - box.w) / 2;
@@ -685,16 +699,27 @@ pub const Motion = struct {
/// flavour's spring (wind-up, overshoot and all), landing exactly on 1
/// at its end. `instant` is there at once.
pub fn ease(motion: Motion, u: f32) f32 {
+ return motion.easeFrom(u, 0);
+ }
+
+ /// `ease` for a move that starts already moving at `launch` lengths
+ /// per unit of `u` (a retarget): no wind-up then, the speed carried on.
+ pub fn easeFrom(motion: Motion, u: f32, launch: f32) f32 {
if (motion.instant or u >= 1) return 1;
if (u <= 0) return 0;
- const spring: Spring = .{ .omega = motion.norm, .zeta = motion.zeta, .target = 1, .from = 0, .velocity = (motion.launch - 2 * motion.anticipate) * motion.norm, .settled = false };
+ const kick = if (launch != 0) launch else (motion.launch - 2 * motion.anticipate) * motion.norm;
+ const spring: Spring = .{ .omega = motion.norm, .zeta = motion.zeta, .target = 1, .from = 0, .velocity = kick, .settled = false };
return spring.value(@intFromFloat(u * std.time.ns_per_s));
}
/// The same move's speed at `u`, in lengths per unit of `u`.
pub fn speed(motion: Motion, u: f32) f32 {
+ return motion.speedFrom(u, 0);
+ }
+
+ pub fn speedFrom(motion: Motion, u: f32, launch: f32) f32 {
const h: f32 = 1.0 / 256.0;
- return (motion.ease(@min(1, u + h)) - motion.ease(@max(0, u - h))) / (@min(1, u + h) - @max(0, u - h));
+ return (motion.easeFrom(@min(1, u + h), launch) - motion.easeFrom(@max(0, u - h), launch)) / (@min(1, u + h) - @max(0, u - h));
}
};