summaryrefslogtreecommitdiff
path: root/src/Presentation.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Presentation.zig')
-rw-r--r--src/Presentation.zig59
1 files changed, 55 insertions, 4 deletions
diff --git a/src/Presentation.zig b/src/Presentation.zig
index 3b5bb3e0..15ed9ea6 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: Track = .{
+ const next = stamp(p, .{
.serial = pane.serial,
.pane = @intCast(id),
.phase = if (!same_lifetime or
@@ -191,19 +191,19 @@ pub fn sync(self: *Presentation, p: *Pardes) void {
.effect = effect,
.from = from,
.to = target,
- };
+ });
self.tracks[id] = next;
changed = true;
animated_change = true;
} else {
- const next: Track = .{
+ const next = stamp(p, .{
.serial = pane.serial,
.pane = @intCast(id),
.phase = .opening,
.effect = effect,
.from = openingBox(effect, target, p.screen_w),
.to = target,
- };
+ });
self.tracks[id] = next;
changed = true;
animated_change = true;
@@ -215,6 +215,21 @@ pub fn sync(self: *Presentation, p: *Pardes) void {
if (changed and self.acknowledged) self.pending = true;
}
+/// A pane that moves (slides, zooms or rises) follows the Motion flavour:
+/// its curve, and a length in proportion (animation.Motion.span).
+fn stamp(p: *const Pardes, track: Track) Track {
+ var stamped = track;
+ switch (track.effect) {
+ .slide, .zoom, .vertical => {
+ const motion = animation.Motion.of(p.settings.motion);
+ stamped.motion = @intFromEnum(p.settings.motion) + 1;
+ stamped.frame_count = @intFromFloat(@max(2, @round(@as(f32, @floatFromInt(track.effect.frames())) * motion.span)));
+ },
+ else => {},
+ }
+ return stamped;
+}
+
fn appendClosing(self: *Presentation, track: Track) bool {
std.debug.assert(track.phase == .closing);
const baseline = self.previous_layout[track.pane] orelse return false;
@@ -388,6 +403,10 @@ pub fn pointerFractional(self: *const Presentation, cols: u16, rows: u16, x: f32
.slide, .zoom, .vertical => {
const target = track.contentBox();
if (track.effect == .vertical and !boxContainsPoint(target, x, y)) continue;
+ // Past its mark (a flavour's overshoot or wind-up) a pane
+ // is drawn over a neighbour; a point there is the
+ // neighbour's, never the moving pane's.
+ if (pastMark(track) and !boxContainsPoint(target, x, y)) continue;
const shown = track.presented();
if (!boxContainsPoint(shown, x, y)) {
if (track.effect == .vertical) return null;
@@ -410,6 +429,13 @@ pub fn pointerFractional(self: *const Presentation, cols: u16, rows: u16, x: f32
return .{ .x = x, .y = y };
}
+/// A flavour's overshoot or wind-up: the pane is drawn beyond the path from
+/// where it was to where it goes.
+fn pastMark(track: Track) bool {
+ const t = track.amount();
+ return t < 0 or t > 1;
+}
+
fn boxContainsPoint(box: Box, x: f32, y: f32) bool {
return box.w > 0 and box.h > 0 and x >= box.x and y >= box.y and x < box.x + box.w and y < box.y + box.h;
}
@@ -436,6 +462,8 @@ pub fn pointer(self: *const Presentation, cols: u16, rows: u16, col: u16, row: u
switch (track.effect) {
.slide, .zoom => {
const shown = track.presented();
+ // Past its mark, the neighbour's cells stay the neighbour's.
+ if (pastMark(track) and !boxContainsCell(track.to, col, row)) continue;
if (shown.w <= 0 or shown.h <= 0 or !boxContainsCell(shown, col, row)) continue;
const x = @as(f32, @floatFromInt(col)) + 0.5;
const y = @as(f32, @floatFromInt(row)) + 0.5;
@@ -688,6 +716,29 @@ test "presentation submission failure preserves shown geometry until retry is ac
try std.testing.expectEqual(CellPosition{ .col = 5, .row = 3 }, p.presentation.pointer(p.screen_w, p.screen_h, 5, 3).?);
}
+test "a pane past its mark never takes a neighbour's click" {
+ // Bouncy, at its peak: pane 0 slides right to 0..40 and swings past it,
+ // over the neighbour at 40..80.
+ var presentation: Presentation = .{};
+ var track: Track = .{ .serial = 7, .effect = .slide, .motion = @intFromEnum(animation.Motion.Flavour.bouncy) + 1, .frame_count = 20, .from = .{ .x = -40, .y = 0, .w = 40, .h = 20 }, .to = .{ .x = 0, .y = 0, .w = 40, .h = 20 } };
+ var peak: u16 = 0;
+ var furthest: f32 = -1e9;
+ while (track.frame < track.frames()) : (track.frame += 1) {
+ if (track.presented().x > furthest) {
+ furthest = track.presented().x;
+ peak = track.frame;
+ }
+ }
+ track.frame = peak;
+ try std.testing.expect(track.presented().x > 2);
+ presentation.shown_tracks[0] = track;
+ // A cell of the neighbour under the overshoot is the neighbour's own.
+ const col: u16 = 41;
+ try std.testing.expectEqual(CellPosition{ .col = col, .row = 5 }, presentation.pointer(80, 20, col, 5).?);
+ const point = presentation.pointerFractional(80, 20, 41.5, 5.5).?;
+ try std.testing.expectApproxEqAbs(@as(f32, 41.5), point.x, 1e-4);
+}
+
test "fractional panel inverse preserves compact header rows through slide and zoom" {
const layer: pardes.Layer = .{ .serial = 7, .viewport = .{ .x = 4, .y = 3, .w = 30, .h = 10 }, .cols = 30, .rows = 15, .context_rows = 4 };
for ([_]Transition{ .slide, .zoom, .vertical }) |effect| {