summaryrefslogtreecommitdiff
path: root/src/draw.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/draw.zig')
-rw-r--r--src/draw.zig48
1 files changed, 45 insertions, 3 deletions
diff --git a/src/draw.zig b/src/draw.zig
index 1f30a40b..9df76cfd 100644
--- a/src/draw.zig
+++ b/src/draw.zig
@@ -134,11 +134,12 @@ pub fn place(p: *Pardes, s: *Surface) void {
// snaps (it has no in-between), and so does nothing that shows it.
const shown = lift != .off or inactiveDim(p) != 0;
const motion = animation.Motion.of(if (shown and builtins.capabilities.lift) p.settings.motion else .off);
- spring.retarget(if (id == p.active) 1 else 0, p.now_ns, motion);
+ // A flavour exaggerates what is only a few pixels (Motion.gain).
+ spring.retarget(if (id == p.active) animation.Motion.of(p.settings.motion).gain else 0, p.now_ns, motion);
_ = spring.step(p.now_ns);
}
for (s.regions[0..s.nregions]) |*region| switch (region.kind) {
- .grip, .tag, .body, .rail => region.lift = if (lift != .off) p.lifts[region.owner].value(p.now_ns) else 0,
+ .grip, .tag, .body, .rail => region.lift = if (lift != .off) std.math.clamp(p.lifts[region.owner].value(p.now_ns), -1, 2) else 0,
.notice => region.lift = if (lift != .off) 1 else 0,
else => {},
};
@@ -416,7 +417,8 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface {
for (p.panes, p.lifts, 0..) |slot, spring, id| {
if (slot == null or id == p.active) continue;
- const amount = @as(f32, @floatFromInt(inactiveDim(p))) / 100 * std.math.clamp(1 - spring.value(p.now_ns), 0, 1);
+ const focus = spring.value(p.now_ns) / animation.Motion.of(p.settings.motion).gain;
+ const amount = @as(f32, @floatFromInt(inactiveDim(p))) / 100 * std.math.clamp(1 - focus, 0, 1);
if (amount > 0.001) recede(p, s, id, amount);
}
@@ -1604,6 +1606,46 @@ test "with no lift and no dim a focus switch asks for no frames" {
try std.testing.expect(!p.continuous());
}
+test "each Motion flavour reaches the lift as its own trajectory" {
+ // A grid snaps; the flavours are a pixel shell's.
+ if (!builtins.capabilities.lift) return error.SkipZigTest;
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 });
+ defer p.deinit();
+ _ = try p.setTestFile("text\n");
+ p.newScratchBelow(0);
+ var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer arena.deinit();
+ p.settings.lift = .shadow;
+ var peaks: [5]f32 = undefined;
+ const ms = std.time.ns_per_ms;
+ var start: u64 = std.time.ns_per_s;
+ for (std.enums.values(animation.Motion.Flavour), &peaks) |flavour, *peak| {
+ p.settings.motion = flavour;
+ p.active = 1;
+ p.advance(start);
+ _ = arena.reset(.retain_capacity);
+ _ = try p.render(arena.allocator());
+ start += 3 * std.time.ns_per_s;
+ p.advance(start);
+ _ = try p.render(arena.allocator());
+ p.active = 0;
+ peak.* = 0;
+ var t: u64 = 0;
+ while (t <= 1500 * ms) : (t += 4 * ms) {
+ p.advance(start + t);
+ _ = arena.reset(.retain_capacity);
+ const s = try p.render(arena.allocator());
+ const lift = for (s.regionList()) |region| {
+ if (region.kind == .grip and region.owner == 0) break region.lift;
+ } else unreachable;
+ peak.* = @max(peak.*, lift);
+ }
+ start += 3 * std.time.ns_per_s;
+ }
+ // Bouncy and playful pass their (exaggerated) mark; smooth never does.
+ try std.testing.expect(peaks[2] <= 1.001 and peaks[3] > 1.25 and peaks[4] > peaks[3]);
+}
+
test "a monkey over notices, prompts, tags and tiny panes at a gui's metrics never panics" {
// The snapshot harness runs the tty, whose tagline pitch is the body's,
// so it never reaches what a narrower tagline does to a notice's band: