diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 073aac2873..fadabaf035 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5131,7 +5131,7 @@ impl EditorElement { let is_singleton = self.editor.read(cx).is_singleton(cx); let line_height = layout.position_map.line_height; - window.set_cursor_style(CursorStyle::Arrow, Some(&layout.gutter_hitbox)); + window.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox); for LineNumberLayout { shaped_line, @@ -5158,9 +5158,9 @@ impl EditorElement { // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor. // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor. if is_singleton { - window.set_cursor_style(CursorStyle::IBeam, Some(&hitbox)); + window.set_cursor_style(CursorStyle::IBeam, &hitbox); } else { - window.set_cursor_style(CursorStyle::PointingHand, Some(&hitbox)); + window.set_cursor_style(CursorStyle::PointingHand, &hitbox); } } } @@ -5378,7 +5378,7 @@ impl EditorElement { .read(cx) .all_diff_hunks_expanded() { - window.set_cursor_style(CursorStyle::PointingHand, Some(hunk_hitbox)); + window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox); } } } @@ -5452,7 +5452,7 @@ impl EditorElement { |window| { let editor = self.editor.read(cx); if editor.mouse_cursor_hidden { - window.set_cursor_style(CursorStyle::None, None); + window.set_window_cursor_style(CursorStyle::None); } else if editor .hovered_link_state .as_ref() @@ -5460,13 +5460,10 @@ impl EditorElement { { window.set_cursor_style( CursorStyle::PointingHand, - Some(&layout.position_map.text_hitbox), + &layout.position_map.text_hitbox, ); } else { - window.set_cursor_style( - CursorStyle::IBeam, - Some(&layout.position_map.text_hitbox), - ); + window.set_cursor_style(CursorStyle::IBeam, &layout.position_map.text_hitbox); }; self.paint_lines_background(layout, window, cx); @@ -5607,6 +5604,7 @@ impl EditorElement { let Some(scrollbars_layout) = layout.scrollbars_layout.take() else { return; }; + let any_scrollbar_dragged = self.editor.read(cx).scroll_manager.any_scrollbar_dragged(); for (scrollbar_layout, axis) in scrollbars_layout.iter_scrollbars() { let hitbox = &scrollbar_layout.hitbox; @@ -5672,7 +5670,11 @@ impl EditorElement { BorderStyle::Solid, )); - window.set_cursor_style(CursorStyle::Arrow, Some(&hitbox)); + if any_scrollbar_dragged { + window.set_window_cursor_style(CursorStyle::Arrow); + } else { + window.set_cursor_style(CursorStyle::Arrow, &hitbox); + } } }) } @@ -5740,7 +5742,7 @@ impl EditorElement { } }); - if self.editor.read(cx).scroll_manager.any_scrollbar_dragged() { + if any_scrollbar_dragged { window.on_mouse_event({ let editor = self.editor.clone(); move |_: &MouseUpEvent, phase, window, cx| { @@ -6126,6 +6128,7 @@ impl EditorElement { fn paint_minimap(&self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { if let Some(mut layout) = layout.minimap.take() { let minimap_hitbox = layout.thumb_layout.hitbox.clone(); + let dragging_minimap = self.editor.read(cx).scroll_manager.is_dragging_minimap(); window.paint_layer(layout.thumb_layout.hitbox.bounds, |window| { window.with_element_namespace("minimap", |window| { @@ -6177,7 +6180,11 @@ impl EditorElement { }); }); - window.set_cursor_style(CursorStyle::Arrow, Some(&minimap_hitbox)); + if dragging_minimap { + window.set_window_cursor_style(CursorStyle::Arrow); + } else { + window.set_cursor_style(CursorStyle::Arrow, &minimap_hitbox); + } let minimap_axis = ScrollbarAxis::Vertical; let pixels_per_line = (minimap_hitbox.size.height / layout.max_scroll_top) @@ -6238,7 +6245,7 @@ impl EditorElement { } }); - if self.editor.read(cx).scroll_manager.is_dragging_minimap() { + if dragging_minimap { window.on_mouse_event({ let editor = self.editor.clone(); move |event: &MouseUpEvent, phase, window, cx| { diff --git a/crates/gpui/examples/window_shadow.rs b/crates/gpui/examples/window_shadow.rs index e75e50e31a..06dde91133 100644 --- a/crates/gpui/examples/window_shadow.rs +++ b/crates/gpui/examples/window_shadow.rs @@ -61,7 +61,7 @@ impl Render for WindowShadow { CursorStyle::ResizeUpRightDownLeft } }, - Some(&hitbox), + &hitbox, ); }, ) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 4c96ede3ca..bbc3454923 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1744,11 +1744,11 @@ impl Interactivity { if let Some(drag) = cx.active_drag.as_ref() { if let Some(mouse_cursor) = drag.cursor_style { - window.set_cursor_style(mouse_cursor, None); + window.set_window_cursor_style(mouse_cursor); } } else { if let Some(mouse_cursor) = style.mouse_cursor { - window.set_cursor_style(mouse_cursor, Some(hitbox)); + window.set_cursor_style(mouse_cursor, hitbox); } } diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 86cf4407b5..014f617e2c 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -769,7 +769,7 @@ impl Element for InteractiveText { .iter() .any(|range| range.contains(&ix)) { - window.set_cursor_style(crate::CursorStyle::PointingHand, Some(hitbox)) + window.set_cursor_style(crate::CursorStyle::PointingHand, hitbox) } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 20eaca0c5e..8253320898 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -408,7 +408,7 @@ pub(crate) type AnyMouseListener = #[derive(Clone)] pub(crate) struct CursorStyleRequest { - pub(crate) hitbox_id: Option, // None represents whole window + pub(crate) hitbox_id: HitboxId, pub(crate) style: CursorStyle, } @@ -622,6 +622,7 @@ pub(crate) struct Frame { pub(crate) input_handlers: Vec>, pub(crate) tooltip_requests: Vec>, pub(crate) cursor_styles: Vec, + window_cursor_style: Option, #[cfg(any(test, feature = "test-support"))] pub(crate) debug_bounds: FxHashMap>, #[cfg(any(feature = "inspector", debug_assertions))] @@ -666,6 +667,7 @@ impl Frame { input_handlers: Vec::new(), tooltip_requests: Vec::new(), cursor_styles: Vec::new(), + window_cursor_style: None, #[cfg(any(test, feature = "test-support"))] debug_bounds: FxHashMap::default(), @@ -691,6 +693,7 @@ impl Frame { self.window_control_hitboxes.clear(); self.deferred_draws.clear(); self.focus = None; + self.window_cursor_style = None; #[cfg(any(feature = "inspector", debug_assertions))] { @@ -699,6 +702,17 @@ impl Frame { } } + pub(crate) fn cursor_style(&self, window: &Window) -> Option { + self.window_cursor_style.or_else(|| { + self.cursor_styles.iter().rev().find_map(|request| { + request + .hitbox_id + .is_hovered(window) + .then_some(request.style) + }) + }) + } + pub(crate) fn hit_test(&self, position: Point) -> HitTest { let mut set_hover_hitbox_count = false; let mut hit_test = HitTest::default(); @@ -2157,14 +2171,23 @@ impl Window { /// Updates the cursor style at the platform level. This method should only be called /// during the prepaint phase of element drawing. - pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: Option<&Hitbox>) { + pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) { self.invalidator.debug_assert_paint(); self.next_frame.cursor_styles.push(CursorStyleRequest { - hitbox_id: hitbox.map(|hitbox| hitbox.id), + hitbox_id: hitbox.id, style, }); } + /// Updates the cursor style for the entire window at the platform level. A cursor + /// style using this method will have precedence over any cursor style set using + /// `set_cursor_style`. This method should only be called during the prepaint + /// phase of element drawing. + pub fn set_window_cursor_style(&mut self, style: CursorStyle) { + self.invalidator.debug_assert_paint(); + self.next_frame.window_cursor_style = Some(style); + } + /// Sets a tooltip to be rendered for the upcoming frame. This method should only be called /// during the paint phase of element drawing. pub fn set_tooltip(&mut self, tooltip: AnyTooltip) -> TooltipId { @@ -3245,15 +3268,7 @@ impl Window { if self.is_window_hovered() { let style = self .rendered_frame - .cursor_styles - .iter() - .rev() - .find(|request| { - request - .hitbox_id - .map_or(true, |hitbox_id| hitbox_id.is_hovered(self)) - }) - .map(|request| request.style) + .cursor_style(self) .unwrap_or(CursorStyle::Arrow); cx.platform.set_cursor_style(style); } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 626ffcef6f..172cda09bb 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -576,9 +576,9 @@ impl MarkdownElement { .is_some(); if is_hovering_link { - window.set_cursor_style(CursorStyle::PointingHand, Some(hitbox)); + window.set_cursor_style(CursorStyle::PointingHand, hitbox); } else { - window.set_cursor_style(CursorStyle::IBeam, Some(hitbox)); + window.set_cursor_style(CursorStyle::IBeam, hitbox); } let on_open_url = self.on_url_click.take(); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 2ea27fe5bb..3c68c0501d 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -974,9 +974,9 @@ impl Element for TerminalElement { && bounds.contains(&window.mouse_position()) && self.terminal_view.read(cx).hover.is_some() { - window.set_cursor_style(gpui::CursorStyle::PointingHand, Some(&layout.hitbox)); + window.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox); } else { - window.set_cursor_style(gpui::CursorStyle::IBeam, Some(&layout.hitbox)); + window.set_cursor_style(gpui::CursorStyle::IBeam, &layout.hitbox); } let original_cursor = layout.cursor.take(); diff --git a/crates/ui/src/components/indent_guides.rs b/crates/ui/src/components/indent_guides.rs index f6f256323d..01b3e2cf74 100644 --- a/crates/ui/src/components/indent_guides.rs +++ b/crates/ui/src/components/indent_guides.rs @@ -330,7 +330,7 @@ mod uniform_list { }); let mut hovered_hitbox_id = None; for (i, hitbox) in hitboxes.iter().enumerate() { - window.set_cursor_style(gpui::CursorStyle::PointingHand, Some(hitbox)); + window.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox); let indent_guide = &self.indent_guides[i]; let fill_color = if hitbox.is_hovered(window) { hovered_hitbox_id = Some(hitbox.id); diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 4ee2760c93..2a8c4885ac 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -2,10 +2,10 @@ 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, HitboxBehavior, Hsla, IsZero, LayoutId, - ListState, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollHandle, - ScrollWheelEvent, Size, Style, UniformListScrollHandle, Window, quad, + Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, CursorStyle, + Edges, Element, 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 { @@ -22,6 +22,12 @@ enum ThumbState { Dragging(Pixels), } +impl ThumbState { + fn is_dragging(&self) -> bool { + matches!(*self, ThumbState::Dragging(_)) + } +} + impl ScrollableHandle for UniformListScrollHandle { fn content_size(&self) -> Size { self.0.borrow().base_handle.content_size() @@ -236,7 +242,7 @@ impl Element for Scrollbar { _inspector_id: Option<&gpui::InspectorElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, - _prepaint: &mut Self::PrepaintState, + hitbox: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { @@ -244,7 +250,8 @@ impl Element for Scrollbar { window.with_content_mask(Some(ContentMask { bounds }), |window| { let axis = self.kind; let colors = cx.theme().colors(); - let thumb_base_color = match self.state.thumb_state.get() { + let thumb_state = self.state.thumb_state.get(); + let thumb_base_color = match thumb_state { ThumbState::Dragging(_) => colors.scrollbar_thumb_active_background, ThumbState::Hover => colors.scrollbar_thumb_hover_background, ThumbState::Inactive => colors.scrollbar_thumb_background, @@ -285,6 +292,12 @@ impl Element for Scrollbar { BorderStyle::default(), )); + if thumb_state.is_dragging() { + window.set_window_cursor_style(CursorStyle::Arrow); + } else { + window.set_cursor_style(CursorStyle::Arrow, hitbox); + } + let scroll = self.state.scroll_handle.clone(); enum ScrollbarMouseEvent { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 7e5e77f97b..4565cef347 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1281,7 +1281,17 @@ mod element { Axis::Vertical => CursorStyle::ResizeRow, Axis::Horizontal => CursorStyle::ResizeColumn, }; - window.set_cursor_style(cursor_style, Some(&handle.hitbox)); + + if layout + .dragged_handle + .borrow() + .is_some_and(|dragged_ix| dragged_ix == ix) + { + window.set_window_cursor_style(cursor_style); + } else { + window.set_cursor_style(cursor_style, &handle.hitbox); + } + window.paint_quad(gpui::fill( handle.divider_bounds, cx.theme().colors().pane_group_border, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e2b84ef1b9..adf16c0910 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7411,7 +7411,7 @@ pub fn client_side_decorations( CursorStyle::ResizeUpRightDownLeft } }, - Some(&hitbox), + &hitbox, ); }, )