Move application of content mask and z-index into Interactivity::paint

This allows the content mask to correctly apply to bounds used in event handlers,
which prevents content under opaque borders from being hovered in overflow hidden
containers.

Co-Authored-By: Antonio <antonio@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Nathan Sobo 2023-12-20 11:43:45 -07:00
parent fffb30ac6d
commit 15f16f08d9
4 changed files with 643 additions and 604 deletions

File diff suppressed because it is too large Load diff

View file

@ -200,54 +200,52 @@ impl Element for UniformList {
bounds.lower_right() - point(border.right + padding.right, border.bottom), bounds.lower_right() - point(border.right + padding.right, border.bottom),
); );
style.paint(bounds, cx, |cx| { if self.item_count > 0 {
if self.item_count > 0 { let content_height =
let content_height = item_height * self.item_count + padding.top + padding.bottom;
item_height * self.item_count + padding.top + padding.bottom; let min_scroll_offset = padded_bounds.size.height - content_height;
let min_scroll_offset = padded_bounds.size.height - content_height; let is_scrolled = scroll_offset.y != px(0.);
let is_scrolled = scroll_offset.y != px(0.);
if is_scrolled && scroll_offset.y < min_scroll_offset { if is_scrolled && scroll_offset.y < min_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_scroll_offset; shared_scroll_offset.borrow_mut().y = min_scroll_offset;
scroll_offset.y = min_scroll_offset; scroll_offset.y = min_scroll_offset;
} }
if let Some(scroll_handle) = self.scroll_handle.clone() { if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.borrow_mut().replace(ScrollHandleState { scroll_handle.0.borrow_mut().replace(ScrollHandleState {
item_height, item_height,
list_height: padded_bounds.size.height, list_height: padded_bounds.size.height,
scroll_offset: shared_scroll_offset, scroll_offset: shared_scroll_offset,
});
}
let first_visible_element_ix =
(-(scroll_offset.y + padding.top) / item_height).floor() as usize;
let last_visible_element_ix =
((-scroll_offset.y + padded_bounds.size.height) / item_height).ceil()
as usize;
let visible_range = first_visible_element_ix
..cmp::min(last_visible_element_ix, self.item_count);
let mut items = (self.render_items)(visible_range.clone(), cx);
cx.with_z_index(1, |cx| {
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (item, ix) in items.iter_mut().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(
px(0.),
item_height * ix + scroll_offset.y + padding.top,
);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, cx);
}
});
}); });
} }
});
let first_visible_element_ix =
(-(scroll_offset.y + padding.top) / item_height).floor() as usize;
let last_visible_element_ix = ((-scroll_offset.y + padded_bounds.size.height)
/ item_height)
.ceil() as usize;
let visible_range = first_visible_element_ix
..cmp::min(last_visible_element_ix, self.item_count);
let mut items = (self.render_items)(visible_range.clone(), cx);
cx.with_z_index(1, |cx| {
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (item, ix) in items.iter_mut().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(
px(0.),
item_height * ix + scroll_offset.y + padding.top,
);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, cx);
}
});
});
}
}, },
) )
} }

View file

