Add additional cursors to gpui2

Nate Butler and Marshall Bowers created

Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>

Change summary

crates/gpui2/src/platform.rs              |  18 +++
crates/gpui2/src/platform/mac/platform.rs |  29 +++++-
crates/gpui2/src/styled.rs                | 119 +++++++++++++++++++++++++
crates/storybook2/src/stories.rs          |   2 
crates/storybook2/src/stories/cursor.rs   | 112 +++++++++++++++++++++++
crates/storybook2/src/story_selector.rs   |   2 
6 files changed, 275 insertions(+), 7 deletions(-)

Detailed changes

crates/gpui2/src/platform.rs 🔗

@@ -472,13 +472,27 @@ pub enum PromptLevel {
     Critical,
 }
 
+/// Change the style of the cursor (pointer)
 #[derive(Copy, Clone, Debug)]
 pub enum CursorStyle {
     Arrow,
+    IBeam,
+    Crosshair,
+    ClosedHand,
+    OpenHand,
+    PointingHand,
+    ResizeLeft,
+    ResizeRight,
     ResizeLeftRight,
+    ResizeUp,
+    ResizeDown,
     ResizeUpDown,
-    PointingHand,
-    IBeam,
+    DisappearingItem,
+    IBeamCursorForVerticalLayout,
+    OperationNotAllowed,
+    DragLink,
+    DragCopy,
+    ContextualMenu,
 }
 
 impl Default for CursorStyle {

crates/gpui2/src/platform/mac/platform.rs 🔗

@@ -724,16 +724,35 @@ impl Platform for MacPlatform {
         }
     }
 
