Add PointingHand on tabs

Conrad Irwin created

Change summary

crates/gpui2/src/elements/div.rs |  7 +++++++
crates/gpui2/src/style.rs        | 10 +++++++---
crates/gpui2/src/styled.rs       | 34 +++++++++++++++++++++++++++++++---
crates/gpui2/src/window.rs       | 31 ++++++++++++++++++++++---------
crates/workspace2/src/pane.rs    |  6 ++++--
crates/zed2/src/main.rs          |  1 -
6 files changed, 71 insertions(+), 18 deletions(-)

Detailed changes

crates/gpui2/src/elements/div.rs 🔗

@@ -254,6 +254,13 @@ where
                 return;
             }
 
+            if let Some(mouse_cursor) = style.mouse_cursor {
+                let hovered = bounds.contains_point(&cx.mouse_position());
+                if hovered {
+                    cx.set_cursor_style(mouse_cursor);
+                }
+            }
+
             if let Some(group) = this.group.clone() {
                 GroupBounds::push(group, bounds, cx);
             }

crates/gpui2/src/style.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
     black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
-    Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
-    FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, Rgba,
-    SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
+    Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
+    FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems,
+    Result, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
 };
 use refineable::{Cascade, Refineable};
 use smallvec::SmallVec;
@@ -101,6 +101,9 @@ pub struct Style {
     /// TEXT
     pub text: TextStyleRefinement,
 
+    /// The mouse cursor style shown when the mouse pointer is over an element.
+    pub mouse_cursor: Option<CursorStyle>,
+
     pub z_index: Option<u32>,
 }
 
@@ -339,6 +342,7 @@ impl Default for Style {
             corner_radii: Corners::default(),
             box_shadow: Default::default(),
             text: TextStyleRefinement::default(),
+            mouse_cursor: None,
             z_index: None,
         }
     }

crates/gpui2/src/styled.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    self as gpui2, hsla, point, px, relative, rems, AlignItems, DefiniteLength, Display, Fill,
-    FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString, StyleRefinement,
-    Visibility,
+    self as gpui2, hsla, point, px, relative, rems, AlignItems, CursorStyle, DefiniteLength,
+    Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString,
+    StyleRefinement, Visibility,
 };
 use crate::{BoxShadow, TextStyleRefinement};
 use smallvec::smallvec;
@@ -81,6 +81,34 @@ pub trait Styled {
         self
     }
 
+    fn cursor(mut self, cursor: CursorStyle) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().mouse_cursor = Some(cursor);
+        self
+    }
+
+    /// Sets the cursor style when hovering an element to `default`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_default(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().mouse_cursor = Some(CursorStyle::Arrow);
+        self
+    }
+
+    /// Sets the cursor style when hovering an element to `pointer`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_pointer(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().mouse_cursor = Some(CursorStyle::PointingHand);
+        self
+    }
+
     /// Sets the flex direction of the element to `column`.
     /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
     fn flex_col(mut self) -> Self

crates/gpui2/src/window.rs 🔗

@@ -1,14 +1,14 @@
 use crate::{
     px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
-    Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
-    Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
-    Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
-    Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
-    MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point,
-    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
-    Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
-    WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
+    Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
+    GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
+    KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
+    PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
+    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
+    Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
+    VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Result};
 use collections::HashMap;
@@ -190,6 +190,7 @@ pub struct Window {
     pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
+    requested_cursor_style: Option<CursorStyle>,
     scale_factor: f32,
     bounds: WindowBounds,
     bounds_observers: SubscriberSet<(), AnyObserver>,
@@ -283,6 +284,7 @@ impl Window {
             focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             default_prevented: true,
             mouse_position,
+            requested_cursor_style: None,
             scale_factor,
             bounds,
             bounds_observers: SubscriberSet::new(),
@@ -669,6 +671,10 @@ impl<'a> WindowContext<'a> {
         self.window.mouse_position
     }
 
+    pub fn set_cursor_style(&mut self, style: CursorStyle) {
+        self.window.requested_cursor_style = Some(style)
+    }
+
     /// Called during painting to invoke the given closure in a new stacking context. The given
     /// z-index is interpreted relative to the previous call to `stack`.
     pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
@@ -987,6 +993,13 @@ impl<'a> WindowContext<'a> {
         let scene = self.window.scene_builder.build();
 
         self.window.platform_window.draw(scene);
+        let cursor_style = self
+            .window
+            .requested_cursor_style
+            .take()
+            .unwrap_or(CursorStyle::Arrow);
+        self.platform.set_cursor_style(cursor_style);
+
         self.window.dirty = false;
     }
 

crates/workspace2/src/pane.rs 🔗

@@ -9,8 +9,9 @@ use crate::{
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, Model,
-    PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+    AppContext, AsyncWindowContext, Component, CursorStyle, Div, EntityId, EventEmitter,
+    FocusHandle, Model, PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView,
+    WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -1397,6 +1398,7 @@ impl Pane {
         div()
             .group("")
             .id(item.id())
+            .cursor_pointer()
             // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
             // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
             // .on_drop(|_view, state: View<DraggedTab>, cx| {

crates/zed2/src/main.rs 🔗

@@ -208,7 +208,6 @@ fn main() {
         if stdout_is_a_pty() {
             cx.activate(true);
             let urls = collect_url_args();
-            dbg!(&urls);
             if !urls.is_empty() {
                 listener.open_urls(urls)
             }