@ -258,16 +258,30 @@ impl Style {
} }
} }
pub fn overflow_mask(&self, bounds: Bounds<Pixels>) -> Option<ContentMask<Pixels>> { pub fn overflow_mask(
&self,
bounds: Bounds<Pixels>,
rem_size: Pixels,
) -> Option<ContentMask<Pixels>> {
match self.overflow { match self.overflow {
Point { Point {
x: Overflow::Visible, x: Overflow::Visible,
y: Overflow::Visible, y: Overflow::Visible,
} => None, } => None,
_ => { _ => {
let current_mask = bounds; let mut min = bounds.origin;
let min = current_mask.origin; let mut max = bounds.lower_right();
let max = current_mask.lower_right();
if self
.border_color
.map_or(false, |color| !color.is_transparent())
{
min.x += self.border_widths.left.to_pixels(rem_size);
max.x -= self.border_widths.right.to_pixels(rem_size);
min.y += self.border_widths.top.to_pixels(rem_size);
max.y -= self.border_widths.bottom.to_pixels(rem_size);
}
let bounds = match ( let bounds = match (
self.overflow.x == Overflow::Visible, self.overflow.x == Overflow::Visible,
self.overflow.y == Overflow::Visible, self.overflow.y == Overflow::Visible,
@ -285,61 +299,62 @@ impl Style {
point(bounds.lower_right().x, max.y), point(bounds.lower_right().x, max.y),
), ),
// both hidden // both hidden
(false, false) => bounds, (false, false) => Bounds::from_corners(min, max),
}; };
Some(ContentMask { bounds }) Some(ContentMask { bounds })
} }
} }
} }
pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R // pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
where // where
C: BorrowAppContext, // C: BorrowAppContext,
F: FnOnce(&mut C) -> R, // F: FnOnce(&mut C) -> R,
{ // {
if self.text.is_some() { // if self.text.is_some() {
cx.with_text_style(Some(self.text.clone()), f) // cx.with_text_style(Some(self.text.clone()), f)
} else { // } else {
f(cx) // f(cx)
} // }
} // }
/// Apply overflow to content mask // /// Apply overflow to content mask
pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R // pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
where // where
C: BorrowWindow, // C: BorrowWindow,
F: FnOnce(&mut C) -> R, // F: FnOnce(&mut C) -> R,
{ // {
let current_mask = cx.content_mask(); // let current_mask = cx.content_mask();
let min = current_mask.bounds.origin; // let min = current_mask.bounds.origin;
let max = current_mask.bounds.lower_right(); // let max = current_mask.bounds.lower_right();
let mask_bounds = match ( // let mask_bounds = match (
self.overflow.x == Overflow::Visible, // self.overflow.x == Overflow::Visible,
self.overflow.y == Overflow::Visible, // self.overflow.y == Overflow::Visible,
) { // ) {
// x and y both visible // // x and y both visible
(true, true) => return f(cx), // (true, true) => return f(cx),
// x visible, y hidden // // x visible, y hidden
(true, false) => Bounds::from_corners( // (true, false) => Bounds::from_corners(
point(min.x, bounds.origin.y), // point(min.x, bounds.origin.y),
point(max.x, bounds.lower_right().y), // point(max.x, bounds.lower_right().y),
), // ),
// x hidden, y visible // // x hidden, y visible
(false, true) => Bounds::from_corners( // (false, true) => Bounds::from_corners(
point(bounds.origin.x, min.y), // point(bounds.origin.x, min.y),
point(bounds.lower_right().x, max.y), // point(bounds.lower_right().x, max.y),
), // ),
// both hidden // // both hidden
(false, false) => bounds, // (false, false) => bounds,
}; // };
let mask = ContentMask { // let mask = ContentMask {
bounds: mask_bounds, // bounds: mask_bounds,
}; // };
cx.with_content_mask(Some(mask), f) // cx.with_content_mask(Some(mask), f)
} // }
/// Paints the background of an element styled with this style. /// Paints the background of an element styled with this style.
pub fn paint( pub fn paint(
@ -369,14 +384,14 @@ impl Style {
}); });
let background_color = self.background.as_ref().and_then(Fill::color); let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() { if background_color.is_some() {
cx.with_z_index(1, |cx| { cx.with_z_index(1, |cx| {
cx.paint_quad(quad( cx.paint_quad(quad(
bounds, bounds,
self.corner_radii.to_pixels(bounds.size, rem_size), self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(), background_color.unwrap_or_default(),
self.border_widths.to_pixels(rem_size), Edges::default(),
self.border_color.unwrap_or_default(), Hsla::transparent_black(),
)); ));
}); });
} }
@ -385,6 +400,18 @@ impl Style {
continuation(cx); continuation(cx);
}); });
if self.is_border_visible() {
cx.with_z_index(3, |cx| {
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
Hsla::transparent_black(),
self.border_widths.to_pixels(rem_size),
self.border_color.unwrap_or_default(),
));
});
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if self.debug_below { if self.debug_below {
cx.remove_global::<DebugBelow>(); cx.remove_global::<DebugBelow>();

View file

@ -537,6 +537,7 @@ impl Render for Dock {
div() div()
.flex() .flex()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.overflow_hidden()
.map(|this| match self.position().axis() { .map(|this| match self.position().axis() {
Axis::Horizontal => this.w(px(size)).h_full().flex_row(), Axis::Horizontal => this.w(px(size)).h_full().flex_row(),
Axis::Vertical => this.h(px(size)).w_full().flex_col(), Axis::Vertical => this.h(px(size)).w_full().flex_col(),