summaryrefslogtreecommitdiff
path: root/src/Messages.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Messages.zig')
-rw-r--r--src/Messages.zig81
1 files changed, 75 insertions, 6 deletions
diff --git a/src/Messages.zig b/src/Messages.zig
index 97a2f939..7334de95 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -272,11 +272,35 @@ fn advanceLine(p: *const Pardes, life: *Pane.MessageLife) bool {
return false;
}
-/// Is any message line moving (and so owed ticks)?
-pub fn messagesAnimating(pane: *const Pane) bool {
- if (pane.msg_len > 0 and pane.msg_life.phase != .shown) return true;
- for (pane.msg_older[0..pane.msg_older_len]) |line| if (line.life.phase != .shown) return true;
- return false;
+/// Move every lingering message line `frames` along its linger at once:
+/// how `advance` jumps a wait instead of stepping it frame by frame.
+pub fn holdMessages(p: *Pardes, frames: u64) void {
+ const n: u16 = @intCast(@min(frames, std.math.maxInt(u16)));
+ for (p.panes) |slot| {
+ const pane = slot orelse continue;
+ if (pane.msg_len > 0 and pane.msg_life.phase == .lingering) pane.msg_life.frame +|= n;
+ for (pane.msg_older[0..pane.msg_older_len]) |*line| {
+ if (line.life.phase == .lingering) line.life.frame +|= n;
+ }
+ }
+}
+
+/// Frames until this pane's message lines next change: 1 while a line
+/// falls in or dissolves, the rest of its linger while one only waits, null
+/// when every line just shows.
+pub fn messageWake(p: *const Pardes, pane: *const Pane) ?u64 {
+ var soonest: ?u64 = null;
+ const linger = messageFrames(p.settings.message_linger_ms);
+ for (0..@as(usize, pane.msg_older_len) + 1) |i| {
+ const life = if (i < pane.msg_older_len) pane.msg_older[i].life else if (pane.msg_len > 0) pane.msg_life else continue;
+ const frames: u64 = switch (life.phase) {
+ .shown => continue,
+ .entering, .leaving => 1,
+ .lingering => @max(1, linger -| life.frame),
+ };
+ soonest = if (soonest) |n| @min(n, frames) else frames;
+ }
+ return soonest;
}
/// How present a message is (1 = fully) and how far above its row it
@@ -286,7 +310,7 @@ pub const MessageMotion = struct { alpha: f32 = 1, slide: f32 = 0 };
/// Animation frames a message timing lasts; at least one.
pub fn messageFrames(ms: u16) u16 {
- return @intCast(@max(1, (@as(u32, ms) + animation.Animation.frame_ms - 1) / animation.Animation.frame_ms));
+ return @intCast(@max(1, (@as(u32, ms) + animation.frame_ms - 1) / animation.frame_ms));
}
pub fn messageMotion(p: *const Pardes, life: Pane.MessageLife) MessageMotion {
@@ -792,3 +816,48 @@ test "notices stack over the body's top rows without moving them" {
for (p.surface.tagLayers()) |*layer|
try std.testing.expect(layer.cols == 0 or layer.kind != .notice);
}
+
+test "an idle core wants no wake and a lingering message sleeps to its linger's end" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const frame = animation.frame_ns;
+ p.advance(1_000_000);
+ try std.testing.expectEqual(@as(?u64, null), p.nextWake());
+ const pane = p.panes[p.active].?;
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Msg hello"));
+ // Falling in: a frame from now.
+ try std.testing.expectEqual(@as(?u64, p.stepped_ns + frame), p.nextWake());
+ const fall = messageFrames(p.settings.message_fall_ms);
+ p.advance(p.stepped_ns + fall * frame);
+ try std.testing.expectEqual(Pane.MessagePhase.shown, pane.msg_life.phase);
+ try std.testing.expectEqual(@as(?u64, null), p.nextWake());
+ // A key starts the 800 ms linger: the core sleeps to its end, not a
+ // frame at a time, and the frames it jumps draw nothing.
+ p.update(.{ .key = .{ .cp = 'x' } });
+ try std.testing.expectEqual(Pane.MessagePhase.lingering, pane.msg_life.phase);
+ const linger = messageFrames(p.settings.message_linger_ms);
+ const due = p.stepped_ns + linger * frame;
+ try std.testing.expectEqual(@as(?u64, due), p.nextWake());
+ p.needs_frame = false;
+ p.advance(due - frame);
+ try std.testing.expectEqual(@as(?u64, due), p.nextWake());
+ try std.testing.expectEqual(Pane.MessagePhase.lingering, pane.msg_life.phase);
+ p.advance(due);
+ try std.testing.expectEqual(Pane.MessagePhase.leaving, pane.msg_life.phase);
+ try std.testing.expectEqual(@as(?u64, due + frame), p.nextWake());
+ p.advance(due + 10 * std.time.ns_per_s);
+ try std.testing.expectEqual(@as(usize, 0), pane.msg_len);
+ try std.testing.expectEqual(@as(?u64, null), p.nextWake());
+
+ // A long linger ends when it says, jumped in one step, not stretched by
+ // the cap on catching up with moving frames.
+ try std.testing.expect(p.executeBuiltinLine(p.active, "MessageLinger 5000"));
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Msg again"));
+ p.advance(p.stepped_ns + fall * frame);
+ p.update(.{ .key = .{ .cp = 'x' } });
+ const long_due = p.stepped_ns + messageFrames(5000) * frame;
+ try std.testing.expectEqual(@as(?u64, long_due), p.nextWake());
+ p.advance(long_due);
+ try std.testing.expectEqual(Pane.MessagePhase.leaving, pane.msg_life.phase);
+}