input: Fix Input may infinite repaint loop in occurs at fractional DPI scale factors (e.g. 125%, 150%).#2216
Open
lyl2dora wants to merge 3 commits into
Open
Conversation
…input_bounds Taffy layout rounding can produce sub-pixel floating-point differences between frames (e.g., 34.5px → 34.33px at fractional scale factors). When update_scroll_offset and set_input_bounds unconditionally call cx.notify(), these tiny differences trigger an infinite repaint loop: paint → set offset (unchanged at physical pixels) → notify → repaint → … Fix: snap values to physical-pixel integers (round(value × scale_factor)) before comparing. Only call cx.notify() when the physical pixel value actually changes. This breaks the perpetual loop while preserving correct repaints for real scroll/resize changes.
paint() is a read-only observation endpoint in GPUI's render cycle. Calling cx.notify() unconditionally at the end of paint creates an infinite repaint loop: paint → notify → repaint → paint → … The previous commit added conditional notification to set_input_bounds and update_scroll_offset (only when values actually change at physical-pixel granularity), so the unconditional cx.notify() at the end of paint is no longer needed and can be safely removed.
At fractional DPI scale factors (e.g. 1.5×), taffy's edge-rounding can shrink the reported bounds.height below line_height (e.g. 34.5 physical pixels → 34 physical pixels). When the scroll margin (edge_height or top_bottom_margin) exceeds the viewport, every cursor movement falsely triggers a scroll adjustment, causing visible jitter. Fix: clamp both margins to bounds.size.height so they can never exceed the actual viewport, eliminating the false scroll triggers.
Member
|
Would you please upload some screenshot for this infinite repaint loop? |
Author
|
Reproduction (Windows 11, 150% DPI scaling) Modified examples/input to simulate inline rename — a common pattern for file trees / tables: Input::new(&self.input_state) before.mp4Each click shifts the text down by ~0.33px (0.5 physical pixel at 1.5×). The root cause:
This affects any Input used for inline editing (zero padding, tight height) at fractional DPI scales (125%, 150%). |
huacnlee
reviewed
Apr 7, 2026
| cx.notify(); | ||
|
|
||
| let snap = |v: Pixels| (v.as_f32() * scale_factor).round() as i32; | ||
| if snap(old.x) != snap(offset.x) || snap(old.y) != snap(offset.y) { |
Member
There was a problem hiding this comment.
This conversion process is strange, and I don't think it's a solution to the problem.
huacnlee
requested changes
Apr 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix an infinite repaint loop (perpetual
cx.notify()) in the Input componentthat occurs at fractional DPI scale factors (e.g. 125%, 150%).
Root cause: taffy's layout rounding produces sub-pixel floating-point
differences between frames.
paint()unconditionally calledcx.notify()atthe end, and
update_scroll_offset/set_input_boundsalso calledcx.notify()without checking whether the value actually changed — creating apaint → notify → repaint → paint → …infinite loop.Fix (3 commits):
update_scroll_offsetandset_input_bounds— onlycx.notify()when the rounded physical pixelvalue actually changes.
cx.notify()at the end ofpaint(), since thetwo functions above now notify conditionally.
edge_heightandtop_bottom_marginto viewport height, preventingfalse scroll triggers when taffy rounds
bounds.heightbelowline_height.