finestra/resources/
cursor.rs

1// Copyright (C) 2024 Tristan Gerritsen <tristan@thewoosh.org>
2// All Rights Reserved.
3
4use std::time::Duration;
5
6use crate::Timer;
7
8/// The cursor is the pointer that the user can move around with the mouse.
9///
10/// ```
11/// # use finestra::{Cursor, SystemCursor};
12///
13/// let curosr = Cursor::system(SystemCursor::IBeam);
14/// ```
15#[derive(Clone, Debug, PartialEq)]
16pub struct Cursor {
17    pub(crate) kind: CursorKind,
18}
19
20impl Cursor {
21    /// Creates a [`Cursor`] object using the cross-platform [`SystemCursor`]
22    /// enumeration.
23    pub fn system(cursor: SystemCursor) -> Self {
24        Self {
25            kind: CursorKind::System(cursor)
26        }
27    }
28
29    /// Creates a [`Cursor`] object using a cursor that might not be available
30    /// on the current platform.
31    pub fn unstable(cursor: UnstableCursor, alternative: SystemCursor) -> Self {
32        Self {
33            kind: CursorKind::Unstable {
34                cursor,
35                alternative,
36            }
37        }
38    }
39
40    /// Show the given timer for a certain duration, starting from this
41    /// function call.
42    pub fn show_for(&self, duration: Duration) {
43        self.push_internal();
44
45        let cursor = self.clone();
46        Timer::delayed_action(duration, move || {
47            cursor.pop_internal();
48        }).schedule_once();
49    }
50}
51
52#[cfg(target_os = "macos")]
53impl Cursor {
54    fn push_internal(&self) {
55        use cacao::appkit::Cursor as CacaoCursor;
56        CacaoCursor::push(self.clone().into());
57    }
58
59    fn pop_internal(&self) {
60        use cacao::appkit::Cursor as CacaoCursor;
61        CacaoCursor::pop();
62    }
63}
64
65#[cfg(not(target_os = "macos"))]
66impl Cursor {
67    fn push_internal(&self) {
68        todo!();
69    }
70
71    fn pop_internal(&self) {
72        todo!();
73    }
74}
75
76impl Default for Cursor {
77    fn default() -> Self {
78        Self::system(SystemCursor::Default)
79    }
80}
81
82#[derive(Clone, Debug, PartialEq)]
83pub(crate) enum CursorKind {
84    Unstable {
85        cursor: UnstableCursor,
86        alternative: SystemCursor,
87    },
88    System(SystemCursor),
89}
90
91/// These cursor are guaranteed to be available on all platforms, or appropriate
92/// alternatives that carry the meaning of the cursor exist.
93///
94/// ## Notes:
95/// * On macOS, there is no way to set the wait (spinning) cursor.
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub enum SystemCursor {
98    /// The platform-dependent default cursor (most likely the
99    /// [`SystemCursor::Arrow`]).
100    #[default]
101    Default,
102
103    /// An arrow-like cursor, which is most likely the default cursor.
104    ///
105    /// * macOS: [arrowCursor](https://developer.apple.com/documentation/appkit/nscursor/1527160-arrowcursor)
106    /// * Windows: [IDC_ARROW](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
107    Arrow,
108
109    /// Used for indicating insertion points/high precision operations.
110    ///
111    /// * macOS: [crosshairCursor](https://developer.apple.com/documentation/appkit/nscursor/1525359-crosshaircursor)
112    /// * Windows: [IDC_CROSS](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
113    CrossHair,
114
115    /// Used for indicating that the action is not allowed, e.g. drag-and-drop,
116    /// hover over a disabled element.
117    ///
118    /// * macOS: [operationNotAllowedCursor](https://developer.apple.com/documentation/appkit/nscursor/1525180-operationnotallowedcursor)
119    /// * Windows: [IDC_NO](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
120    NotAllowed,
121
122    /// The text selection cursor.
123    ///
124    /// * macOS: [IBeamCursor](https://developer.apple.com/documentation/appkit/nscursor/1526109-ibeamcursor)
125    /// * Windows: [IDC_IBEAM](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
126    IBeam,
127
128    /// The (open) hand cursor, often used for clickable components, like links
129    /// and buttons.
130    ///
131    /// * macOS: [pointingHandCursor](https://developer.apple.com/documentation/appkit/nscursor/1531896-pointinghandcursor)
132    /// * Windows: [IDC_HAND](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
133    Hand,
134
135    /// Used for resizing the window or view from the bottom of the object.
136    ///
137    /// * macOS: [resizeDownCursor](https://developer.apple.com/documentation/appkit/nscursor/1531340-resizedowncursor)
138    /// * Windows: [IDC_SIZENS](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
139    ResizeDown,
140
141    /// Used for resizing the window or view from the left side of the object.
142    ///
143    /// * macOS: [resizeLeftCursor](https://developer.apple.com/documentation/appkit/nscursor/1535416-resizeleftcursor)
144    /// * Windows: [IDC_SIZEWE](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
145    ResizeLeft,
146
147    /// Used for resizing the window or view from the right side of the object.
148    ///
149    /// * macOS: [resizeRightCursor](https://developer.apple.com/documentation/appkit/nscursor/1526314-resizerightcursor)
150    /// * Windows: [IDC_SIZEWE](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
151    ResizeRight,
152
153    /// Used for resizing the window or view from the top of the object.
154    ///
155    /// * macOS: [resizeUpCursor](https://developer.apple.com/documentation/appkit/nscursor/1532226-resizeupcursor)
156    /// * Windows: [IDC_SIZENS](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
157    ResizeUp,
158}
159
160/// The following cursors are available, that might be most appropriate for some
161/// platforms, but don't have proper alternatives on other platforms.
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub enum UnstableCursor {
164    /// A spinning cursor indicating that the application is busy processing,
165    /// and cannot accept interactions from the user.
166    ///
167    /// * macOS: Not available.
168    /// * Windows: [IDC_WAIT](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
169    Busy,
170
171    /// A spinning cursor indicating that the application is busy processing,
172    /// but can accept interactions from the user (where
173    /// [`UnstableCursor::Busy`] can't).
174    ///
175    /// * macOS: Not available.
176    /// * Windows: [IDC_APPSTARTING](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
177    BusyInBackground,
178
179    /// A cursor that indicates that the item will disappear if the action is
180    /// continued.
181    ///
182    /// * macOS: [disappearingItemCursor](https://developer.apple.com/documentation/appkit/nscursor/1534280-disappearingitemcursor?language=objc)
183    /// * Windows: Not available.
184    DisappearingItem,
185
186    /// A cursor that indicates that the current context allows help from the
187    /// application for the action.
188    ///
189    /// * macOS: Not available.
190    /// * Windows: [IDC_HELP](https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors)
191    Help,
192}
193
194impl From<SystemCursor> for Cursor {
195    fn from(value: SystemCursor) -> Self {
196        Self::system(value)
197    }
198}
199
200#[cfg(target_os = "macos")]
201impl From<SystemCursor> for cacao::appkit::CursorType {
202    fn from(value: SystemCursor) -> Self {
203        match value {
204            SystemCursor::Default => Self::Arrow,
205
206            SystemCursor::Arrow => Self::Arrow,
207            SystemCursor::CrossHair => Self::Crosshair,
208            SystemCursor::Hand => Self::OpenHand,
209            SystemCursor::IBeam => Self::IBeam,
210            SystemCursor::NotAllowed => Self::OperationNotAllowed,
211            SystemCursor::ResizeDown => Self::ResizeDown,
212            SystemCursor::ResizeLeft => Self::ResizeLeft,
213            SystemCursor::ResizeRight => Self::ResizeRight,
214            SystemCursor::ResizeUp => Self::ResizeUp,
215        }
216    }
217}
218
219#[cfg(target_os = "macos")]
220impl From<UnstableCursor> for Option<cacao::appkit::CursorType> {
221    fn from(value: UnstableCursor) -> Self {
222        use cacao::appkit::CursorType as Type;
223        match value {
224            UnstableCursor::Busy => None,
225            UnstableCursor::BusyInBackground => None,
226            UnstableCursor::DisappearingItem => Some(Type::DisappearingItem),
227            UnstableCursor::Help => None,
228        }
229    }
230}
231
232#[cfg(target_os = "macos")]
233impl From<Cursor> for cacao::appkit::CursorType {
234    fn from(value: Cursor) -> Self {
235        match value.kind {
236            CursorKind::Unstable { cursor, alternative } => {
237                let unstable: Option<_> = cursor.into();
238                unstable.unwrap_or_else(|| alternative.into())
239            }
240            CursorKind::System(system) => system.into(),
241        }
242    }
243}