+    /// Match cusor style to to one of the styles available
+    /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
     fn set_cursor_style(&self, style: CursorStyle) {
         unsafe {
             let new_cursor: id = match style {
                 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
-                CursorStyle::ResizeLeftRight => {
-                    msg_send![class!(NSCursor), resizeLeftRightCursor]
-                }
-                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
-                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
                 CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
+                CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
+                CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
+                CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
+                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
+                CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
+                CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
+                CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
+                CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
+                CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
+                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
+                CursorStyle::DisappearingItem => {
+                    msg_send![class!(NSCursor), disappearingItemCursor]
+                }
+                CursorStyle::IBeamCursorForVerticalLayout => {
+                    msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
+                }
+                CursorStyle::OperationNotAllowed => {
+                    msg_send![class!(NSCursor), operationNotAllowedCursor]
+                }
+                CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
+                CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
+                CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
             };
 
             let old_cursor: id = msg_send![class!(NSCursor), currentCursor];

crates/gpui2/src/styled.rs 🔗

@@ -101,6 +101,125 @@ pub trait Styled: Sized {
         self
     }
 
+    /// Sets cursor style when hovering over an element to `text`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_text(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::IBeam);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `move`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_move(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `not-allowed`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_not_allowed(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `context-menu`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_context_menu(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ContextualMenu);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `crosshair`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_crosshair(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::Crosshair);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `vertical-text`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_vertical_text(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::IBeamCursorForVerticalLayout);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `alias`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_alias(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::DragLink);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `copy`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_copy(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::DragCopy);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `no-drop`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_no_drop(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `grab`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_grab(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::OpenHand);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `grabbing`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_grabbing(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `col-resize`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_col_resize(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ResizeLeftRight);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `row-resize`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_row_resize(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ResizeUpDown);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `n-resize`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_n_resize(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ResizeUp);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `e-resize`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_e_resize(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ResizeRight);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `s-resize`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_s_resize(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ResizeDown);
+        self
+    }
+
+    /// Sets cursor style when hovering over an element to `w-resize`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_w_resize(mut self) -> Self {
+        self.style().mouse_cursor = Some(CursorStyle::ResizeLeft);
+        self
+    }
+
     /// Sets the whitespace of the element to `normal`.
     /// [Docs](https://tailwindcss.com/docs/whitespace#normal)
     fn whitespace_normal(mut self) -> Self {

crates/storybook2/src/stories.rs 🔗

@@ -1,4 +1,5 @@
 mod auto_height_editor;
+mod cursor;
 mod focus;
 mod kitchen_sink;
 mod picker;
@@ -7,6 +8,7 @@ mod text;
 mod z_index;
 
 pub use auto_height_editor::*;
+pub use cursor::*;
 pub use focus::*;
 pub use kitchen_sink::*;
 pub use picker::*;

crates/storybook2/src/stories/cursor.rs 🔗

@@ -0,0 +1,112 @@
+use gpui::{Div, Render, Stateful};
+use story::Story;
+use ui::prelude::*;
+
+pub struct CursorStory;
+
+impl Render for CursorStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        let all_cursors: [(&str, Box<dyn Fn(Stateful<Div>) -> Stateful<Div>>); 19] = [
+            (
+                "cursor_default",
+                Box::new(|el: Stateful<Div>| el.cursor_default()),
+            ),
+            (
+                "cursor_pointer",
+                Box::new(|el: Stateful<Div>| el.cursor_pointer()),
+            ),
+            (
+                "cursor_text",
+                Box::new(|el: Stateful<Div>| el.cursor_text()),
+            ),
+            (
+                "cursor_move",
+                Box::new(|el: Stateful<Div>| el.cursor_move()),
+            ),
+            (
+                "cursor_not_allowed",
+                Box::new(|el: Stateful<Div>| el.cursor_not_allowed()),
+            ),
+            (
+                "cursor_context_menu",
+                Box::new(|el: Stateful<Div>| el.cursor_context_menu()),
+            ),
+            (
+                "cursor_crosshair",
+                Box::new(|el: Stateful<Div>| el.cursor_crosshair()),
+            ),
+            (
+                "cursor_vertical_text",
+                Box::new(|el: Stateful<Div>| el.cursor_vertical_text()),
+            ),
+            (
+                "cursor_alias",
+                Box::new(|el: Stateful<Div>| el.cursor_alias()),
+            ),
+            (
+                "cursor_copy",
+                Box::new(|el: Stateful<Div>| el.cursor_copy()),
+            ),
+            (
+                "cursor_no_drop",
+                Box::new(|el: Stateful<Div>| el.cursor_no_drop()),
+            ),
+            (
+                "cursor_grab",
+                Box::new(|el: Stateful<Div>| el.cursor_grab()),
+            ),
+            (
+                "cursor_grabbing",
+                Box::new(|el: Stateful<Div>| el.cursor_grabbing()),
+            ),
+            (
+                "cursor_col_resize",
+                Box::new(|el: Stateful<Div>| el.cursor_col_resize()),
+            ),
+            (
+                "cursor_row_resize",
+                Box::new(|el: Stateful<Div>| el.cursor_row_resize()),
+            ),
+            (
+                "cursor_n_resize",
+                Box::new(|el: Stateful<Div>| el.cursor_n_resize()),
+            ),
+            (
+                "cursor_e_resize",
+                Box::new(|el: Stateful<Div>| el.cursor_e_resize()),
+            ),
+            (
+                "cursor_s_resize",
+                Box::new(|el: Stateful<Div>| el.cursor_s_resize()),
+            ),
+            (
+                "cursor_w_resize",
+                Box::new(|el: Stateful<Div>| el.cursor_w_resize()),
+            ),
+        ];
+
+        Story::container()
+            .flex()
+            .gap_1()
+            .child(Story::title("cursor"))
+            .children(all_cursors.map(|(name, apply_cursor)| {
+                div().gap_1().flex().text_color(gpui::white()).child(
+                    div()
+                        .flex()
+                        .items_center()
+                        .justify_center()
+                        .id(name)
+                        .map(apply_cursor)
+                        .w_64()
+                        .h_8()
+                        .bg(gpui::red())
+                        .hover(|style| style.bg(gpui::blue()))
+                        .active(|style| style.bg(gpui::green()))
+                        .text_sm()
+                        .child(Story::label(name)),
+                )
+            }))
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -17,6 +17,7 @@ pub enum ComponentStory {
     Button,
     Checkbox,
     ContextMenu,
+    Cursor,
     Disclosure,
     Focus,
     Icon,
@@ -40,6 +41,7 @@ impl ComponentStory {
             Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
             Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
             Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
+            Self::Cursor => cx.build_view(|_| crate::stories::CursorStory).into(),
             Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(),
             Self::Focus => FocusStory::view(cx).into(),
             Self::Icon => cx.build_view(|_| ui::IconStory).into(),