fix(ui): bind redraw-tick to full-bleed backdrop, not 1px sentinel#8
Conversation
The v1.0.2 force_repaint mechanism bumps redraw-tick on a 1×1 px invisible Rectangle. Slint's incremental damage tracker only marks elements whose properties actually changed as dirty, so each bump invalidated a 1px tile and the software renderer re-blitted the rest of the (stale) framebuffer. After long idle this surfaced as the blank-screen-with-a-few-elements report — most of the window kept showing pixels from before, while the two or three elements that happened to re-evaluate painted correctly. Move the redraw-tick binding onto the existing full-bleed backdrop Rectangle (and delete the now-redundant 1px sentinel). Each Rust-side bump now dirties the entire window, forcing a full re-rasterization. The opacity range stays in [0.999, 1.0] and blends bg-on-bg with the Window background, so there is no visible flicker.
Captures the escalation path if the redraw-tick + full-bleed backdrop fix from 8d82451 is insufficient: Win32 RedrawWindow belt, then switch to renderer-femtovg, then window recreation as last resort. Also distinguishes render-staleness symptoms from data-refresh symptoms so future sessions don't apply renderer fixes to a config-refresh bug.
|
@codex review Generated by Claude Code |
|
/gemini review Generated by Claude Code |
There was a problem hiding this comment.
Code Review
This pull request introduces a troubleshooting guide for blank-screen and stale-framebuffer issues and refines the existing redraw mechanism. The previous 1x1 pixel sentinel has been replaced with a full-bleed backdrop whose opacity is bound to the redraw-tick property, ensuring the entire window is re-rasterized by the software renderer. Feedback was provided to correct the documentation's description of matching on platform-specific window handles using the raw-window-handle crate.
| In `src/main.rs::force_repaint`, after `request_redraw()`, get the HWND from | ||
| the Slint window via `raw_window_handle_06` and call `invalidate_hwnd`. The | ||
| Slint API for this is `w.window().window_handle().window_handle()` returning | ||
| `Result<WindowHandle, _>`; match on `WindowHandle::Win32(h)` and pass |
There was a problem hiding this comment.
In raw-window-handle 0.6 (the version used by Slint 1.11), WindowHandle is a struct that wraps the RawWindowHandle enum. To match on the platform-specific variant, you should call .as_raw() on the handle returned by Slint's API.
| `Result<WindowHandle, _>`; match on `WindowHandle::Win32(h)` and pass | |
| Result<WindowHandle, _>; match on handle.as_raw() { RawWindowHandle::Win32(h) => pass |
Per gemini-code-assist review on PR #8: WindowHandle in raw-window-handle 0.6 wraps the RawWindowHandle enum; the variants live on the inner enum reached via .as_raw(), not on WindowHandle itself.
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive fallback playbook for addressing blank-screen or stale-framebuffer issues and refines the existing redraw mechanism. The UI logic was updated to replace a 1x1 pixel sentinel with a full-bleed backdrop whose opacity is bound to the refresh tick, ensuring the entire window is invalidated in Slint's damage tracker. Review feedback correctly identified several technical inaccuracies in the documentation's code snippets, including incorrect Win32 API arguments and invalid Rust pointer casts.
| pub fn invalidate_hwnd(hwnd: windows::Win32::Foundation::HWND) { | ||
| use windows::Win32::Graphics::Gdi::{RedrawWindow, RDW_ALLCHILDREN, RDW_ERASE, RDW_INVALIDATE}; | ||
| unsafe { | ||
| let _ = RedrawWindow(Some(hwnd), None, None, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); | ||
| } | ||
| } |
There was a problem hiding this comment.
The RedrawWindow call in this snippet contains several errors that will prevent it from compiling with the windows crate (version 0.58 as used in this project):
- The first argument
hwndshould be passed directly, not wrapped inSome().RedrawWindowexpects a type that implementsInto<HWND>. - The third argument
hrgnupdateis of typeHRGN, which does not implementFrom<None>. To specify no update region, you should passHRGN::default()orHRGN(0). HRGNneeds to be added to theusestatement for the snippet to be self-contained.
| pub fn invalidate_hwnd(hwnd: windows::Win32::Foundation::HWND) { | |
| use windows::Win32::Graphics::Gdi::{RedrawWindow, RDW_ALLCHILDREN, RDW_ERASE, RDW_INVALIDATE}; | |
| unsafe { | |
| let _ = RedrawWindow(Some(hwnd), None, None, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); | |
| } | |
| } | |
| pub fn invalidate_hwnd(hwnd: windows::Win32::Foundation::HWND) { | |
| use windows::Win32::Graphics::Gdi::{RedrawWindow, HRGN, RDW_ALLCHILDREN, RDW_ERASE, RDW_INVALIDATE}; | |
| unsafe { | |
| let _ = RedrawWindow(hwnd, None, HRGN::default(), RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); | |
| } | |
| } |
| the Slint window via `raw-window-handle` 0.6 and call `invalidate_hwnd`. The | ||
| Slint API for this is `w.window().window_handle()` returning a | ||
| `Result<WindowHandle, _>`. `WindowHandle` is a wrapper around the | ||
| `RawWindowHandle` enum, so match on `handle.as_raw()` and pull out |
There was a problem hiding this comment.
In Rust, casting an integer type (isize from h.hwnd.get()) directly to a pointer type via as _ is not permitted. You must explicitly cast to a pointer type first (e.g., as *mut _) before it can be used in the HWND constructor.
| `RawWindowHandle` enum, so match on `handle.as_raw()` and pull out | |
| HWND(h.hwnd.get() as *mut _). |
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Per gemini-code-assist review on PR #8: - RedrawWindow takes hwnd directly (Param<HWND>), not Some(hwnd). - The hrgnupdate parameter is HRGN (Param<HRGN>), not Option<_>; pass HRGN::default() and add HRGN to the imports. - isize -> *mut needs an explicit pointer type (as *mut _, not as _), so the HWND construction example uses h.hwnd.get() as *mut _. Doc-only — no behavior change.
|
@codex review Generated by Claude Code |
|
/gemini review Generated by Claude Code |
There was a problem hiding this comment.
Code Review
This pull request introduces a fallback playbook for addressing blank-screen and stale-framebuffer issues and enhances the existing redraw mechanism. The previous 1x1 pixel sentinel in ui/main.slint has been replaced with a full-bleed backdrop whose opacity is bound to a refresh tick, ensuring the entire window is marked dirty in Slint's damage tracker to prevent partial painting. Documentation and constants in src/main.rs were also updated to reflect these changes. A review comment correctly identified an inaccuracy in the documentation regarding the Slint API for obtaining raw window handles and provided a code suggestion for the correct implementation.
| Slint API for this is `w.window().window_handle()` returning a | ||
| `Result<WindowHandle, _>`. `WindowHandle` is a wrapper around the | ||
| `RawWindowHandle` enum, so match on `handle.as_raw()` and pull out | ||
| `RawWindowHandle::Win32(h)`, then pass `HWND(h.hwnd.get() as *mut _)`. |
There was a problem hiding this comment.
The description of the Slint API for obtaining the raw window handle is slightly inaccurate for Slint 1.11. w.window().window_handle() returns a &slint::WindowHandle, which then requires calling .window_handle() (from the HasWindowHandle trait) to get a Result<raw_window_handle::WindowHandle, _>. The as_raw() method is then called on the inner WindowHandle struct.
| Slint API for this is `w.window().window_handle()` returning a | |
| `Result<WindowHandle, _>`. `WindowHandle` is a wrapper around the | |
| `RawWindowHandle` enum, so match on `handle.as_raw()` and pull out | |
| `RawWindowHandle::Win32(h)`, then pass `HWND(h.hwnd.get() as *mut _)`. | |
| Slint API for this is w.window().window_handle().window_handle() returning a | |
| Result<raw_window_handle::WindowHandle, _>. That WindowHandle struct has an | |
| as_raw() method returning the RawWindowHandle enum, so match on that and pull out | |
| RawWindowHandle::Win32(h), then pass HWND(h.hwnd.get() as *mut _). |
Per gemini-code-assist follow-up review on PR #8: the previous round dropped one of the two .window_handle() calls. Slint 1.11 requires both — w.window().window_handle() returns &slint::WindowHandle, and the second .window_handle() (from the HasWindowHandle trait) returns the Result<raw_window_handle::WindowHandle, _> we then .as_raw() to get the RawWindowHandle enum. Doc-only — no behavior change.
|
@codex review Generated by Claude Code |
|
/gemini review Generated by Claude Code |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive fallback playbook for addressing blank-screen or stale-framebuffer issues and refines the existing UI refresh mechanism. The code changes replace a 1x1 pixel sentinel with a full-bleed backdrop in ui/main.slint, ensuring that periodic property updates force a full-window repaint in the Slint software renderer. A review comment correctly points out that the new backdrop rectangle lacks explicit dimensions, which could cause it to default to a zero size and fail to trigger the intended damage tracking; adding explicit width and height constraints is recommended.
| Rectangle { | ||
| background: FadeTheme.bg; | ||
| opacity: 1.0 - root.redraw-tick * 0.000001; | ||
| } |
There was a problem hiding this comment.
The Rectangle intended to serve as a full-bleed backdrop likely has a size of 0x0 because it is a direct child of the Window (via SettingsWindow) and is not placed within a layout, nor does it have explicit dimensions. In Slint, elements not in a layout default to their preferred size, which for an empty Rectangle is zero. To ensure this element covers the entire window and correctly triggers a full-window repaint in the damage tracker as intended, you should explicitly set its width and height to 100%.
Rectangle {
width: 100%;
height: 100%;
background: FadeTheme.bg;
opacity: 1.0 - root.redraw-tick * 0.000001;
}
Per gemini-code-assist (high-priority) review on PR #8: a bare Rectangle as a direct child of Window uses its preferred size, which is 0x0 for an empty Rectangle. The opacity-bound backdrop introduced in 8d82451 was therefore not actually full-bleed and its damage region collapsed to nothing — defeating the very fix it was meant to deliver. The previous full-bleed-by-comment Rectangle worked only because the Window itself sets background: FadeTheme.bg and was painting over the same pixels for free; that masked the size bug as long as nothing depended on the Rectangle's actual extent. Set width/height: 100% so the damage tracker dirties the whole window when redraw-tick mutates.
|
@codex review Generated by Claude Code |
|
/gemini review Generated by Claude Code |
There was a problem hiding this comment.
Code Review
This pull request introduces a troubleshooting playbook for blank-screen and stale-framebuffer issues and improves the UI's redraw mechanism. The previous 1x1 pixel sentinel has been replaced with a full-bleed backdrop whose opacity is bound to a refresh tick, ensuring the Slint software renderer re-rasterizes the entire window to prevent stale pixels. Documentation and code comments have been updated to reflect these changes. I have no feedback to provide.
|
Codex Review: Didn't find any major issues. Keep them coming! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
force_repaintmechanism bumpedredraw-tickon a 1×1 px invisible sentinelRectangle. Slint's incremental damage tracker only marks elements whose properties actually changed as dirty, so each bump invalidated a 1px tile while the rest of the framebuffer kept showing stale pixels.request_redraw()alone is not enough to dirty Slint's scene; only a property mutation is.redraw-tickbinding onto the existing full-bleed backdropRectangle(and delete the now-redundant 1px sentinel). Each Rust-side bump now dirties the entire window, forcing a full re-rasterization. Opacity stays in[0.999, 1.0]and alpha-blendsFadeTheme.bgontoFadeTheme.bg(the Window background) — no visible flicker.docs/blank-screen-fallback.mddescribing the escalation order (Win32RedrawWindowbelt → switch torenderer-femtovg→ window recreation) if this fix turns out to still be insufficient under longer idle, so future sessions don't re-derive it from scratch.Why not Win32
InvalidateRectinsteadInvalidateRect/RedrawWindowis upstream of Slint's renderer — it convinces Win32 to send a freshWM_PAINT, but Slint then consults its own internal damage region, sees nothing changed, and re-blits the same stale framebuffer. The bug lives in Slint's damage tracker, so the fix has to mutate a tracked property on a full-window element. Win32 invalidation is documented in the fallback doc as Step 1 of the escalation if this turns out to be insufficient under longer idle.Test plan
cargo checkpasses (Linux)cargo testpasses (62/62)https://claude.ai/code/session_01Us9bxTiUworKoSoLs3rLLu
Generated by Claude Code