1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use objc::{class, msg_send, sel, sel_impl};

use crate::foundation::{id, NO, YES};

/// Represents a type of cursor that you can associate with mouse movement.
/// @TODO: Loading?
#[derive(Debug)]
pub enum CursorType {
    /// A standard arrow.
    Arrow,

    /// Current Cusrosr
    Current,

    /// Current System cursor
    CurrentSystem,

    /// A crosshair.
    Crosshair,

    /// A closed hand, typically for mousedown and drag.
    ClosedHand,

    /// An open hand, typically for indicating draggable.
    OpenHand,

    /// A pointing hand, like clicking a link.
    PointingHand,

    /// Indicator that something can be resized to the left.
    ResizeLeft,

    /// Indicator that something can be resized to the right.
    ResizeRight,

    /// Indicator  that something can be resized on the horizontal axis.
    ResizeLeftRight,

    /// Indicates that something can be resized up.
    ResizeUp,

    /// Indicates that something can be resized down.
    ResizeDown,

    /// Indicator  that something can be resized on the vertical axis.
    ResizeUpDown,

    /// Otherwise known as the "poof" or "cloud" cursor. Indicates something will vanish, like
    /// dragging into the Trash.
    DisappearingItem,

    /// Indicate an insertion point, like for text.
    IBeam,

    /// The vertical version of `CursorType::IBeam`.
    IBeamVertical,

    /// Indicates an operation is illegal.
    OperationNotAllowed,

    /// The drag link cursor.
    DragLink,

    /// Used for drag-and-drop usually, will displayu the standard "+" icon next to the cursor.
    DragCopy,

    /// Indicates a context menu will open.
    ContextMenu
}

/// A wrapper around NSCursor.
///
/// You use then when you need to control how the cursor (pointer) should appear. Like `NSCursor`,
/// this is stack based - you push, and you pop. You are responsible for ensuring that this is
/// correctly popped!
///
/// For a very abbreviated example:
///
/// ```rust,no_run
/// use cacao::appkit::Cursor;
/// use cacao::appkit::CursorType;
/// use cacao::dragdrop::DragInfo;
/// use cacao::dragdrop::DragOperation;
/// use cacao::view::ViewDelegate;
/// struct MyView;
/// impl ViewDelegate for MyView {
///     const NAME: &'static str = "RootView";
///     fn dragging_entered(&self, _info: DragInfo) -> DragOperation {
///         Cursor::push(CursorType::DragCopy);
///         DragOperation::Copy
///     }
///
///     fn dragging_exited(&self, _info: DragInfo) {
///         Cursor::pop();
///     }
/// }
/// ```
///
/// This will show the "add files +" indicator when the user has entered the dragging threshold
/// with some items that trigger it, and undo the cursor when the user leaves (regardless of drop
/// status).
#[derive(Debug)]
pub struct Cursor;

impl Cursor {
    /// Given a cursor type, will make it the system cursor.
    /// The inverse of this call, which you should call when ready, is `pop()`.
    pub fn push(cursor_type: CursorType) {
        unsafe {
            let cursor: id = match cursor_type {
                CursorType::Arrow => msg_send![class!(NSCursor), arrowCursor],
                CursorType::Current => msg_send![class!(NSCursor), currentCursor],
                CursorType::CurrentSystem => msg_send![class!(NSCursor), currentSystemCursor],
                CursorType::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
                CursorType::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
                CursorType::OpenHand => msg_send![class!(NSCursor), openHandCursor],
                CursorType::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
                CursorType::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
                CursorType::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
                CursorType::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
                CursorType::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
                CursorType::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
                CursorType::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
                CursorType::DisappearingItem => msg_send![class!(NSCursor), disappearingItemCursor],
                CursorType::IBeam => msg_send![class!(NSCursor), IBeamCursor],
                CursorType::IBeamVertical => msg_send![class!(NSCursor), IBeamCursorForVerticalLayout],
                CursorType::OperationNotAllowed => msg_send![class!(NSCursor), operationNotAllowedCursor],
                CursorType::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
                CursorType::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
                CursorType::ContextMenu => msg_send![class!(NSCursor), contextualMenuCursor]
            };

            let _: () = msg_send![cursor, push];
        }
    }

    /// Pops the current cursor off the cursor-stack. The inverse of push.
    pub fn pop() {
        unsafe {
            let _: () = msg_send![class!(NSCursor), pop];
        }
    }

    /// Hides the cursor. Part of a balanced call stack.
    pub fn hide() {
        unsafe {
            let _: () = msg_send![class!(NSCursor), hide];
        }
    }

    /// Un-hides the cursor. Part of a balanced call stack.
    pub fn unhide() {
        unsafe {
            let _: () = msg_send![class!(NSCursor), unhide];
        }
    }

    /// Sets the cursor to hidden, but will reveal it if the user moves the mouse.
    ///
    /// Potentially useful for games and other immersive experiences.
    ///
    /// If you use this, do _not_ use `unhide` - just call this with the inverted boolean value.
    /// Trying to invert this with `unhide` will result in undefined system behavior.
    pub fn set_hidden_until_mouse_moves(status: bool) {
        unsafe {
            let _: () = msg_send![class!(NSCursor), setHiddenUntilMouseMoves:match status {
                true => YES,
                false => NO
            }];
        }
    }
}