diff options
Diffstat (limited to 'src/Messages.zig')
| -rw-r--r-- | src/Messages.zig | 88 |
1 files changed, 79 insertions, 9 deletions
diff --git a/src/Messages.zig b/src/Messages.zig index 66261eb4..ea62041f 100644 --- a/src/Messages.zig +++ b/src/Messages.zig @@ -5,6 +5,7 @@ const pardes = @import("pardes.zig"); const memory = @import("memory.zig"); const std = @import("std"); const layout = @import("layout.zig"); +const animation = @import("animation.zig"); const ctlfs = @import("ninep/tree.zig"); const panes = @import("panes.zig"); const CellStyle = @import("surface.zig").CellStyle; @@ -302,11 +303,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 @@ -316,7 +341,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) + layout.Animation.frame_ms - 1) / layout.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 { @@ -811,7 +836,7 @@ test "a builtin announces itself on the message row, and Verbose silences it" { _ = try p.render(frame.allocator()); const r = p.rects[p.active]; const band = for (p.surface.tagLayers()) |*layer| { - if (layer.cols != 0 and layer.kind == .notice) break layer; + if (layer.rows != 0 and layer.kind == .notice) break layer; } else return error.NoNoticeBand; try std.testing.expectEqual(@as(u16, 1), band.viewport.h); try std.testing.expectEqual(if (p.settings.tag_bottom) r.y else r.y + BOX_H, band.viewport.y); @@ -858,7 +883,7 @@ test "notices stack over the body's top rows without moving them" { var seen: usize = 0; for (p.surface.tagLayers()) |*layer| { - if (layer.cols == 0 or layer.kind != .notice) continue; + if (layer.rows == 0 or layer.kind != .notice) continue; try std.testing.expectEqual(@as(u16, 1), layer.viewport.h); // Sized to its own message, right up against the pane's right edge -- // not the width of the pane. @@ -878,5 +903,50 @@ test "notices stack over the body's top rows without moving them" { _ = frame.reset(.retain_capacity); _ = try p.render(frame.allocator()); for (p.surface.tagLayers()) |*layer| - try std.testing.expect(layer.cols == 0 or layer.kind != .notice); + try std.testing.expect(layer.rows == 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); } |
