Skip to content

Commit 27da33f

Browse files
committed
fix(tui): honor forced repaints
Invalidate the fullscreen and inline renderer frame timers so forced repaint requests cannot be dropped by the frame-rate limiter. Add coverage for both renderer paths at a low FPS. Fixes #7
1 parent 5ff2a09 commit 27da33f

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

pkgs/artisanal/lib/src/tui/renderer.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,9 @@ class FullScreenTuiRenderer implements TuiRenderer {
415415
void invalidate() {
416416
_lastView = null;
417417
_lastFrame = null;
418+
// Invalidation is used by forced repaints. The next render must not be
419+
// discarded just because the regular frame-rate interval has not elapsed.
420+
_frameStopwatch.stop();
418421
}
419422

420423
@override
@@ -613,6 +616,9 @@ class InlineTuiRenderer implements TuiRenderer {
613616
@override
614617
void invalidate() {
615618
_lastLineCount = 0;
619+
// Invalidation is used by forced repaints. The next render must not be
620+
// discarded just because the regular frame-rate interval has not elapsed.
621+
_frameStopwatch.stop();
616622
}
617623

618624
@override

pkgs/artisanal/test/tui/fullscreen_renderer_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,26 @@ void main() {
4949
expect(terminal.output, isNot(contains('alpha')));
5050
});
5151

52+
test('invalidate bypasses the frame-rate limiter', () {
53+
final terminal = StringTerminal();
54+
final renderer = FullScreenTuiRenderer(
55+
terminal: terminal,
56+
options: const TuiRendererOptions(
57+
fps: 1,
58+
altScreen: true,
59+
hideCursor: false,
60+
),
61+
);
62+
63+
renderer.render('same frame');
64+
terminal.clear();
65+
66+
renderer.invalidate();
67+
renderer.render('same frame');
68+
69+
expect(terminal.output, contains('same frame'));
70+
});
71+
5272
test('re-renders lines whose inherited ANSI state changed', () {
5373
final terminal = StringTerminal();
5474
final renderer = buildRenderer(terminal);
@@ -182,4 +202,24 @@ void main() {
182202
);
183203
});
184204
});
205+
206+
test('InlineTuiRenderer invalidate bypasses the frame-rate limiter', () {
207+
final terminal = StringTerminal();
208+
final renderer = InlineTuiRenderer(
209+
terminal: terminal,
210+
options: const TuiRendererOptions(
211+
fps: 1,
212+
altScreen: false,
213+
hideCursor: false,
214+
),
215+
);
216+
217+
renderer.render('same frame');
218+
terminal.clear();
219+
220+
renderer.invalidate();
221+
renderer.render('same frame');
222+
223+
expect(terminal.output, contains('same frame'));
224+
});
185225
}

0 commit comments

Comments
 (0)