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}