From 7d150b050c4858562db700061a9872ac45621b40 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Mon, 2 Sep 2024 20:16:24 +0200 Subject: [PATCH] renderer: make blinking more robust As it turns out you can just set an "always visible" flag for the cursor when a user starts typing, and unset it when the next blink occurs. Note to self: maybe consider spending less time coding at 1am. --- src/renderer/Thread.zig | 132 ++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 72 deletions(-) diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index a460b4594..a3ce8fc4c 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -16,7 +16,7 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.renderer_thread); const DRAW_INTERVAL = 8; // 120 FPS -const CURSOR_BLINK_INTERVAL = 600; +const BLINK_INTERVAL = 600; /// The type used for sending messages to the IO thread. For now this is /// hardcoded with a capacity. We can make this a comptime parameter in @@ -57,15 +57,15 @@ draw_now: xev.Async, draw_now_c: xev.Completion = .{}, /// The timer used for text blinking. This timer will always run, uninterrupted by -/// user text input, unlike the cursor blink timer which gets reset during typing. +/// user text input. blink_h: xev.Timer, blink_c: xev.Completion = .{}, blink_c_cancel: xev.Completion = .{}, -/// The timer used for cursor blinking. This timer will get reset on user text input, -/// ensuring the cursor will always remain visible while typing. -/// When the user stops typing, the timer will wait till the main blink timer fires, -/// ensuring that the cursor remains in sync with text blinking. +/// The timer used to reset cursor blinking. When the cursor is set to always visible +/// (for example, while typing), this timer introduces a delay that synchronizes with +/// the main blink timer, to unset the always visible flag and allow the cursor to blink +/// in sync with the rest of the screen again. cursor_h: xev.Timer, cursor_c: xev.Completion = .{}, cursor_c_cancel: xev.Completion = .{}, @@ -95,10 +95,9 @@ flags: packed struct { /// thread automatically. blink_visible: bool = false, - /// This is true when a blinking cursor should be visible and false - /// when it should not be visible. This is toggled on a timer by the - /// thread automatically. - cursor_blink_visible: bool = false, + /// Whether the cursor should be forced into staying visible regardless + /// of blinking. Used when the user is typing. + cursor_always_visible: bool = false, /// This is true when the inspector is active. has_inspector: bool = false, @@ -242,19 +241,11 @@ fn threadMain_(self: *Thread) !void { self.blink_h.run( &self.loop, &self.blink_c, - CURSOR_BLINK_INTERVAL, + BLINK_INTERVAL, Thread, self, blinkTimerCallback, ); - self.cursor_h.run( - &self.loop, - &self.cursor_c, - CURSOR_BLINK_INTERVAL, - Thread, - self, - cursorTimerCallback, - ); // Start the draw timer self.startDrawTimer(); @@ -331,52 +322,51 @@ fn drainMailbox(self: *Thread) !void { self.stopDrawTimer(); } - // If we're not focused, then we stop the cursor blink - if (self.cursor_c.state() == .active and - self.cursor_c_cancel.state() == .dead) + // If we're not focused, then we stop the blink + if (self.blink_c.state() == .active and + self.blink_c_cancel.state() == .dead) { - self.cursor_h.cancel( + self.blink_h.cancel( &self.loop, - &self.cursor_c, - &self.cursor_c_cancel, + &self.blink_c, + &self.blink_c_cancel, void, null, - cursorCancelCallback, + blinkCancelCallback, ); } } else { // Start the draw timer self.startDrawTimer(); - // If we're focused, we immediately show the cursor again - // and then restart the timer. - if (self.cursor_c.state() != .active) { - self.flags.cursor_blink_visible = true; - self.cursor_h.run( + // If we're focused, we immediately make blinking cells visible + // again and then restart the timer. + if (self.blink_c.state() != .active) { + self.flags.blink_visible = true; + self.blink_h.run( &self.loop, - &self.cursor_c, - CURSOR_BLINK_INTERVAL, + &self.blink_c, + BLINK_INTERVAL, Thread, self, - cursorTimerCallback, + blinkTimerCallback, ); } } }, .reset_cursor_blink => { - self.flags.cursor_blink_visible = true; + self.flags.cursor_always_visible = true; - if (self.cursor_c.state() == .active) { - self.cursor_h.cancel( - &self.loop, - &self.cursor_c, - &self.cursor_c_cancel, - void, - null, - cursorCancelCallback, - ); - } + self.cursor_h.reset( + &self.loop, + &self.cursor_c, + &self.cursor_c_cancel, + BLINK_INTERVAL, + Thread, + self, + resetCursorBlinkCallback, + ); }, .font_grid => |grid| { @@ -558,7 +548,7 @@ fn renderCallback( t.surface, t.state, t.flags.blink_visible, - t.flags.cursor_blink_visible, + t.flags.cursor_always_visible or t.flags.blink_visible, ) catch |err| log.warn("error rendering err={}", .{err}); @@ -573,28 +563,6 @@ fn blinkTimerCallback( _: *xev.Loop, _: *xev.Completion, r: xev.Timer.RunError!void, -) xev.CallbackAction { - _ = r catch unreachable; - - const t: *Thread = self_ orelse { - // This shouldn't happen so we log it. - log.warn("render callback fired without data set", .{}); - return .disarm; - }; - - t.flags.blink_visible = !t.flags.blink_visible; - t.wakeup.notify() catch {}; - - t.cursor_h.run(&t.loop, &t.cursor_c, CURSOR_BLINK_INTERVAL, Thread, t, cursorTimerCallback); - t.blink_h.run(&t.loop, &t.blink_c, CURSOR_BLINK_INTERVAL, Thread, t, blinkTimerCallback); - - return .disarm; -} -fn cursorTimerCallback( - self_: ?*Thread, - _: *xev.Loop, - _: *xev.Completion, - r: xev.Timer.RunError!void, ) xev.CallbackAction { _ = r catch |err| switch (err) { // This is sent when our timer is canceled. That's fine. @@ -612,12 +580,14 @@ fn cursorTimerCallback( return .disarm; }; - t.flags.cursor_blink_visible = !t.flags.blink_visible; - // We intentionally don't call `t.wakeup.notify()` here to avoid a double redraw. + t.flags.blink_visible = !t.flags.blink_visible; + t.wakeup.notify() catch {}; + t.blink_h.run(&t.loop, &t.blink_c, BLINK_INTERVAL, Thread, t, blinkTimerCallback); + return .disarm; } -fn cursorCancelCallback( +fn blinkCancelCallback( _: ?*void, _: *xev.Loop, _: *xev.Completion, @@ -635,7 +605,7 @@ fn cursorCancelCallback( error.Canceled => {}, // success error.NotFound => {}, // completed before it could cancel else => { - log.warn("error in cursor cancel callback err={}", .{err}); + log.warn("error in blink cancel callback err={}", .{err}); unreachable; }, }; @@ -643,6 +613,24 @@ fn cursorCancelCallback( return .disarm; } +fn resetCursorBlinkCallback( + self_: ?*Thread, + _: *xev.Loop, + _: *xev.Completion, + r: xev.Timer.RunError!void, +) xev.CallbackAction { + _ = r catch unreachable; + + const t: *Thread = self_ orelse { + // This shouldn't happen so we log it. + log.warn("render callback fired without data set", .{}); + return .disarm; + }; + + t.flags.cursor_always_visible = false; + return .disarm; +} + // fn prepFrameCallback(h: *libuv.Prepare) void { // _ = h; //