summaryrefslogtreecommitdiff
path: root/src/animation.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 06:41:28 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commitff252345bc3e1edc1aee93ca826f294bf9a7874f (patch)
tree61c8d8ff2a13e7ceca77d439eeff8edc41fa644c /src/animation.zig
parent09ec515c5e94de741104038cd61f18b2b997e78c (diff)
downloadpardes-ff252345bc3e1edc1aee93ca826f294bf9a7874f.tar.gz
pardes-ff252345bc3e1edc1aee93ca826f294bf9a7874f.zip
G6: a wheel's notch scrolls the core at once and the picture glides after it
A mouse wheel's notch (SDL's whole-number step, notchOf) scrolls the core by its lines at once, so keys and clicks act where the text is, and the picture follows on the Motion flavour's spring at half again its pace, critically damped whatever the flavour (text never passes its mark), carried from its current speed (animation.Spring.carry), so notches in a row flow on. How far the rows moved is measured on the next frame against the last (Glide.measure: a wrapped line's rows, a terminal's output, with a couple of rows' slack for the cursor's line and its number); the rows that left are kept, nearest first, and drawn in the gap the lag opens; the lag is never more than the body's height. A touchpad's fractions move the picture under the finger as before; `Motion off` lands a notch at once; a PDF page scrolls as before. The glide runs on the core's continuous path: Pardes.shell_continuous keeps frames coming every loop while it moves and carries a virtual clock with it; idle, nothing. The test feed takes wheel events (ESC]777;mouse;wheel;<amount>;<x>;<y>BEL). Tests: notch vs fraction; a carried spring keeps its speed and never passes its mark; a notch over two-row lines lags two rows, keeps the rows that left, settles frame by frame and leaves the continuous path, and lands at once with Motion off. Gates as G5. Feel review: .scratch/render/scroll/glide-{smooth,crisp,bouncy,off}{,-quarter}.mp4 and strips (a notch down, three, then two up).
Diffstat (limited to 'src/animation.zig')
-rw-r--r--src/animation.zig26
1 files changed, 26 insertions, 0 deletions
diff --git a/src/animation.zig b/src/animation.zig
index 9002a2c6..12d352d1 100644
--- a/src/animation.zig
+++ b/src/animation.zig
@@ -744,6 +744,14 @@ pub const Spring = struct {
return spring.at(now_ns).value;
}
+ /// Moves what it shows by `by` at `now_ns`, its speed kept, toward the
+ /// same target (a scroll's picture falling behind the text it follows),
+ /// at `motion`'s pace, never past its mark.
+ pub fn carry(spring: *Spring, by: f32, now_ns: u64, motion: Motion) void {
+ const state = spring.at(now_ns);
+ spring.* = .{ .omega = motion.omega, .zeta = 1, .target = spring.target, .from = state.value + by, .velocity = state.velocity, .from_ns = now_ns, .settled = false };
+ }
+
/// Heads for `target` from wherever it is at `now_ns`, moving as it was,
/// at the pace and damping `motion` gives: at once when it is `off`.
pub fn retarget(spring: *Spring, target: f32, now_ns: u64, motion: Motion) void {
@@ -769,6 +777,24 @@ pub const Spring = struct {
}
};
+test "a carried spring keeps its speed, heads back to its mark and never passes it" {
+ const ms = std.time.ns_per_ms;
+ var spring: Spring = .{};
+ const smooth = Motion.of(.smooth);
+ spring.carry(-3, 0, smooth);
+ try std.testing.expectEqual(@as(f32, -3), spring.value(0));
+ const mid = spring.at(40 * ms);
+ try std.testing.expect(mid.value > -3 and mid.value < 0 and mid.velocity > 0);
+ // Another notch mid-way: one more row behind, still moving back.
+ spring.carry(-1, 40 * ms, smooth);
+ const after = spring.at(40 * ms);
+ try std.testing.expectApproxEqAbs(mid.value - 1, after.value, 1e-5);
+ try std.testing.expectApproxEqAbs(mid.velocity, after.velocity, 1e-4);
+ var t: u64 = 40 * ms;
+ while (spring.step(t)) : (t += frame_ns) try std.testing.expect(spring.value(t) <= 1e-4);
+ try std.testing.expect(t < 400 * ms);
+}
+
test "a spring settles without overshoot and keeps its velocity when retargeted" {
var spring: Spring = .{};
const ms = std.time.ns_per_ms;