tree, text_edit, render: hit-test only the visible part of scrolled widgets; keep the clip when a text field's interior misses the dirty region
Two partial-repaint bugs seen in Settings. A row scrolled half out of a `scroll` viewport kept its full layout rect for hit testing: only `paint_rect` is clamped to the viewport, and `find_widget_at` tested `rect` alone. The row whose bottom sliver showed just below the search field therefore owned, for the pointer, its whole height above the viewport, so hovering the field showed the hand cursor and highlighted that row, and a click opened it. `find_widget_at` now requires the point to lie in `paint_rect` as well. Inside a `scroll` or `viewport` that is the visible part; elsewhere `paint_rect` always encloses `rect`, so nothing else changes. `rect` is left intact because sliders, focus scrolling, the IME rect and tooltips need the full geometry. Two tests cover a clipped row and an unclipped button. The search field's placeholder darkened as the pointer moved over the list, until a full repaint restored it. `TextEdit::draw` composes its interior clip as the intersection of the parent clip with the inner rect; during a hover repaint of a row the intersection is empty, and `set_clip_rects` with an empty list means "no clip", so the placeholder was painted unclipped over the previous pixels on every partial frame. With `text_secondary` being `text_primary` at 0.6 alpha, two or three passes read as bold. `interior_clip` now returns a zero-area rect for an empty intersection, shared by the single-line and multiline paths, which the GLES backend already turns into a zero scissor; the software backend gains the matching rule, keeping an all-zero mask when the rect list is non-empty but has no area instead of dropping the clip. A test pins the three cases.
This commit is contained in:
@@ -13,6 +13,25 @@ use super::wrapping::compute_visual_lines;
|
||||
/// do not overlap (zero-area / negative-extent results count as
|
||||
/// "no overlap" so the caller can `filter_map` straight into a clip
|
||||
/// list).
|
||||
/// Clip for the field interior. An empty intersection with the parent clip
|
||||
/// must still clip everything: an empty rect list would lift the clip.
|
||||
pub( super ) fn interior_clip( outer: &[ Rect ], inner: Rect ) -> Vec<Rect>
|
||||
{
|
||||
if outer.is_empty()
|
||||
{
|
||||
return vec![ inner ];
|
||||
}
|
||||
let clip: Vec<Rect> = outer.iter()
|
||||
.filter_map( |o| rect_intersect( *o, inner ) )
|
||||
.collect();
|
||||
if clip.is_empty()
|
||||
{
|
||||
vec![ Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 } ]
|
||||
} else {
|
||||
clip
|
||||
}
|
||||
}
|
||||
|
||||
fn rect_intersect( a: Rect, b: Rect ) -> Option<Rect>
|
||||
{
|
||||
let x0 = a.x.max( b.x );
|
||||
@@ -118,15 +137,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
||||
width: ( rect.width - theme::PAD_H - toggle_reserve ).max( 0.0 ),
|
||||
height: rect.height,
|
||||
};
|
||||
let composed_clip: Vec<Rect> = if outer_clip.is_empty()
|
||||
{
|
||||
vec![ inner_rect ]
|
||||
} else {
|
||||
outer_clip.iter()
|
||||
.filter_map( |o| rect_intersect( *o, inner_rect ) )
|
||||
.collect()
|
||||
};
|
||||
canvas.set_clip_rects( &composed_clip );
|
||||
canvas.set_clip_rects( &interior_clip( &outer_clip, inner_rect ) );
|
||||
|
||||
// Selection highlight (single-line). Painted before the text
|
||||
// so the glyphs sit on top of the tinted band.
|
||||
@@ -300,15 +311,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
||||
width: ( rect.width - theme::PAD_H ).max( 0.0 ),
|
||||
height: ( rect.height - theme::PAD_V_MULTI ).max( 0.0 ),
|
||||
};
|
||||
let composed_clip: Vec<Rect> = if outer_clip.is_empty()
|
||||
{
|
||||
vec![ inner_rect ]
|
||||
} else {
|
||||
outer_clip.iter()
|
||||
.filter_map( |o| rect_intersect( *o, inner_rect ) )
|
||||
.collect()
|
||||
};
|
||||
canvas.set_clip_rects( &composed_clip );
|
||||
canvas.set_clip_rects( &interior_clip( &outer_clip, inner_rect ) );
|
||||
|
||||
// Selection highlight (multiline). Painted before the text so
|
||||
// the glyphs sit on top of the tinted band(s). Iterates
|
||||
|
||||
Reference in New Issue
Block a user