Improve interaction between clicking and dragging (#3737)

Once a drag starts, we won't fire click listeners or style any elements
as active.

- Don't fire click listeners or show active state once a drag is in
progress
- Don't show hover style when a drag is in progress
- Draw borders above content
- If borders are opaque, apply them to the content mask. This prevents
hovers from firing on content underneath the border, which was creating
issues where the drag handle was inside the border, so we'd flicker the
hover when the mouse moved out of the drag handle and into the 1px
border on the left dock.
- Add a `block_mouse` helper which causes transparent elements to paint
an "opaque" layer to prevent mouse events from falling through. We use
this for the drag handle as well to disable hover, click, etc on items
in the panel.

Release Notes:

- N/A
This commit is contained in:
Nathan Sobo
2023-12-20 19:31:06 -07:00
committed by GitHub
4 changed files with 689 additions and 614 deletions
File diff suppressed because it is too large Load Diff
+42 -44
View File
@@ -209,54 +209,52 @@ impl Element for UniformList {
bounds.lower_right() - point(border.right + padding.right, border.bottom),
);
style.paint(bounds, cx, |cx| {
if self.item_count > 0 {
let content_height =
item_height * self.item_count + padding.top + padding.bottom;
let min_scroll_offset = padded_bounds.size.height - content_height;
let is_scrolled = scroll_offset.y != px(0.);
if self.item_count > 0 {
let content_height =
item_height * self.item_count + padding.top + padding.bottom;
let min_scroll_offset = padded_bounds.size.height - content_height;
let is_scrolled = scroll_offset.y != px(0.);
if is_scrolled && scroll_offset.y < min_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_scroll_offset;
scroll_offset.y = min_scroll_offset;
}
if is_scrolled && scroll_offset.y < min_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_scroll_offset;
scroll_offset.y = min_scroll_offset;
}
if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.borrow_mut().replace(ScrollHandleState {
item_height,
list_height: padded_bounds.size.height,
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);
}
});
if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.borrow_mut().replace(ScrollHandleState {
item_height,
list_height: padded_bounds.size.height,
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);
}
});
});
}
},
)
}
+79 -52
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 {
Point {
x: Overflow::Visible,
y: Overflow::Visible,
} => None,
_ => {
let current_mask = bounds;
let min = current_mask.origin;
let max = current_mask.lower_right();
let mut min = bounds.origin;
let mut max = bounds.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 (
self.overflow.x == Overflow::Visible,
self.overflow.y == Overflow::Visible,
@@ -285,61 +299,62 @@ impl Style {
point(bounds.lower_right().x, max.y),
),
// both hidden
(false, false) => bounds,
(false, false) => Bounds::from_corners(min, max),
};
Some(ContentMask { bounds })
}
}
}
pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
where
C: BorrowAppContext,
F: FnOnce(&mut C) -> R,
{
if self.text.is_some() {
cx.with_text_style(Some(self.text.clone()), f)
} else {
f(cx)
}
}
// pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
// where
// C: BorrowAppContext,
// F: FnOnce(&mut C) -> R,
// {
// if self.text.is_some() {
// cx.with_text_style(Some(self.text.clone()), f)
// } else {
// f(cx)
// }
// }
/// Apply overflow to content mask
pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
where
C: BorrowWindow,
F: FnOnce(&mut C) -> R,
{
let current_mask = cx.content_mask();
// /// Apply overflow to content mask
// pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
// where
// C: BorrowWindow,
// F: FnOnce(&mut C) -> R,
// {
// let current_mask = cx.content_mask();
let min = current_mask.bounds.origin;
let max = current_mask.bounds.lower_right();
// let min = current_mask.bounds.origin;
// let max = current_mask.bounds.lower_right();
let mask_bounds = match (
self.overflow.x == Overflow::Visible,
self.overflow.y == Overflow::Visible,
) {
// x and y both visible
(true, true) => return f(cx),
// x visible, y hidden
(true, false) => Bounds::from_corners(
point(min.x, bounds.origin.y),
point(max.x, bounds.lower_right().y),
),
// x hidden, y visible
(false, true) => Bounds::from_corners(
point(bounds.origin.x, min.y),
point(bounds.lower_right().x, max.y),
),
// both hidden
(false, false) => bounds,
};
let mask = ContentMask {
bounds: mask_bounds,
};
// let mask_bounds = match (
// self.overflow.x == Overflow::Visible,
// self.overflow.y == Overflow::Visible,
// ) {
// // x and y both visible
// (true, true) => return f(cx),
// // x visible, y hidden
// (true, false) => Bounds::from_corners(
// point(min.x, bounds.origin.y),
// point(max.x, bounds.lower_right().y),
// ),
// // x hidden, y visible
// (false, true) => Bounds::from_corners(
// point(bounds.origin.x, min.y),
// point(bounds.lower_right().x, max.y),
// ),
// // both hidden
// (false, false) => bounds,
// };
// let mask = ContentMask {
// 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.
pub fn paint(
@@ -369,14 +384,14 @@ impl Style {
});
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.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
self.border_widths.to_pixels(rem_size),
self.border_color.unwrap_or_default(),
Edges::default(),
Hsla::transparent_black(),
));
});
}
@@ -385,6 +400,18 @@ impl Style {
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)]
if self.debug_below {
cx.remove_global::<DebugBelow>();
+3 -1
View File
@@ -499,7 +499,8 @@ impl Render for Dock {
cx.stop_propagation();
}
}))
.z_index(1);
.z_index(1)
.block_mouse();
const HANDLE_SIZE: Pixels = Pixels(6.);
@@ -536,6 +537,7 @@ impl Render for Dock {
div()
.flex()
.border_color(cx.theme().colors().border)
.overflow_hidden()
.map(|this| match self.position().axis() {
Axis::Horizontal => this.w(px(size)).h_full().flex_row(),
Axis::Vertical => this.h(px(size)).w_full().flex_col(),