Skip to main content

ferroui_base/input/
cursor.rs

1use crate::media::imaging::Bitmap;
2use crate::platform::{ICursorFactory, ICursorImpl};
3use crate::{FerroLocator, LocatorExtensions, PixelPoint};
4use std::cell::RefCell;
5use std::fmt;
6use std::rc::Rc;
7
8/// Defines the standard cursor types.
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
10pub enum StandardCursorType {
11    #[default]
12    Arrow,
13    Ibeam,
14    Wait,
15    Cross,
16    UpArrow,
17    SizeWestEast,
18    SizeNorthSouth,
19    SizeAll,
20    No,
21    Hand,
22    AppStarting,
23    Help,
24    TopSide,
25    BottomSide,
26    LeftSide,
27    RightSide,
28    TopLeftCorner,
29    TopRightCorner,
30    BottomLeftCorner,
31    BottomRightCorner,
32    DragMove,
33    DragCopy,
34    DragLink,
35    None,
36}
37
38impl StandardCursorType {
39    const ALL: [StandardCursorType; 24] = [
40        StandardCursorType::Arrow,
41        StandardCursorType::Ibeam,
42        StandardCursorType::Wait,
43        StandardCursorType::Cross,
44        StandardCursorType::UpArrow,
45        StandardCursorType::SizeWestEast,
46        StandardCursorType::SizeNorthSouth,
47        StandardCursorType::SizeAll,
48        StandardCursorType::No,
49        StandardCursorType::Hand,
50        StandardCursorType::AppStarting,
51        StandardCursorType::Help,
52        StandardCursorType::TopSide,
53        StandardCursorType::BottomSide,
54        StandardCursorType::LeftSide,
55        StandardCursorType::RightSide,
56        StandardCursorType::TopLeftCorner,
57        StandardCursorType::TopRightCorner,
58        StandardCursorType::BottomLeftCorner,
59        StandardCursorType::BottomRightCorner,
60        StandardCursorType::DragMove,
61        StandardCursorType::DragCopy,
62        StandardCursorType::DragLink,
63        StandardCursorType::None,
64    ];
65
66    /// The name of the cursor type.
67    pub fn name(self) -> &'static str {
68        match self {
69            StandardCursorType::Arrow => "Arrow",
70            StandardCursorType::Ibeam => "Ibeam",
71            StandardCursorType::Wait => "Wait",
72            StandardCursorType::Cross => "Cross",
73            StandardCursorType::UpArrow => "UpArrow",
74            StandardCursorType::SizeWestEast => "SizeWestEast",
75            StandardCursorType::SizeNorthSouth => "SizeNorthSouth",
76            StandardCursorType::SizeAll => "SizeAll",
77            StandardCursorType::No => "No",
78            StandardCursorType::Hand => "Hand",
79            StandardCursorType::AppStarting => "AppStarting",
80            StandardCursorType::Help => "Help",
81            StandardCursorType::TopSide => "TopSide",
82            StandardCursorType::BottomSide => "BottomSide",
83            StandardCursorType::LeftSide => "LeftSide",
84            StandardCursorType::RightSide => "RightSide",
85            StandardCursorType::TopLeftCorner => "TopLeftCorner",
86            StandardCursorType::TopRightCorner => "TopRightCorner",
87            StandardCursorType::BottomLeftCorner => "BottomLeftCorner",
88            StandardCursorType::BottomRightCorner => "BottomRightCorner",
89            StandardCursorType::DragMove => "DragMove",
90            StandardCursorType::DragCopy => "DragCopy",
91            StandardCursorType::DragLink => "DragLink",
92            StandardCursorType::None => "None",
93        }
94    }
95
96    /// Parses the name of a cursor type, ignoring case.
97    pub fn parse(s: &str) -> Option<StandardCursorType> {
98        let s = s.trim();
99        Self::ALL.iter().copied().find(|t| t.name().eq_ignore_ascii_case(s))
100    }
101}
102
103impl fmt::Display for StandardCursorType {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.write_str(self.name())
106    }
107}
108
109/// The error returned when a string does not name a standard cursor type.
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct ParseCursorError(pub String);
112
113impl fmt::Display for ParseCursorError {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        write!(f, "Unrecognized cursor type '{}'.", self.0)
116    }
117}
118
119impl std::error::Error for ParseCursorError {}
120
121crate::ferro_thread_local! {
122    static DEFAULT_CURSOR: RefCell<Option<Rc<Cursor>>> = const { RefCell::new(None) };
123}
124
125/// Represents a mouse cursor.
126///
127/// Cursors have reference semantics: two cursors are equal only if they are
128/// the same object.
129pub struct Cursor {
130    platform_impl: Rc<dyn ICursorImpl>,
131    name: String,
132}
133
134impl Cursor {
135    fn get_cursor_factory() -> Rc<dyn ICursorFactory> {
136        FerroLocator::current().get_required_service::<dyn ICursorFactory>()
137    }
138
139    /// The default cursor: the standard arrow.
140    ///
141    /// Panics if no cursor factory is registered with the locator.
142    pub fn default_cursor() -> Rc<Cursor> {
143        if let Some(cursor) = DEFAULT_CURSOR.with(|cursor| cursor.borrow().clone()) {
144            return cursor;
145        }
146        let cursor = Cursor::new(StandardCursorType::Arrow);
147        DEFAULT_CURSOR.with(|slot| *slot.borrow_mut() = Some(cursor.clone()));
148        cursor
149    }
150
151    /// Creates a standard cursor.
152    ///
153    /// Panics if no cursor factory is registered with the locator.
154    pub fn new(cursor_type: StandardCursorType) -> Rc<Cursor> {
155        Rc::new(Cursor {
156            platform_impl: Self::get_cursor_factory().get_cursor(cursor_type),
157            name: cursor_type.name().to_string(),
158        })
159    }
160
161    /// Creates a cursor from a bitmap. `hot_spot` is the position within
162    /// the bitmap that is the position of the pointer.
163    ///
164    /// Panics if no cursor factory is registered with the locator.
165    pub fn from_bitmap(cursor: &Bitmap, hot_spot: PixelPoint) -> Rc<Cursor> {
166        Rc::new(Cursor {
167            platform_impl: Self::get_cursor_factory().create_cursor(cursor, hot_spot),
168            name: "BitmapCursor".to_string(),
169        })
170    }
171
172    /// The platform implementation of the cursor.
173    pub fn platform_impl(&self) -> &Rc<dyn ICursorImpl> {
174        &self.platform_impl
175    }
176
177    /// Releases the platform resources of the cursor.
178    pub fn dispose(&self) {
179        self.platform_impl.dispose()
180    }
181
182    /// Creates a standard cursor from the name of its type.
183    ///
184    /// Panics if no cursor factory is registered with the locator.
185    pub fn parse(s: &str) -> Result<Rc<Cursor>, ParseCursorError> {
186        match StandardCursorType::parse(s) {
187            Some(cursor_type) => Ok(Cursor::new(cursor_type)),
188            None => Err(ParseCursorError(s.to_string())),
189        }
190    }
191}
192
193impl PartialEq for Cursor {
194    #[inline]
195    fn eq(&self, other: &Self) -> bool {
196        std::ptr::eq(self, other)
197    }
198}
199
200impl Eq for Cursor {}
201
202impl fmt::Display for Cursor {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        f.write_str(&self.name)
205    }
206}
207
208impl fmt::Debug for Cursor {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        f.write_str(&self.name)
211    }
212}