diff options
Diffstat (limited to 'src/animation.zig')
| -rw-r--r-- | src/animation.zig | 33 |
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)); } }; |
