summaryrefslogtreecommitdiff
path: root/src/Messages.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Messages.zig')
-rw-r--r--src/Messages.zig88
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);
}