diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 06:58:19 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | c1dbb16d069a54f52533f346655b0fc49026c9a4 (patch) | |
| tree | 0a82753404805951cf7fda3973b86d7741f73833 /src/Presentation.zig | |
| parent | ff252345bc3e1edc1aee93ca826f294bf9a7874f (diff) | |
| download | pardes-c1dbb16d069a54f52533f346655b0fc49026c9a4.tar.gz pardes-c1dbb16d069a54f52533f346655b0fc49026c9a4.zip | |
G7: a moving pane casts a shadow, a retargeted move keeps its speed, a dissolve crossfades
- Track.launch: a move retargeted mid-way (Presentation.sync) starts at
the old move's speed along the new one's longest side (carriedLaunch,
Motion.easeFrom/speedFrom), so a second change during a slide bends the
motion instead of stopping it dead. The macOS track ABI grows the float
(pardes.h, 48 bytes; its reserved byte is named motion, as in Zig).
- A pane that slides, zooms or rises casts Lift's soft shadow at
sin(pi x progress) of its strength, on the other panes' bodies and
rails alone, under the same contrast ceiling, carried by its track.
- The GUI's PanelDissolve is a crossfade: the new cell drawn first, the
old one over it at 1 - progress, premultiplied, on the blending cell
pipeline for such groups (new cells now emitted before old ones, as the
old layers already came after the new). sRGB blend, not linear light
(documented). The tty keeps its per-cell swap.
Presentation.zig's and animation.zig's tests were not in any test root;
pardes.zig's test block now takes them in.
Tests: a retargeted move carries its speed and still lands exactly; a
moving pane's shadow is clipped off every tag, grip and its own body,
none at the start or the landing, most mid-way. GUI golden 18 changes by
the shadow (frame 6 of a slide). Gates as G6. Feel review:
.scratch/render/transitions/.
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); +} |
