From 7c84ebe66494c3b05dcd23f198b5c5f4189cb708 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 13:14:00 -0300 Subject: A window whose focus moves to a PDF or picture while the cursor glides goes idle: the glide stops instead of holding frames for ever glideCursor returned early when no cursor was drawn, so a glide under way (right-click a PDF from a tag: the cursor sets off for the new pane) was never stepped again and cursor_glide.moving() kept continuous() true: the GUI redrew at full rate, every frame, until focus came back to a cursor. With no cursor drawn the glide is now dropped; the next cursor lands. A pixel shell's only: the terminal has no cursor glide. Co-Authored-By: Claude Opus 5.5 --- src/draw.zig | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) (limited to 'src/draw.zig') diff --git a/src/draw.zig b/src/draw.zig index 0ea0b35e..9fcbb15f 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -225,7 +225,15 @@ fn glideCursor(p: *Pardes, s: *Surface) void { s.chrome.cursor_alpha = 1; s.chrome.cursor_idle_ms = std.math.maxInt(u32); if (!builtins.capabilities.lift) return; - const box = focusedCursorBox(p, s) orelse return; + const box = focusedCursorBox(p, s) orelse { + // No cursor drawn (a PDF or picture took focus, a program hid its + // own): a glide under way has nowhere to land, and its springs, + // never stepped again, held continuous() and the frames on for + // ever. It stops; the next cursor lands where it is. + p.cursor_glide = .{}; + p.cursor_crossing = false; + return; + }; const motion = animation.Motion.of(p.settings.motion); var snap = if (s.cursor) |cursor| cursor.bar else false; var frame: PaneFrame = .{}; @@ -2341,6 +2349,35 @@ test "focus moved to another column glides from where the cursor was drawn" { try std.testing.expect(!p.cursor_glide.moving() and !p.cursor_crossing); } +test "focus moving to a pane with no cursor while the cursor glides lets the session go idle" { + if (!builtins.capabilities.lift) return error.SkipZigTest; + const p = try Pardes.init(std.testing.allocator, .{ .cols = 120, .rows = 20 }); + defer p.deinit(); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + p.settings.motion = .smooth; + try twoColumns(p, &arena); + p.active = 0; + p.advance(3 * std.time.ns_per_s); + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + p.active = 1; + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + try std.testing.expect(p.cursor_glide.moving()); + // Mid-glide, the pane focused draws no cursor (as a PDF pane does). + p.panes[1].?.body.mode = .tty; + p.update(.{ .output = .{ .pane = 1, .bytes = "\x1b[?25l" } }); + p.advance(3 * std.time.ns_per_s + 20 * std.time.ns_per_ms); + _ = arena.reset(.retain_capacity); + const hidden = try p.render(arena.allocator()); + try std.testing.expect(hidden.cursor == null); + p.advance(5 * std.time.ns_per_s); + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + try std.testing.expect(!p.cursor_glide.moving() and !p.continuous()); +} + test "a pane focus reaches brightens on the same spring the one it leaves dims on, and Motion off snaps" { if (!builtins.capabilities.lift) return error.SkipZigTest; const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); -- cgit v1.3