diff options
Diffstat (limited to 'src/Messages.zig')
| -rw-r--r-- | src/Messages.zig | 81 |
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); +} |
