Skip to content

fix: skip full-screen clear in fullscreen mode to prevent flicker#190

Closed
djordjeglbvc wants to merge 1 commit into
ccbrown:mainfrom
djordjeglbvc:fix/fullscreen-clear-flicker
Closed

fix: skip full-screen clear in fullscreen mode to prevent flicker#190
djordjeglbvc wants to merge 1 commit into
ccbrown:mainfrom
djordjeglbvc:fix/fullscreen-clear-flicker

Conversation

@djordjeglbvc
Copy link
Copy Markdown
Contributor

When using element.fullscreen(), rapid state changes which fire TUI updates cause visible flicker at the bottom of the terminal. The flicker ranges from a few rows to more than half the screen, and always affects the bottom portion. The issue is apparent in applications with dense per-cell styling (background colors, multiple text styles per row) and complex web of UI components/elements.

Observed on Konsole 25.04.2 (Wayland). The terminal supports DEC mode 2026 (synchronized output), but the flicker occurs regardless — the volume of ANSI data between clear and rewrite appears to exceed what synchronized output can atomically batch.

Since write_canvas() overwrites every row and each row already emits CSI K (erase to end of line), the full-screen clear is redundant. Replace it with a simple cursor reposition to (0,0).

The non-fullscreen (inline) path is unchanged — it still needs the clear to handle variable-height output correctly.

What It Does

In fullscreen mode, replace the clear with a cursor::MoveTo(0, 0).

This is safe because (as already mentioned) write_canvas() overwrites every row and emits CSI K for each row, so additional clear is redundant.

Related Issues

(possibly related) Flickering issue

In fullscreen (alternate screen) mode, clear_canvas() used
ClearFromCursorDown which wipes the entire visible area before
write_canvas() rewrites it top-to-bottom.  When state changes are
frequent (e.g. rapid mouse-move events), the bottom rows spend a
visible moment cleared before being redrawn, producing flicker.

Since write_canvas() overwrites every row and each row already emits
CSI K (erase to end of line), the full-screen clear is redundant.
Replace it with a simple cursor reposition to (0,0).

The non-fullscreen (inline) path is unchanged — it still needs the
clear to handle variable-height output correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ccbrown
Copy link
Copy Markdown
Owner

ccbrown commented Apr 15, 2026

Thanks for the PR! This makes sense to me, but it is assuming that all fullscreen mode TUIs actually fill the entire height of the terminal. This isn't necessarily the case.

For example, if you modify the fullscreen example like so...

diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs
index 41d4424..88f7e06 100644
--- a/examples/fullscreen.rs
+++ b/examples/fullscreen.rs
@@ -35,7 +35,7 @@ fn Example(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
     element! {
         View(
             width,
-            height,
+            height: height - (time.get().timestamp() % 10) as u16,
             background_color: Color::DarkGrey,
             border_style: BorderStyle::Double,
             border_color: Color::Blue,

You'll see artifacts like these:

スクリーンショット 2026-04-15 午後4 52 44

I think the fix for that is pretty simple, but you might also want to take a look at #179, which optimizes things even further.

@djordjeglbvc
Copy link
Copy Markdown
Contributor Author

Thanks for the review! It passed over my head to test with lower height...

I saw #179, it looks very good, and it would make my change unnecessary, but I assumed it is a long term change. I did a quick test, and saw some artifacts, so I gave up. I will re-check. Thanks!

Thanks for the PR! This makes sense to me, but it is assuming that all fullscreen mode TUIs actually fill the entire height of the terminal. This isn't necessarily the case.

For example, if you modify the fullscreen example like so...

fuleinist added a commit to fuleinist/iocraft that referenced this pull request Apr 16, 2026
In fullscreen mode, write_canvas() already overwrites every row and
emits CSI K (erase to end of line) for each row. The full clear was
redundant and caused visible flicker on rapid state changes with dense
per-cell styling.

This matches the approach described in PR ccbrown#190 from djordjeglbvc.

Fixes: ccbrown#190
@djordjeglbvc
Copy link
Copy Markdown
Contributor Author

Ok, I think I found an issue which originally discouraged me from using feat: add row-level diff rendering, and have posted a suggestion for a fix there.
If a fix gets included there, I will abandon this PR, as the other one is much better.

@djordjeglbvc
Copy link
Copy Markdown
Contributor Author

Fixed by #179.

Closing as obsolete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants