Skip to main content

inset_services/
mouse_cursor.rs

1//! Flutter counterpart: `services/mouse_cursor.dart`.
2
3use std::any::Any;
4use std::collections::HashMap;
5use std::fmt;
6use std::rc::Rc;
7
8use inset_embedder::{Platform, SystemMouseCursorKind, ViewId};
9use inset_gestures::PointerEvent;
10
11/// A shared reference to an immutable [`MouseCursor`] (a Dart object reference).
12pub type MouseCursorRef = Rc<dyn MouseCursor>;
13
14/// Maintains the state of mouse cursors and manages how cursors are searched
15/// for.
16///
17/// This is typically created as a global singleton and owned by `MouseTracker`.
18pub struct MouseCursorManager {
19    /// The mouse cursor to use if all cursor candidates choose to defer.
20    ///
21    /// See also:
22    ///
23    ///  * `MouseCursor.defer`, the mouse cursor object to use to defer.
24    pub fallback_mouse_cursor: MouseCursorRef,
25    last_session: HashMap<i64, Box<dyn MouseCursorSession>>,
26}
27
28impl MouseCursorManager {
29    /// Create a [`MouseCursorManager`] by specifying the fallback cursor.
30    ///
31    /// The `fallback_mouse_cursor` must not be `MouseCursor.defer`.
32    pub fn new(fallback_mouse_cursor: MouseCursorRef) -> MouseCursorManager {
33        debug_assert!(!fallback_mouse_cursor.is_defer());
34        MouseCursorManager {
35            fallback_mouse_cursor,
36            last_session: HashMap::new(),
37        }
38    }
39
40    /// Returns the active mouse cursor of a device.
41    ///
42    /// The return value is the last [`MouseCursor`] activated onto this device,
43    /// even if the activation failed.
44    ///
45    /// This function is only active in debug mode: in release it returns `None`.
46    pub fn debug_device_active_cursor(&self, device: i64) -> Option<MouseCursorRef> {
47        if !cfg!(debug_assertions) {
48            return None;
49        }
50        self.last_session
51            .get(&device)
52            .map(|session| session.cursor())
53    }
54
55    /// Handles the changes that cause a pointer device to have a new list of
56    /// mouse cursor candidates.
57    ///
58    /// This change can be caused by a pointer event, in which case
59    /// `triggering_event` should not be `None`, or by other changes, such as when a
60    /// widget has moved under a still mouse, which is detected after the current
61    /// frame is complete. In either case, `cursor_candidates` should be the list
62    /// of cursors at the location of the mouse in hit-test order.
63    ///
64    /// `view` is the one the device is on, from its latest event: the cursor is that view's
65    /// host's to show, and the session asks it.
66    pub fn handle_device_cursor_update(
67        &mut self,
68        platform: &dyn Platform,
69        view: ViewId,
70        device: i64,
71        triggering_event: Option<&PointerEvent>,
72        cursor_candidates: impl IntoIterator<Item = MouseCursorRef>,
73    ) {
74        if let Some(PointerEvent::Removed(_)) = triggering_event {
75            self.last_session.remove(&device);
76            return;
77        }
78        let next_cursor = first_non_deferred(cursor_candidates)
79            .unwrap_or_else(|| Rc::clone(&self.fallback_mouse_cursor));
80        debug_assert!(!next_cursor.is_defer());
81        if let Some(last_session) = self.last_session.get(&device)
82            && *last_session.cursor() == *next_cursor
83        {
84            return;
85        }
86        let mut next_session = next_cursor.create_session(device);
87        if let Some(mut last_session) = self.last_session.remove(&device) {
88            last_session.dispose();
89        }
90        next_session.activate(platform, view);
91        self.last_session.insert(device, next_session);
92    }
93}
94
95/// Manages the duration that a pointing device should display a specific mouse
96/// cursor.
97///
98/// While [`MouseCursor`] classes describe the kind of cursors, [`MouseCursorSession`]
99/// classes represent a continuous use of the cursor on a pointing device. The
100/// [`MouseCursorSession`] classes can be stateful. For example, a cursor that
101/// needs to load resources might want to try a fallback cursor first, and switch
102/// to the target cursor when the resources are loaded.
103///
104/// A [`MouseCursorSession`] has the following lifecycle:
105///
106///  * When a pointing device should start displaying a cursor, `MouseTracker`
107///    creates a session by calling [`MouseCursor::create_session`] on the target
108///    cursor, and stores it in a table associated with the device.
109///  * `MouseTracker` then immediately calls the session's [`activate`], where
110///    the session should fetch resources and make system calls.
111///  * When the pointing device should start displaying another cursor,
112///    `MouseTracker` calls [`dispose`] on this session. After [`dispose`], this
113///    session will no longer be used in the future.
114///
115/// [`activate`]: MouseCursorSession::activate
116/// [`dispose`]: MouseCursorSession::dispose
117pub trait MouseCursorSession {
118    /// The cursor that created this session.
119    fn cursor(&self) -> MouseCursorRef;
120
121    /// The device ID of the pointing device.
122    fn device(&self) -> i64;
123
124    /// Override this method to do the work of changing the cursor of the device.
125    ///
126    /// Called right after this session is created.
127    ///
128    /// This method has full control over the cursor until the [`dispose`] call,
129    /// and can make system calls to change the pointer cursor as many times as
130    /// necessary (usually through the system cursor request of the host of `view`,
131    /// the one the device is on, which `platform` finds; a session that changes the
132    /// cursor later keeps it).
133    ///
134    /// [`dispose`]: MouseCursorSession::dispose
135    fn activate(&mut self, platform: &dyn Platform, view: ViewId);
136
137    /// Called when device stops displaying the cursor.
138    ///
139    /// After this call, this session instance will no longer be used in the
140    /// future.
141    ///
142    /// When implementing this method in subclasses, you should release resources
143    /// and prevent [`activate`] from causing side effects after disposal.
144    ///
145    /// [`activate`]: MouseCursorSession::activate
146    fn dispose(&mut self);
147}
148
149/// An interface for mouse cursor definitions.
150///
151/// A mouse cursor is a graphical image on the screen that echoes the movement
152/// of a pointing device, such as a mouse or a stylus. A [`MouseCursor`] object
153/// defines a kind of mouse cursor, such as an arrow, a pointing hand, or an
154/// I-beam.
155///
156/// During the painting phase, [`MouseCursor`] objects are assigned to regions on
157/// the screen via annotations. Later during a device update (e.g. when a mouse
158/// moves), `MouseTracker` finds the _active cursor_, the top-most region
159/// associated with the position of each mouse device, and activates the cursor
160/// as necessary.
161///
162/// ## Cursor classes
163///
164/// A [`SystemMouseCursor`] is a cursor that is natively supported by the
165/// platform that the program is running on. All supported system mouse cursors
166/// are enumerated in [`SystemMouseCursors`].
167///
168/// ## Using cursors
169///
170/// A [`MouseCursor`] object is used by being assigned to a `MouseRegion` or
171/// another widget that exposes the `MouseRegion` API, such as
172/// `InkResponse.mouseCursor`.
173///
174/// ## Related classes
175///
176/// [`MouseCursorSession`] represents the duration when a pointing device
177/// displays a cursor, and defines the states and behaviors of the cursor.
178///
179/// [`MouseCursorManager`] is a mixin that manages the states of mouse cursors,
180/// such as `MouseTracker`.
181///
182/// Values are shared as [`MouseCursorRef`]. Dart's `MouseCursor.defer` and
183/// `MouseCursor.uncontrolled` are `<dyn MouseCursor>::defer()` and
184/// `<dyn MouseCursor>::uncontrolled()`.
185pub trait MouseCursor: fmt::Debug {
186    /// Associate a pointing device to this cursor.
187    ///
188    /// A mouse cursor class usually has a corresponding [`MouseCursorSession`]
189    /// class, and instantiates such class in this method.
190    ///
191    /// This method is called each time a pointing device starts displaying this
192    /// cursor. A given cursor can be displayed by multiple devices at the same
193    /// time, in which case this method will be called separately for each device.
194    fn create_session(&self, device: i64) -> Box<dyn MouseCursorSession>;
195
196    /// A very short description of the mouse cursor.
197    ///
198    /// The `debug_description` should be a few words that can describe this cursor
199    /// to make debug information more readable. It is returned as the `Debug`
200    /// output when the diagnostic level is at or above info.
201    fn debug_description(&self) -> String;
202
203    /// Downcast support for Dart's `==` between cursor classes.
204    fn as_any(&self) -> &dyn Any;
205
206    /// Dart's `==`. Identity unless a class overrides it, as [`SystemMouseCursor`] does.
207    fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
208        std::ptr::addr_eq(self as *const Self, other as *const dyn MouseCursor)
209    }
210
211    /// Whether this is `MouseCursor.defer`.
212    fn is_defer(&self) -> bool {
213        self.as_any().is::<DeferringMouseCursor>()
214    }
215}
216
217impl dyn MouseCursor {
218    /// A special class that indicates that the region with this cursor defers the
219    /// choice of cursor to the next region behind it.
220    ///
221    /// When an event occurs, `MouseTracker` will update each pointer's cursor by
222    /// finding the list of regions that contain the pointer's location, from front
223    /// to back in hit-test order. The pointer's cursor will be the first cursor
224    /// that is not deferred.
225    pub fn defer() -> MouseCursorRef {
226        Rc::new(DeferringMouseCursor)
227    }
228
229    /// A special value that doesn't change cursor by itself, but make a region
230    /// that blocks other regions behind it from changing the cursor.
231    ///
232    /// When a pointer enters a region with a cursor of `uncontrolled`, the pointer
233    /// retains its previous cursor and keeps so until it moves out of the region.
234    /// Technically, this region absorb the mouse cursor hit test without changing
235    /// the pointer's cursor.
236    ///
237    /// This is useful in a region that displays a platform view, which let the
238    /// operating system handle pointer events and change cursors accordingly. To
239    /// achieve this, the region's cursor must not be any Flutter cursor, since
240    /// that might overwrite the system request upon pointer entering; the cursor
241    /// must not be `None` either, since that allows the widgets behind the region to
242    /// change cursors.
243    pub fn uncontrolled() -> MouseCursorRef {
244        Rc::new(NoopMouseCursor)
245    }
246}
247
248impl PartialEq for dyn MouseCursor {
249    fn eq(&self, other: &dyn MouseCursor) -> bool {
250        self.eq_cursor(other)
251    }
252}
253
254/// The cursor behind `MouseCursor.defer`. Every instance is equal.
255#[derive(Debug)]
256pub struct DeferringMouseCursor;
257
258impl MouseCursor for DeferringMouseCursor {
259    fn create_session(&self, _device: i64) -> Box<dyn MouseCursorSession> {
260        panic!("DeferringMouseCursor can not create a session");
261    }
262
263    fn debug_description(&self) -> String {
264        "defer".to_owned()
265    }
266
267    fn as_any(&self) -> &dyn Any {
268        self
269    }
270
271    fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
272        other.is_defer()
273    }
274}
275
276/// Returns the first cursor from the given cursors that is not a
277/// [`DeferringMouseCursor`], or `None` if none is found.
278fn first_non_deferred(cursors: impl IntoIterator<Item = MouseCursorRef>) -> Option<MouseCursorRef> {
279    cursors.into_iter().find(|cursor| !cursor.is_defer())
280}
281
282struct NoopMouseCursorSession {
283    device: i64,
284}
285
286impl MouseCursorSession for NoopMouseCursorSession {
287    fn cursor(&self) -> MouseCursorRef {
288        <dyn MouseCursor>::uncontrolled()
289    }
290
291    fn device(&self) -> i64 {
292        self.device
293    }
294
295    fn activate(&mut self, _platform: &dyn Platform, _view: ViewId) {
296        // Nothing.
297    }
298
299    fn dispose(&mut self) {
300        // Nothing.
301    }
302}
303
304/// The cursor behind `MouseCursor.uncontrolled`. Every instance is equal.
305#[derive(Debug)]
306pub struct NoopMouseCursor;
307
308impl MouseCursor for NoopMouseCursor {
309    fn create_session(&self, device: i64) -> Box<dyn MouseCursorSession> {
310        Box::new(NoopMouseCursorSession { device })
311    }
312
313    fn debug_description(&self) -> String {
314        "uncontrolled".to_owned()
315    }
316
317    fn as_any(&self) -> &dyn Any {
318        self
319    }
320
321    fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
322        other.as_any().is::<NoopMouseCursor>()
323    }
324}
325
326struct SystemMouseCursorSession {
327    cursor: SystemMouseCursor,
328    device: i64,
329}
330
331impl MouseCursorSession for SystemMouseCursorSession {
332    fn cursor(&self) -> MouseCursorRef {
333        Rc::new(self.cursor)
334    }
335
336    fn device(&self) -> i64 {
337        self.device
338    }
339
340    fn activate(&mut self, platform: &dyn Platform, view: ViewId) {
341        if let Some(view) = platform.view(view) {
342            view.host()
343                .activate_system_cursor(self.device, self.cursor.kind());
344        }
345    }
346
347    fn dispose(&mut self) {
348        // Nothing.
349    }
350}
351
352/// A mouse cursor that is natively supported on the platform that the
353/// application is running on.
354///
355/// System cursors can be used without external resources, and their appearances
356/// match the experience of native apps. Examples of system cursors are a
357/// pointing arrow, a pointing hand, a double arrow for resizing, or a text
358/// I-beam, etc.
359///
360/// An instance of [`SystemMouseCursor`] refers to one cursor from each platform
361/// that represents the same concept, such as being text, being clickable, or
362/// being a forbidden operation. Since the set of system cursors supported by each
363/// platform varies, multiple instances can correspond to the same system cursor.
364///
365/// Each cursor is noted with its corresponding native cursors on each platform:
366///
367///  * Android: API name in Java
368///  * Web: CSS cursor
369///  * Windows: Win32 API
370///  * Linux: GDK, `gdk_cursor_new_from_name`
371///  * macOS: API name in ObjectiveC
372///
373/// If the platform that the application is running on is not listed for a
374/// cursor, using this cursor falls back to [`SystemMouseCursors::BASIC`].
375///
376/// [`SystemMouseCursors`] enumerates the complete set of system cursors supported
377/// by Flutter, which are hard-coded in the engine. Therefore, manually
378/// instantiating this class is not supported.
379#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
380pub struct SystemMouseCursor {
381    kind: SystemMouseCursorKind,
382}
383
384impl SystemMouseCursor {
385    const fn new(kind: SystemMouseCursorKind) -> SystemMouseCursor {
386        SystemMouseCursor { kind }
387    }
388
389    /// The kind of the cursor.
390    ///
391    /// The interpretation of `kind` is platform-dependent.
392    pub fn kind(&self) -> SystemMouseCursorKind {
393        self.kind
394    }
395}
396
397impl MouseCursor for SystemMouseCursor {
398    fn create_session(&self, device: i64) -> Box<dyn MouseCursorSession> {
399        Box::new(SystemMouseCursorSession {
400            cursor: *self,
401            device,
402        })
403    }
404
405    fn debug_description(&self) -> String {
406        format!("SystemMouseCursor({:?})", self.kind)
407    }
408
409    fn as_any(&self) -> &dyn Any {
410        self
411    }
412
413    fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
414        other
415            .as_any()
416            .downcast_ref::<SystemMouseCursor>()
417            .is_some_and(|other| other.kind == self.kind)
418    }
419}
420
421impl From<SystemMouseCursor> for MouseCursorRef {
422    fn from(cursor: SystemMouseCursor) -> MouseCursorRef {
423        Rc::new(cursor)
424    }
425}
426
427/// A collection of system [`MouseCursor`]s.
428///
429/// System cursors are standard mouse cursors that are provided by the current
430/// platform. They don't require external resources.
431///
432/// [`SystemMouseCursors`] is a superset of the system cursors of every platform
433/// that Flutter supports, therefore some of these objects might map to the same
434/// result, or fallback to the [`BASIC`](Self::BASIC) arrow. This mapping is defined
435/// by the Flutter engine.
436///
437/// The cursors should be named based on the cursors' use cases instead of their
438/// appearance, because different platforms might (although not commonly) use
439/// different shapes for the same use case.
440pub struct SystemMouseCursors;
441
442impl SystemMouseCursors {
443    /// Hide the cursor.
444    ///
445    /// Any cursor other than [`NONE`](Self::NONE) or `MouseCursor.uncontrolled`
446    /// unhides the cursor.
447    pub const NONE: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::None);
448
449    /// The platform-dependent basic cursor.
450    ///
451    /// Typically the shape of an arrow.
452    pub const BASIC: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Basic);
453
454    /// A cursor that emphasizes an element being clickable, such as a hyperlink.
455    ///
456    /// Typically the shape of a pointing hand.
457    pub const CLICK: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Click);
458
459    /// A cursor indicating an operation that will not be carried out.
460    ///
461    /// Typically the shape of a circle with a diagonal line. May fall back to
462    /// [`NO_DROP`](Self::NO_DROP).
463    pub const FORBIDDEN: SystemMouseCursor =
464        SystemMouseCursor::new(SystemMouseCursorKind::Forbidden);
465
466    /// A cursor indicating the status that the program is busy and therefore
467    /// can not be interacted with.
468    ///
469    /// Typically the shape of an hourglass or a watch.
470    ///
471    /// This cursor is not available as a system cursor on macOS. Although macOS
472    /// displays a "spinning ball" cursor when busy, it's handled by the OS and not
473    /// exposed for applications to choose.
474    pub const WAIT: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Wait);
475
476    /// A cursor indicating the status that the program is busy but can still be
477    /// interacted with.
478    ///
479    /// Typically the shape of an arrow with an hourglass or a watch at the
480    /// corner. Does *not* fall back to [`WAIT`](Self::WAIT) if unavailable.
481    pub const PROGRESS: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Progress);
482
483    /// A cursor indicating somewhere the user can trigger a context menu.
484    ///
485    /// Typically the shape of an arrow with a small menu at the corner.
486    pub const CONTEXT_MENU: SystemMouseCursor =
487        SystemMouseCursor::new(SystemMouseCursorKind::ContextMenu);
488
489    /// A cursor indicating help information.
490    ///
491    /// Typically the shape of a question mark, or an arrow therewith.
492    pub const HELP: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Help);
493
494    /// A cursor indicating selectable text.
495    ///
496    /// Typically the shape of a capital I.
497    pub const TEXT: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Text);
498
499    /// A cursor indicating selectable vertical text.
500    ///
501    /// Typically the shape of a capital I rotated to be horizontal. May fall
502    /// back to [`TEXT`](Self::TEXT).
503    pub const VERTICAL_TEXT: SystemMouseCursor =
504        SystemMouseCursor::new(SystemMouseCursorKind::VerticalText);
505
506    /// A cursor indicating selectable table cells.
507    ///
508    /// Typically the shape of a hollow plus sign.
509    pub const CELL: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Cell);
510
511    /// A cursor indicating precise selection, such as selecting a pixel in a
512    /// bitmap.
513    ///
514    /// Typically the shape of a crosshair.
515    pub const PRECISE: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Precise);
516
517    /// A cursor indicating moving something.
518    ///
519    /// Typically the shape of four-way arrow. May fall back to
520    /// [`ALL_SCROLL`](Self::ALL_SCROLL).
521    pub const MOVE: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Move);
522
523    /// A cursor indicating something that can be dragged.
524    ///
525    /// Typically the shape of an open hand.
526    pub const GRAB: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Grab);
527
528    /// A cursor indicating something that is being dragged.
529    ///
530    /// Typically the shape of a closed hand.
531    pub const GRABBING: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Grabbing);
532
533    /// A cursor indicating somewhere that the current item may not be dropped.
534    ///
535    /// Typically the shape of a hand with a [`FORBIDDEN`](Self::FORBIDDEN) sign at
536    /// the corner. May fall back to [`FORBIDDEN`](Self::FORBIDDEN).
537    pub const NO_DROP: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::NoDrop);
538
539    /// A cursor indicating that the current operation will create an alias of, or
540    /// a shortcut of the item.
541    ///
542    /// Typically the shape of an arrow with a shortcut icon at the corner.
543    pub const ALIAS: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Alias);
544
545    /// A cursor indicating that the current operation will copy the item.
546    ///
547    /// Typically the shape of an arrow with a boxed plus sign at the corner.
548    pub const COPY: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Copy);
549
550    /// A cursor indicating that the current operation will result in the
551    /// disappearance of the item.
552    ///
553    /// Typically the shape of an arrow with a cloud of smoke at the corner.
554    pub const DISAPPEARING: SystemMouseCursor =
555        SystemMouseCursor::new(SystemMouseCursorKind::Disappearing);
556
557    /// A cursor indicating scrolling in any direction.
558    ///
559    /// Typically the shape of a dot surrounded by 4 arrows.
560    pub const ALL_SCROLL: SystemMouseCursor =
561        SystemMouseCursor::new(SystemMouseCursorKind::AllScroll);
562
563    /// A cursor indicating resizing an object bidirectionally from its left or
564    /// right edge.
565    ///
566    /// Typically the shape of a bidirectional arrow pointing left and right.
567    pub const RESIZE_LEFT_RIGHT: SystemMouseCursor =
568        SystemMouseCursor::new(SystemMouseCursorKind::ResizeLeftRight);
569
570    /// A cursor indicating resizing an object bidirectionally from its top or
571    /// bottom edge.
572    ///
573    /// Typically the shape of a bidirectional arrow pointing up and down.
574    pub const RESIZE_UP_DOWN: SystemMouseCursor =
575        SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpDown);
576
577    /// A cursor indicating resizing an object bidirectionally from its top left or
578    /// bottom right corner.
579    ///
580    /// Typically the shape of a bidirectional arrow pointing upper left and lower
581    /// right.
582    pub const RESIZE_UP_LEFT_DOWN_RIGHT: SystemMouseCursor =
583        SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpLeftDownRight);
584
585    /// A cursor indicating resizing an object bidirectionally from its top right or
586    /// bottom left corner.
587    ///
588    /// Typically the shape of a bidirectional arrow pointing upper right and lower
589    /// left.
590    pub const RESIZE_UP_RIGHT_DOWN_LEFT: SystemMouseCursor =
591        SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpRightDownLeft);
592
593    /// A cursor indicating resizing an object from its top edge.
594    ///
595    /// Typically the shape of an arrow pointing up. May fallback to
596    /// [`RESIZE_UP_DOWN`](Self::RESIZE_UP_DOWN).
597    pub const RESIZE_UP: SystemMouseCursor =
598        SystemMouseCursor::new(SystemMouseCursorKind::ResizeUp);
599
600    /// A cursor indicating resizing an object from its bottom edge.
601    ///
602    /// Typically the shape of an arrow pointing down. May fallback to
603    /// [`RESIZE_UP_DOWN`](Self::RESIZE_UP_DOWN).
604    pub const RESIZE_DOWN: SystemMouseCursor =
605        SystemMouseCursor::new(SystemMouseCursorKind::ResizeDown);
606
607    /// A cursor indicating resizing an object from its left edge.
608    ///
609    /// Typically the shape of an arrow pointing left. May fallback to
610    /// [`RESIZE_LEFT_RIGHT`](Self::RESIZE_LEFT_RIGHT).
611    pub const RESIZE_LEFT: SystemMouseCursor =
612        SystemMouseCursor::new(SystemMouseCursorKind::ResizeLeft);
613
614    /// A cursor indicating resizing an object from its right edge.
615    ///
616    /// Typically the shape of an arrow pointing right. May fallback to
617    /// [`RESIZE_LEFT_RIGHT`](Self::RESIZE_LEFT_RIGHT).
618    pub const RESIZE_RIGHT: SystemMouseCursor =
619        SystemMouseCursor::new(SystemMouseCursorKind::ResizeRight);
620
621    /// A cursor indicating resizing an object from its top-left corner.
622    ///
623    /// Typically the shape of an arrow pointing upper left. May fallback to
624    /// [`RESIZE_UP_LEFT_DOWN_RIGHT`](Self::RESIZE_UP_LEFT_DOWN_RIGHT).
625    pub const RESIZE_UP_LEFT: SystemMouseCursor =
626        SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpLeft);
627
628    /// A cursor indicating resizing an object from its top-right corner.
629    ///
630    /// Typically the shape of an arrow pointing upper right. May fallback to
631    /// [`RESIZE_UP_RIGHT_DOWN_LEFT`](Self::RESIZE_UP_RIGHT_DOWN_LEFT).
632    pub const RESIZE_UP_RIGHT: SystemMouseCursor =
633        SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpRight);
634
635    /// A cursor indicating resizing an object from its bottom-left corner.
636    ///
637    /// Typically the shape of an arrow pointing lower left. May fallback to
638    /// [`RESIZE_UP_RIGHT_DOWN_LEFT`](Self::RESIZE_UP_RIGHT_DOWN_LEFT).
639    pub const RESIZE_DOWN_LEFT: SystemMouseCursor =
640        SystemMouseCursor::new(SystemMouseCursorKind::ResizeDownLeft);
641
642    /// A cursor indicating resizing an object from its bottom-right corner.
643    ///
644    /// Typically the shape of an arrow pointing lower right. May fallback to
645    /// [`RESIZE_UP_LEFT_DOWN_RIGHT`](Self::RESIZE_UP_LEFT_DOWN_RIGHT).
646    pub const RESIZE_DOWN_RIGHT: SystemMouseCursor =
647        SystemMouseCursor::new(SystemMouseCursorKind::ResizeDownRight);
648
649    /// A cursor indicating resizing a column, or an item horizontally.
650    ///
651    /// Typically the shape of arrows pointing left and right with a vertical bar
652    /// separating them. May fallback to [`RESIZE_LEFT_RIGHT`](Self::RESIZE_LEFT_RIGHT).
653    pub const RESIZE_COLUMN: SystemMouseCursor =
654        SystemMouseCursor::new(SystemMouseCursorKind::ResizeColumn);
655
656    /// A cursor indicating resizing a row, or an item vertically.
657    ///
658    /// Typically the shape of arrows pointing up and down with a horizontal bar
659    /// separating them. May fallback to [`RESIZE_UP_DOWN`](Self::RESIZE_UP_DOWN).
660    pub const RESIZE_ROW: SystemMouseCursor =
661        SystemMouseCursor::new(SystemMouseCursorKind::ResizeRow);
662
663    /// A cursor indicating zooming in.
664    ///
665    /// Typically a magnifying glass with a plus sign.
666    pub const ZOOM_IN: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::ZoomIn);
667
668    /// A cursor indicating zooming out.
669    ///
670    /// Typically a magnifying glass with a minus sign.
671    pub const ZOOM_OUT: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::ZoomOut);
672}
673
674#[cfg(test)]
675mod tests {
676    use inset_embedder::ViewRef;
677    use inset_gestures::{PointerHoverEvent, PointerRemovedEvent};
678    use inset_test::{TestPlatform, TestView};
679
680    use super::*;
681
682    const VIEW: ViewId = ViewId(3);
683
684    /// A platform with view [`VIEW`], whose host records the cursors it is asked for, and the view.
685    fn recording_platform() -> (TestPlatform, Rc<TestView>) {
686        let view = Rc::new(TestView::new(800.0, 600.0).with_id(VIEW));
687        let platform = TestPlatform::new().with_view(Rc::clone(&view) as ViewRef);
688        (platform, view)
689    }
690
691    fn hover() -> PointerEvent {
692        PointerEvent::Hover(PointerHoverEvent::default())
693    }
694
695    #[test]
696    fn system_cursors_compare_by_kind_and_special_cursors_by_class() {
697        let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
698        let click_again: MouseCursorRef = SystemMouseCursors::CLICK.into();
699        let basic: MouseCursorRef = SystemMouseCursors::BASIC.into();
700        assert!(*click == *click_again);
701        assert!(*click != *basic);
702        assert!(*<dyn MouseCursor>::defer() == *<dyn MouseCursor>::defer());
703        assert!(*<dyn MouseCursor>::uncontrolled() == *<dyn MouseCursor>::uncontrolled());
704        assert!(*<dyn MouseCursor>::defer() != *<dyn MouseCursor>::uncontrolled());
705        assert!(*<dyn MouseCursor>::defer() != *click);
706        assert_eq!(click.debug_description(), "SystemMouseCursor(Click)");
707        assert_eq!(<dyn MouseCursor>::defer().debug_description(), "defer");
708    }
709
710    #[test]
711    fn manager_activates_the_first_non_deferred_candidate() {
712        let (platform, view) = recording_platform();
713        let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
714        manager.handle_device_cursor_update(
715            &platform,
716            VIEW,
717            1,
718            Some(&hover()),
719            [<dyn MouseCursor>::defer(), SystemMouseCursors::TEXT.into()],
720        );
721        assert_eq!(
722            view.test_host().cursors(),
723            [(1, SystemMouseCursorKind::Text)],
724            "the deferring region lets the text region behind it decide"
725        );
726        let text: MouseCursorRef = SystemMouseCursors::TEXT.into();
727        assert!(*manager.debug_device_active_cursor(1).unwrap() == *text);
728    }
729
730    #[test]
731    fn manager_falls_back_when_every_candidate_defers() {
732        let (platform, view) = recording_platform();
733        let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
734        manager.handle_device_cursor_update(
735            &platform,
736            VIEW,
737            1,
738            Some(&hover()),
739            [<dyn MouseCursor>::defer()],
740        );
741        assert_eq!(
742            view.test_host().cursors(),
743            [(1, SystemMouseCursorKind::Basic)]
744        );
745    }
746
747    #[test]
748    fn manager_does_not_reactivate_the_same_cursor() {
749        let (platform, view) = recording_platform();
750        let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
751        manager.handle_device_cursor_update(
752            &platform,
753            VIEW,
754            1,
755            Some(&hover()),
756            [SystemMouseCursors::CLICK.into()],
757        );
758        manager.handle_device_cursor_update(
759            &platform,
760            VIEW,
761            1,
762            None,
763            [SystemMouseCursors::CLICK.into()],
764        );
765        assert_eq!(view.test_host().cursors().len(), 1);
766
767        manager.handle_device_cursor_update(
768            &platform,
769            VIEW,
770            1,
771            None,
772            [SystemMouseCursors::GRAB.into()],
773        );
774        assert_eq!(view.test_host().cursors().len(), 2);
775    }
776
777    #[test]
778    fn an_uncontrolled_region_keeps_the_previous_system_request() {
779        let (platform, view) = recording_platform();
780        let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
781        manager.handle_device_cursor_update(
782            &platform,
783            VIEW,
784            1,
785            Some(&hover()),
786            [SystemMouseCursors::CLICK.into()],
787        );
788        manager.handle_device_cursor_update(
789            &platform,
790            VIEW,
791            1,
792            None,
793            [<dyn MouseCursor>::uncontrolled()],
794        );
795        assert_eq!(
796            view.test_host().cursors(),
797            [(1, SystemMouseCursorKind::Click)],
798            "the no-op session makes no request; the host keeps whatever it shows"
799        );
800        assert!(
801            *manager.debug_device_active_cursor(1).unwrap() == *<dyn MouseCursor>::uncontrolled()
802        );
803    }
804
805    #[test]
806    fn a_removed_device_forgets_its_session() {
807        let (platform, _view) = recording_platform();
808        let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
809        manager.handle_device_cursor_update(
810            &platform,
811            VIEW,
812            1,
813            Some(&hover()),
814            [SystemMouseCursors::CLICK.into()],
815        );
816        let removed = PointerEvent::Removed(PointerRemovedEvent::default());
817        manager.handle_device_cursor_update(&platform, VIEW, 1, Some(&removed), []);
818        assert!(manager.debug_device_active_cursor(1).is_none());
819    }
820}