diff options
Diffstat (limited to 'src/Presentation.zig')
| -rw-r--r-- | src/Presentation.zig | 39 |
1 files changed, 38 insertions, 1 deletions
diff --git a/src/Presentation.zig b/src/Presentation.zig index 15ed9ea6..5cec73e0 100644 --- a/src/Presentation.zig +++ b/src/Presentation.zig @@ -183,7 +183,7 @@ pub fn sync(self: *Presentation, p: *Pardes) void { prior.?.from else old.box; - const next = stamp(p, .{ + var next = stamp(p, .{ .serial = pane.serial, .pane = @intCast(id), .phase = if (!same_lifetime or @@ -192,6 +192,10 @@ pub fn sync(self: *Presentation, p: *Pardes) void { .from = from, .to = target, }); + // Retargeted mid-way from where it shows: it keeps its speed. + if (same_lifetime and shown != null) if (prior) |old_move| { + next.launch = carriedLaunch(old_move, next); + }; self.tracks[id] = next; changed = true; animated_change = true; @@ -230,6 +234,23 @@ fn stamp(p: *const Pardes, track: Track) Track { return stamped; } +/// A retargeted move's starting speed, in its own lengths per unit of +/// progress, from the old move's speed now (cells per frame), along the +/// new move's longest side; within four lengths per unit either way. +fn carriedLaunch(old_move: Track, next: Track) f32 { + const v = old_move.velocity(); + if (next.frames() <= 1) return 0; + const steps: f32 = @floatFromInt(next.frames() - 1); + const d: [4]f32 = .{ next.to.x - next.from.x, next.to.y - next.from.y, next.to.w - next.from.w, next.to.h - next.from.h }; + const vs: [4]f32 = .{ v.x, v.y, v.w, v.h }; + var axis: usize = 0; + for (d, 0..) |value, i| if (@abs(value) > @abs(d[axis])) { + axis = i; + }; + if (@abs(d[axis]) < 1e-3) return 0; + return std.math.clamp(vs[axis] * steps / d[axis], -4, 4); +} + fn appendClosing(self: *Presentation, track: Track) bool { std.debug.assert(track.phase == .closing); const baseline = self.previous_layout[track.pane] orelse return false; @@ -784,3 +805,19 @@ test "presentation snapshots own compact body and tag rows across later edits" { try std.testing.expectEqual(@as(u8, 'B'), state.previous_body_layers[0].cells[0].text[0]); try std.testing.expectEqual(@as(u8, 'U'), state.previous_tag_layers[0].cells[0].text[0]); } + +test "a move retargeted mid-way carries its speed into the new one" { + const smooth = @intFromEnum(animation.Motion.Flavour.smooth) + 1; + const first: Track = .{ .serial = 1, .phase = .moving, .effect = .slide, .motion = smooth, .frame = 4, .frame_count = 14, .from = .{ .x = 0, .y = 1, .w = 40, .h = 20 }, .to = .{ .x = 40, .y = 1, .w = 40, .h = 20 } }; + const was = first.velocity(); + try std.testing.expect(was.x > 0.5); + var next: Track = .{ .serial = 1, .phase = .moving, .effect = .slide, .motion = smooth, .frame_count = 14, .from = first.presented(), .to = .{ .x = 60, .y = 1, .w = 40, .h = 20 } }; + next.launch = carriedLaunch(first, next); + try std.testing.expect(next.launch > 0); + // It sets off at the speed the old one had, not from a stop. + const now = next.velocity(); + try std.testing.expectApproxEqRel(was.x, now.x, 0.15); + // And still lands exactly. + next.frame = 13; + try std.testing.expectEqual(next.to.x, next.presented().x); +} |
