gpui: Support hitbox blocking mouse interaction except scrolling (#31712)

tl;dr: This adds `.block_mouse_except_scroll()` which should typically
be used instead of `.occlude()` for cases when the mouse shouldn't
interact with elements drawn below an element. The rationale for
treating scroll events differently:

* Mouse move / click / styles / tooltips are for elements the user is
interacting with directly.
* Mouse scroll events are about finding the current outer scroll
container.

Most use of `occlude` should probably be switched to this, but I figured
I'd derisk this change by minimizing behavior changes to just the 3 uses
of `block_mouse_except_scroll`.

GPUI changes:

* Added `InteractiveElement::block_mouse_except_scroll()`, and removes
`stop_mouse_events_except_scroll()`

* Added `Hitbox::should_handle_scroll()` to be used when handling scroll
wheel events.

* `Window::insert_hitbox` now takes `HitboxBehavior` instead of
`occlude: bool`.

    - `false` for that bool is now `HitboxBehavior::Normal`.

    - `true` for that bool is now `HitboxBehavior::BlockMouse`.
    
    - The new mode is `HitboxBehavior::BlockMouseExceptScroll`.

* Removes `Default` impl for `HitboxId` since applications should not
manually create `HitboxId(0)`.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-05-29 15:41:15 -06:00 committed by GitHub
parent 2abc5893c1
commit 9086784038
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 231 additions and 99 deletions

View file

@ -2162,7 +2162,7 @@ impl ActiveThread {
.inset_0()
.bg(panel_bg)
.opacity(0.8)
.stop_mouse_events_except_scroll()
.block_mouse_except_scroll()
.on_click(cx.listener(Self::handle_cancel_click));
v_flex()

View file

@ -699,7 +699,7 @@ fn render_diff_hunk_controls(
.rounded_b_md()
.bg(cx.theme().colors().editor_background)
.gap_1()
.stop_mouse_events_except_scroll()
.block_mouse_except_scroll()
.shadow_md()
.children(vec![
Button::new(("reject", row as u64), "Reject")

View file

@ -21907,7 +21907,7 @@ fn render_diff_hunk_controls(
.rounded_b_lg()
.bg(cx.theme().colors().editor_background)
.gap_1()
.stop_mouse_events_except_scroll()
.block_mouse_except_scroll()
.shadow_md()
.child(if status.has_secondary_hunk() {
Button::new(("stage", row as u64), "Stage")

View file

@ -42,13 +42,13 @@ use git::{
use gpui::{
Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges,
Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, Hsla,
InteractiveElement, IntoElement, IsZero, Keystroke, Length, ModifiersChangedEvent, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
transparent_black,
Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, Keystroke, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString,
Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, WeakEntity,
Window, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px,
quad, relative, size, solid_background, transparent_black,
};
use itertools::Itertools;
use language::language_settings::{
@ -1620,7 +1620,7 @@ impl EditorElement {
);
let layout = ScrollbarLayout::for_minimap(
window.insert_hitbox(minimap_bounds, false),
window.insert_hitbox(minimap_bounds, HitboxBehavior::Normal),
visible_editor_lines,
total_editor_lines,
minimap_line_height,
@ -1791,7 +1791,7 @@ impl EditorElement {
if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) {
let hunk_bounds =
Self::diff_hunk_bounds(snapshot, line_height, gutter_hitbox.bounds, hunk);
*hitbox = Some(window.insert_hitbox(hunk_bounds, true));
*hitbox = Some(window.insert_hitbox(hunk_bounds, HitboxBehavior::BlockMouse));
}
}
}
@ -2883,7 +2883,7 @@ impl EditorElement {
let hitbox = line_origin.map(|line_origin| {
window.insert_hitbox(
Bounds::new(line_origin, size(shaped_line.width, line_height)),
false,
HitboxBehavior::Normal,
)
});
#[cfg(test)]
@ -6371,7 +6371,7 @@ impl EditorElement {
}
};
if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
delta = delta.coalesce(event.delta);
editor.update(cx, |editor, cx| {
let position_map: &PositionMap = &position_map;
@ -7651,15 +7651,17 @@ impl Element for EditorElement {
.map(|(guide, active)| (self.column_pixels(*guide, window, cx), *active))
.collect::<SmallVec<[_; 2]>>();
let hitbox = window.insert_hitbox(bounds, false);
let gutter_hitbox =
window.insert_hitbox(gutter_bounds(bounds, gutter_dimensions), false);
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
let gutter_hitbox = window.insert_hitbox(
gutter_bounds(bounds, gutter_dimensions),
HitboxBehavior::Normal,
);
let text_hitbox = window.insert_hitbox(
Bounds {
origin: gutter_hitbox.top_right(),
size: size(text_width, bounds.size.height),
},
false,
HitboxBehavior::Normal,
);
let content_origin = text_hitbox.origin + content_offset;
@ -8880,7 +8882,7 @@ impl EditorScrollbars {
})
.map(|(viewport_size, scroll_range)| {
ScrollbarLayout::new(
window.insert_hitbox(scrollbar_bounds_for(axis), false),
window.insert_hitbox(scrollbar_bounds_for(axis), HitboxBehavior::Normal),
viewport_size,
scroll_range,
glyph_grid_cell.along(axis),

View file

@ -1,8 +1,8 @@
use gpui::{
App, Application, Bounds, Context, CursorStyle, Decorations, Hsla, MouseButton, Pixels, Point,
ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black,
white,
App, Application, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton,
Pixels, Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds,
WindowDecorations, WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size,
transparent_black, white,
};
struct WindowShadow {}
@ -37,7 +37,7 @@ impl Render for WindowShadow {
point(px(0.0), px(0.0)),
window.window_bounds().get_bounds().size,
),
false,
HitboxBehavior::Normal,
)
},
move |_bounds, hitbox, window, _cx| {

View file

@ -17,10 +17,10 @@
use crate::{
Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxId,
InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow,
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior,
HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
LayoutId, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
StyleRefinement, Styled, Task, TooltipId, Visibility, Window, point, px, size,
};
use collections::HashMap;
@ -313,7 +313,7 @@ impl Interactivity {
) {
self.scroll_wheel_listeners
.push(Box::new(move |event, phase, hitbox, window, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
(listener)(event, window, cx);
}
}));
@ -567,19 +567,20 @@ impl Interactivity {
});
}
/// Block the mouse from interacting with this element or any of its children
/// Block the mouse from all interactions with elements behind this element's hitbox. Typically
/// `block_mouse_except_scroll` should be preferred.
///
/// The imperative API equivalent to [`InteractiveElement::occlude`]
pub fn occlude_mouse(&mut self) {
self.occlude_mouse = true;
self.hitbox_behavior = HitboxBehavior::BlockMouse;
}
/// Registers event handles that stop propagation of mouse events for non-scroll events.
/// Block non-scroll mouse interactions with elements behind this element's hitbox. See
/// [`Hitbox::is_hovered`] for details.
///
/// The imperative API equivalent to [`InteractiveElement::block_mouse_except_scroll`]
pub fn stop_mouse_events_except_scroll(&mut self) {
self.on_any_mouse_down(|_, _, cx| cx.stop_propagation());
self.on_any_mouse_up(|_, _, cx| cx.stop_propagation());
self.on_click(|_, _, cx| cx.stop_propagation());
self.on_hover(|_, _, cx| cx.stop_propagation());
pub fn block_mouse_except_scroll(&mut self) {
self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
}
}
@ -949,7 +950,8 @@ pub trait InteractiveElement: Sized {
self
}
/// Block the mouse from interacting with this element or any of its children
/// Block the mouse from all interactions with elements behind this element's hitbox. Typically
/// `block_mouse_except_scroll` should be preferred.
/// The fluent API equivalent to [`Interactivity::occlude_mouse`]
fn occlude(mut self) -> Self {
self.interactivity().occlude_mouse();
@ -961,10 +963,12 @@ pub trait InteractiveElement: Sized {
self.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
}
/// Registers event handles that stop propagation of mouse events for non-scroll events.
/// Block non-scroll mouse interactions with elements behind this element's hitbox. See
/// [`Hitbox::is_hovered`] for details.
///
/// The fluent API equivalent to [`Interactivity::block_mouse_except_scroll`]
fn stop_mouse_events_except_scroll(mut self) -> Self {
self.interactivity().stop_mouse_events_except_scroll();
fn block_mouse_except_scroll(mut self) -> Self {
self.interactivity().block_mouse_except_scroll();
self
}
}
@ -1448,7 +1452,7 @@ pub struct Interactivity {
pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
pub(crate) tooltip_builder: Option<TooltipBuilder>,
pub(crate) occlude_mouse: bool,
pub(crate) hitbox_behavior: HitboxBehavior,
#[cfg(any(feature = "inspector", debug_assertions))]
pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
@ -1594,7 +1598,7 @@ impl Interactivity {
style.overflow_mask(bounds, window.rem_size()),
|window| {
let hitbox = if self.should_insert_hitbox(&style, window, cx) {
Some(window.insert_hitbox(bounds, self.occlude_mouse))
Some(window.insert_hitbox(bounds, self.hitbox_behavior))
} else {
None
};
@ -1611,7 +1615,7 @@ impl Interactivity {
}
fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
self.occlude_mouse
self.hitbox_behavior != HitboxBehavior::Normal
|| style.mouse_cursor.is_some()
|| self.group.is_some()
|| self.scroll_offset.is_some()
@ -2270,7 +2274,7 @@ impl Interactivity {
let hitbox = hitbox.clone();
let current_view = window.current_view();
window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
let mut scroll_offset = scroll_offset.borrow_mut();
let old_scroll_offset = *scroll_offset;
let delta = event.delta.pixel_delta(line_height);

View file

@ -9,8 +9,9 @@
use crate::{
AnyElement, App, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, Element, EntityId,
FocusHandle, GlobalElementId, Hitbox, InspectorElementId, IntoElement, Overflow, Pixels, Point,
ScrollWheelEvent, Size, Style, StyleRefinement, Styled, Window, point, px, size,
FocusHandle, GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, IntoElement,
Overflow, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, Window, point,
px, size,
};
use collections::VecDeque;
use refineable::Refineable as _;
@ -906,7 +907,7 @@ impl Element for List {
let mut style = Style::default();
style.refine(&self.style);
let hitbox = window.insert_hitbox(bounds, false);
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
// If the width of the list has changed, invalidate all cached item heights
if state.last_layout_bounds.map_or(true, |last_bounds| {
@ -962,7 +963,7 @@ impl Element for List {
let scroll_top = prepaint.layout.scroll_top;
let hitbox_id = prepaint.hitbox.id;
window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(window) {
if phase == DispatchPhase::Bubble && hitbox_id.should_handle_scroll(window) {
list_state.0.borrow_mut().scroll(
&scroll_top,
height,

View file

@ -1,8 +1,8 @@
use crate::{
ActiveTooltip, AnyView, App, Bounds, DispatchPhase, Element, ElementId, GlobalElementId,
HighlightStyle, Hitbox, InspectorElementId, IntoElement, LayoutId, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextOverflow, TextRun,
TextStyle, TooltipId, WhiteSpace, Window, WrappedLine, WrappedLineLayout,
HighlightStyle, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, LayoutId,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextOverflow,
TextRun, TextStyle, TooltipId, WhiteSpace, Window, WrappedLine, WrappedLineLayout,
register_tooltip_mouse_handlers, set_tooltip_on_window,
};
use anyhow::Context as _;
@ -739,7 +739,7 @@ impl Element for InteractiveText {
self.text
.prepaint(None, inspector_id, bounds, state, window, cx);
let hitbox = window.insert_hitbox(bounds, false);
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
(hitbox, interactive_state)
},
)

View file

@ -413,14 +413,42 @@ pub(crate) struct CursorStyleRequest {
pub(crate) style: CursorStyle,
}
/// An identifier for a [Hitbox].
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct HitboxId(usize);
#[derive(Default, Eq, PartialEq)]
pub(crate) struct HitTest {
pub(crate) ids: SmallVec<[HitboxId; 8]>,
pub(crate) hover_hitbox_count: usize,
}
/// An identifier for a [Hitbox] which also includes [HitboxBehavior].
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct HitboxId(u64);
impl HitboxId {
/// Checks if the hitbox with this id is currently hovered.
pub fn is_hovered(&self, window: &Window) -> bool {
window.mouse_hit_test.0.contains(self)
/// Checks if the hitbox with this ID is currently hovered. Except when handling
/// `ScrollWheelEvent`, this is typically what you want when determining whether to handle mouse
/// events or paint hover styles.
///
/// See [`Hitbox::is_hovered`] for details.
pub fn is_hovered(self, window: &Window) -> bool {
let hit_test = &window.mouse_hit_test;
for id in hit_test.ids.iter().take(hit_test.hover_hitbox_count) {
if self == *id {
return true;
}
}
return false;
}
/// Checks if the hitbox with this ID contains the mouse and should handle scroll events.
/// Typically this should only be used when handling `ScrollWheelEvent`, and otherwise
/// `is_hovered` should be used. See the documentation of `Hitbox::is_hovered` for details about
/// this distinction.
pub fn should_handle_scroll(self, window: &Window) -> bool {
window.mouse_hit_test.ids.contains(&self)
}
fn next(mut self) -> HitboxId {
HitboxId(self.0.wrapping_add(1))
}
}
@ -435,19 +463,98 @@ pub struct Hitbox {
pub bounds: Bounds<Pixels>,
/// The content mask when the hitbox was inserted.
pub content_mask: ContentMask<Pixels>,
/// Whether the hitbox occludes other hitboxes inserted prior.
pub opaque: bool,
/// Flags that specify hitbox behavior.
pub behavior: HitboxBehavior,
}
impl Hitbox {
/// Checks if the hitbox is currently hovered.
/// Checks if the hitbox is currently hovered. Except when handling `ScrollWheelEvent`, this is
/// typically what you want when determining whether to handle mouse events or paint hover
/// styles.
///
/// This can return `false` even when the hitbox contains the mouse, if a hitbox in front of
/// this sets `HitboxBehavior::BlockMouse` (`InteractiveElement::occlude`) or
/// `HitboxBehavior::BlockMouseExceptScroll` (`InteractiveElement::block_mouse_except_scroll`).
///
/// Handling of `ScrollWheelEvent` should typically use `should_handle_scroll` instead.
/// Concretely, this is due to use-cases like overlays that cause the elements under to be
/// non-interactive while still allowing scrolling. More abstractly, this is because
/// `is_hovered` is about element interactions directly under the mouse - mouse moves, clicks,
/// hover styling, etc. In contrast, scrolling is about finding the current outer scrollable
/// container.
pub fn is_hovered(&self, window: &Window) -> bool {
self.id.is_hovered(window)
}
/// Checks if the hitbox contains the mouse and should handle scroll events. Typically this
/// should only be used when handling `ScrollWheelEvent`, and otherwise `is_hovered` should be
/// used. See the documentation of `Hitbox::is_hovered` for details about this distinction.
///
/// This can return `false` even when the hitbox contains the mouse, if a hitbox in front of
/// this sets `HitboxBehavior::BlockMouse` (`InteractiveElement::occlude`).
pub fn should_handle_scroll(&self, window: &Window) -> bool {
self.id.should_handle_scroll(window)
}
}
#[derive(Default, Eq, PartialEq)]
pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>);
/// How the hitbox affects mouse behavior.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum HitboxBehavior {
/// Normal hitbox mouse behavior, doesn't affect mouse handling for other hitboxes.
#[default]
Normal,
/// All hitboxes behind this hitbox will be ignored and so will have `hitbox.is_hovered() ==
/// false` and `hitbox.should_handle_scroll() == false`. Typically for elements this causes
/// skipping of all mouse events, hover styles, and tooltips. This flag is set by
/// [`InteractiveElement::occlude`].
///
/// For mouse handlers that check those hitboxes, this behaves the same as registering a
/// bubble-phase handler for every mouse event type:
///
/// ```
/// window.on_mouse_event(move |_: &EveryMouseEventTypeHere, phase, window, cx| {
/// if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
/// cx.stop_propagation();
/// }
/// }
/// ```
///
/// This has effects beyond event handling - any use of hitbox checking, such as hover
/// styles and tooltops. These other behaviors are the main point of this mechanism. An
/// alternative might be to not affect mouse event handling - but this would allow
/// inconsistent UI where clicks and moves interact with elements that are not considered to
/// be hovered.
BlockMouse,
/// All hitboxes behind this hitbox will have `hitbox.is_hovered() == false`, even when
/// `hitbox.should_handle_scroll() == true`. Typically for elements this causes all mouse
/// interaction except scroll events to be ignored - see the documentation of
/// [`Hitbox::is_hovered`] for details. This flag is set by
/// [`InteractiveElement::block_mouse_except_scroll`].
///
/// For mouse handlers that check those hitboxes, this behaves the same as registering a
/// bubble-phase handler for every mouse event type **except** `ScrollWheelEvent`:
///
/// ```
/// window.on_mouse_event(move |_: &EveryMouseEventTypeExceptScroll, phase, window, _cx| {
/// if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
/// cx.stop_propagation();
/// }
/// }
/// ```
///
/// See the documentation of [`Hitbox::is_hovered`] for details of why `ScrollWheelEvent` is
/// handled differently than other mouse events. If also blocking these scroll events is
/// desired, then a `cx.stop_propagation()` handler like the one above can be used.
///
/// This has effects beyond event handling - this affects any use of `is_hovered`, such as
/// hover styles and tooltops. These other behaviors are the main point of this mechanism.
/// An alternative might be to not affect mouse event handling - but this would allow
/// inconsistent UI where clicks and moves interact with elements that are not considered to
/// be hovered.
BlockMouseExceptScroll,
}
/// An identifier for a tooltip.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
@ -578,16 +685,26 @@ impl Frame {
}
pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
let mut set_hover_hitbox_count = false;
let mut hit_test = HitTest::default();
for hitbox in self.hitboxes.iter().rev() {
let bounds = hitbox.bounds.intersect(&hitbox.content_mask.bounds);
if bounds.contains(&position) {
hit_test.0.push(hitbox.id);
if hitbox.opaque {
hit_test.ids.push(hitbox.id);
if !set_hover_hitbox_count
&& hitbox.behavior == HitboxBehavior::BlockMouseExceptScroll
{
hit_test.hover_hitbox_count = hit_test.ids.len();
set_hover_hitbox_count = true;
}
if hitbox.behavior == HitboxBehavior::BlockMouse {
break;
}
}
}
if !set_hover_hitbox_count {
hit_test.hover_hitbox_count = hit_test.ids.len();
}
hit_test
}
@ -638,7 +755,7 @@ pub struct Window {
pub(crate) image_cache_stack: Vec<AnyImageCache>,
pub(crate) rendered_frame: Frame,
pub(crate) next_frame: Frame,
pub(crate) next_hitbox_id: HitboxId,
next_hitbox_id: HitboxId,
pub(crate) next_tooltip_id: TooltipId,
pub(crate) tooltip_bounds: Option<TooltipBounds>,
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
@ -927,7 +1044,7 @@ impl Window {
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
next_frame_callbacks,
next_hitbox_id: HitboxId::default(),
next_hitbox_id: HitboxId(0),
next_tooltip_id: TooltipId::default(),
tooltip_bounds: None,
dirty_views: FxHashSet::default(),
@ -2870,17 +2987,17 @@ impl Window {
/// to determine whether the inserted hitbox was the topmost.
///
/// This method should only be called as part of the prepaint phase of element drawing.
pub fn insert_hitbox(&mut self, bounds: Bounds<Pixels>, opaque: bool) -> Hitbox {
pub fn insert_hitbox(&mut self, bounds: Bounds<Pixels>, behavior: HitboxBehavior) -> Hitbox {
self.invalidator.debug_assert_prepaint();
let content_mask = self.content_mask();
let id = self.next_hitbox_id;
self.next_hitbox_id.0 += 1;
let mut id = self.next_hitbox_id;
self.next_hitbox_id = self.next_hitbox_id.next();
let hitbox = Hitbox {
id,
bounds,
content_mask,
opaque,
behavior,
};
self.next_frame.hitboxes.push(hitbox.clone());
hitbox
@ -4042,7 +4159,7 @@ impl Window {
inspector.update(cx, |inspector, _cx| {
if let Some(depth) = inspector.pick_depth.as_mut() {
*depth += delta_y.0 / SCROLL_PIXELS_PER_LAYER;
let max_depth = self.mouse_hit_test.0.len() as f32 - 0.5;
let max_depth = self.mouse_hit_test.ids.len() as f32 - 0.5;
if *depth < 0.0 {
*depth = 0.0;
} else if *depth > max_depth {
@ -4067,9 +4184,9 @@ impl Window {
) -> Option<(HitboxId, crate::InspectorElementId)> {
if let Some(pick_depth) = inspector.pick_depth {
let depth = (pick_depth as i64).try_into().unwrap_or(0);
let max_skipped = self.mouse_hit_test.0.len().saturating_sub(1);
let max_skipped = self.mouse_hit_test.ids.len().saturating_sub(1);
let skip_count = (depth as usize).min(max_skipped);
for hitbox_id in self.mouse_hit_test.0.iter().skip(skip_count) {
for hitbox_id in self.mouse_hit_test.ids.iter().skip(skip_count) {
if let Some(inspector_id) = frame.inspector_hitboxes.get(hitbox_id) {
return Some((*hitbox_id, inspector_id.clone()));
}

View file

@ -3,6 +3,7 @@ mod path_range;
use base64::Engine as _;
use futures::FutureExt as _;
use gpui::HitboxBehavior;
use language::LanguageName;
use log::Level;
pub use path_range::{LineCol, PathWithRange};
@ -1211,7 +1212,7 @@ impl Element for MarkdownElement {
window.set_focus_handle(&focus_handle, cx);
window.set_view_id(self.markdown.entity_id());
let hitbox = window.insert_hitbox(bounds, false);
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
rendered_markdown.element.prepaint(window, cx);
self.autoscroll(&rendered_markdown.text, window, cx);
hitbox

View file

@ -136,7 +136,9 @@ pub struct IndentGuideLayout {
/// Implements the necessary functionality for rendering indent guides inside a uniform list.
mod uniform_list {
use gpui::{DispatchPhase, Hitbox, MouseButton, MouseDownEvent, MouseMoveEvent};
use gpui::{
DispatchPhase, Hitbox, HitboxBehavior, MouseButton, MouseDownEvent, MouseMoveEvent,
};
use super::*;
@ -256,7 +258,12 @@ mod uniform_list {
.indent_guides
.as_ref()
.iter()
.map(|guide| window.insert_hitbox(guide.hitbox.unwrap_or(guide.bounds), false))
.map(|guide| {
window.insert_hitbox(
guide.hitbox.unwrap_or(guide.bounds),
HitboxBehavior::Normal,
)
})
.collect();
Self::PrepaintState::Interactive {
hitboxes: Rc::new(hitboxes),

View file

@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
AnyElement, AnyView, App, Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId,
Entity, Focusable as _, GlobalElementId, HitboxId, InteractiveElement, IntoElement, LayoutId,
Length, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, Style, Window, anchored,
deferred, div, point, prelude::FluentBuilder, px, size,
Entity, Focusable as _, GlobalElementId, HitboxBehavior, HitboxId, InteractiveElement,
IntoElement, LayoutId, Length, ManagedView, MouseDownEvent, ParentElement, Pixels, Point,
Style, Window, anchored, deferred, div, point, prelude::FluentBuilder, px, size,
};
use crate::prelude::*;
@ -421,7 +421,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
((), element_state)
});
window.insert_hitbox(bounds, false).id
window.insert_hitbox(bounds, HitboxBehavior::Normal).id
})
}

View file

@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
AnyElement, App, Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity,
Focusable as _, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId,
ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Window, anchored,
deferred, div, px,
Focusable as _, GlobalElementId, Hitbox, HitboxBehavior, InteractiveElement, IntoElement,
LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Window,
anchored, deferred, div, px,
};
pub struct RightClickMenu<M: ManagedView> {
@ -185,7 +185,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
window: &mut Window,
cx: &mut App,
) -> PrepaintState {
let hitbox = window.insert_hitbox(bounds, false);
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(window, cx);

View file

@ -3,9 +3,9 @@ use std::{any::Any, cell::Cell, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
use crate::{IntoElement, prelude::*, px, relative};
use gpui::{
Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element,
ElementId, Entity, EntityId, GlobalElementId, Hitbox, Hsla, IsZero, LayoutId, ListState,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollHandle, ScrollWheelEvent,
Size, Style, UniformListScrollHandle, Window, quad,
ElementId, Entity, EntityId, GlobalElementId, Hitbox, HitboxBehavior, Hsla, IsZero, LayoutId,
ListState, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollHandle,
ScrollWheelEvent, Size, Style, UniformListScrollHandle, Window, quad,
};
pub struct Scrollbar {
@ -226,7 +226,7 @@ impl Element for Scrollbar {
_: &mut App,
) -> Self::PrepaintState {
window.with_content_mask(Some(ContentMask { bounds }), |window| {
window.insert_hitbox(bounds, false)
window.insert_hitbox(bounds, HitboxBehavior::Normal)
})
}

View file

@ -902,9 +902,9 @@ mod element {
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
use gpui::{
Along, AnyElement, App, Axis, BorderStyle, Bounds, Element, GlobalElementId, IntoElement,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
WeakEntity, Window, px, relative, size,
Along, AnyElement, App, Axis, BorderStyle, Bounds, Element, GlobalElementId,
HitboxBehavior, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
Pixels, Point, Size, Style, WeakEntity, Window, px, relative, size,
};
use gpui::{CursorStyle, Hitbox};
use parking_lot::Mutex;
@ -1091,7 +1091,7 @@ mod element {
};
PaneAxisHandleLayout {
hitbox: window.insert_hitbox(handle_bounds, true),
hitbox: window.insert_hitbox(handle_bounds, HitboxBehavior::Normal),
divider_bounds,
}
}

View file

@ -37,10 +37,10 @@ use futures::{
use gpui::{
Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
Focusable, Global, Hsla, KeyContext, Keystroke, ManagedView, MouseButton, PathPromptOptions,
Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task, Tiling, WeakEntity,
WindowBounds, WindowHandle, WindowId, WindowOptions, action_as, actions, canvas,
impl_action_as, impl_actions, point, relative, size, transparent_black,
Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task,
Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId, WindowOptions, action_as, actions,
canvas, impl_action_as, impl_actions, point, relative, size, transparent_black,
};
pub use history_manager::*;
pub use item::{
@ -7344,7 +7344,7 @@ pub fn client_side_decorations(
point(px(0.0), px(0.0)),
window.window_bounds().get_bounds().size,
),
false,
HitboxBehavior::Normal,
)
},
move |_bounds, hitbox, window, cx| {