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.
This commit is contained in:
Leah Amelia Chen
2024-09-02 20:16:24 +02:00
parent 7f22f15e5b
commit 7d150b050c

View File

@ -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;
//