cursor_icon/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0 OR Zlib
2
3#![cfg_attr(not(feature = "std"), no_std)]
4#![deny(rust_2018_idioms)]
5#![deny(rustdoc::broken_intra_doc_links)]
6#![deny(unsafe_op_in_unsafe_fn)]
7#![deny(improper_ctypes, improper_ctypes_definitions)]
8#![deny(clippy::all)]
9#![deny(missing_debug_implementations)]
10#![deny(missing_docs)]
11#![forbid(unsafe_code)]
12#![cfg_attr(clippy, deny(warnings))]
13#![cfg_attr(docsrs, feature(doc_auto_cfg))]
14
15//! The cross platform cursor icon type.
16//!
17//! This type is intended to be used as a standard interoperability type between
18//! GUI frameworks in order to convey the cursor icon type.
19//!
20//! # Example
21//!
22//! ```
23//! use cursor_icon::CursorIcon;
24//!
25//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
26//! // Parse a cursor icon from the string that describes it.
27//! let cursor_name = "pointer";
28//! let cursor_icon: CursorIcon = cursor_name.parse()?;
29//! println!("The cursor icon is {:?}", cursor_icon);
30//! # Ok(())
31//! # }
32//! ```
33
34// This file contains a portion of the CSS Basic User Interface Module Level 3
35// specification. In particular, the names for the cursor from the #cursor
36// section and documentation for some of the variants were taken.
37//
38// The original document is https://www.w3.org/TR/css-ui-3/#cursor.
39// Copyright © 2018 W3C® (MIT, ERCIM, Keio, Beihang)
40//
41// These documents were used under the terms of the following license. This W3C
42// license as well as the W3C short notice apply to the `CursorIcon` enum's
43// variants and documentation attached to them.
44
45// --------- BEGINNING OF W3C LICENSE
46// --------------------------------------------------------------
47//
48// License
49//
50// By obtaining and/or copying this work, you (the licensee) agree that you have
51// read, understood, and will comply with the following terms and conditions.
52//
53// Permission to copy, modify, and distribute this work, with or without
54// modification, for any purpose and without fee or royalty is hereby granted,
55// provided that you include the following on ALL copies of the work or portions
56// thereof, including modifications:
57//
58// - The full text of this NOTICE in a location viewable to users of the
59//   redistributed or derivative work.
60// - Any pre-existing intellectual property disclaimers, notices, or terms and
61//   conditions. If none exist, the W3C Software and Document Short Notice
62//   should be included.
63// - Notice of any changes or modifications, through a copyright statement on
64//   the new code or document such as "This software or document includes
65//   material copied from or derived from [title and URI of the W3C document].
66//   Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)."
67//
68// Disclaimers
69//
70// THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS
71// OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES
72// OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF
73// THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS,
74// COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
75//
76// COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
77// CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT.
78//
79// The name and trademarks of copyright holders may NOT be used in advertising
80// or publicity pertaining to the work without specific, written prior
81// permission. Title to copyright in this work will at all times remain with
82// copyright holders.
83//
84// --------- END OF W3C LICENSE
85// --------------------------------------------------------------------
86
87// --------- BEGINNING OF W3C SHORT NOTICE
88// ---------------------------------------------------------
89//
90// winit: https://github.com/rust-windowing/cursor-icon
91//
92// Copyright © 2023 World Wide Web Consortium, (Massachusetts Institute of
93// Technology, European Research Consortium for Informatics and Mathematics,
94// Keio University, Beihang). All Rights Reserved. This work is distributed
95// under the W3C® Software License [1] in the hope that it will be useful, but
96// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
97// FITNESS FOR A PARTICULAR PURPOSE.
98//
99// [1] http://www.w3.org/Consortium/Legal/copyright-software
100//
101// --------- END OF W3C SHORT NOTICE
102// --------------------------------------------------------------
103
104#[cfg(feature = "serde")]
105#[macro_use]
106extern crate serde;
107
108// XXX for forwards compatibility.
109#[cfg(feature = "alloc")]
110extern crate alloc as _;
111
112/// Describes the appearance of the (usually mouse) cursor icon.
113///
114/// The names are taken from the CSS W3C specification:
115/// <https://www.w3.org/TR/css-ui-3/#cursor>
116///
117/// # Examples
118///
119/// ```
120/// use cursor_icon::CursorIcon;
121///
122/// // Get the cursor icon for the default cursor.
123/// let cursor_icon = CursorIcon::Default;
124/// ```
125#[non_exhaustive]
126#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
128pub enum CursorIcon {
129    /// The platform-dependent default cursor. Often rendered as arrow.
130    #[default]
131    Default,
132
133    /// A context menu is available for the object under the cursor. Often
134    /// rendered as an arrow with a small menu-like graphic next to it.
135    ContextMenu,
136
137    /// Help is available for the object under the cursor. Often rendered as a
138    /// question mark or a balloon.
139    Help,
140
141    /// The cursor is a pointer that indicates a link. Often rendered as the
142    /// backside of a hand with the index finger extended.
143    Pointer,
144
145    /// A progress indicator. The program is performing some processing, but is
146    /// different from [`CursorIcon::Wait`] in that the user may still interact
147    /// with the program.
148    Progress,
149
150    /// Indicates that the program is busy and the user should wait. Often
151    /// rendered as a watch or hourglass.
152    Wait,
153
154    /// Indicates that a cell or set of cells may be selected. Often rendered as
155    /// a thick plus-sign with a dot in the middle.
156    Cell,
157
158    /// A simple crosshair (e.g., short line segments resembling a "+" sign).
159    /// Often used to indicate a two dimensional bitmap selection mode.
160    Crosshair,
161
162    /// Indicates text that may be selected. Often rendered as an I-beam.
163    Text,
164
165    /// Indicates vertical-text that may be selected. Often rendered as a
166    /// horizontal I-beam.
167    VerticalText,
168
169    /// Indicates an alias of/shortcut to something is to be created. Often
170    /// rendered as an arrow with a small curved arrow next to it.
171    Alias,
172
173    /// Indicates something is to be copied. Often rendered as an arrow with a
174    /// small plus sign next to it.
175    Copy,
176
177    /// Indicates something is to be moved.
178    Move,
179
180    /// Indicates that the dragged item cannot be dropped at the current cursor
181    /// location. Often rendered as a hand or pointer with a small circle with a
182    /// line through it.
183    NoDrop,
184
185    /// Indicates that the requested action will not be carried out. Often
186    /// rendered as a circle with a line through it.
187    NotAllowed,
188
189    /// Indicates that something can be grabbed (dragged to be moved). Often
190    /// rendered as the backside of an open hand.
191    Grab,
192
193    /// Indicates that something is being grabbed (dragged to be moved). Often
194    /// rendered as the backside of a hand with fingers closed mostly out of
195    /// view.
196    Grabbing,
197
198    /// The east border to be moved.
199    EResize,
200
201    /// The north border to be moved.
202    NResize,
203
204    /// The north-east corner to be moved.
205    NeResize,
206
207    /// The north-west corner to be moved.
208    NwResize,
209
210    /// The south border to be moved.
211    SResize,
212
213    /// The south-east corner to be moved.
214    SeResize,
215
216    /// The south-west corner to be moved.
217    SwResize,
218
219    /// The west border to be moved.
220    WResize,
221
222    /// The east and west borders to be moved.
223    EwResize,
224
225    /// The south and north borders to be moved.
226    NsResize,
227
228    /// The north-east and south-west corners to be moved.
229    NeswResize,
230
231    /// The north-west and south-east corners to be moved.
232    NwseResize,
233
234    /// Indicates that the item/column can be resized horizontally. Often
235    /// rendered as arrows pointing left and right with a vertical bar
236    /// separating them.
237    ColResize,
238
239    /// Indicates that the item/row can be resized vertically. Often rendered as
240    /// arrows pointing up and down with a horizontal bar separating them.
241    RowResize,
242
243    /// Indicates that the something can be scrolled in any direction. Often
244    /// rendered as arrows pointing up, down, left, and right with a dot in the
245    /// middle.
246    AllScroll,
247
248    /// Indicates that something can be zoomed in. Often rendered as a
249    /// magnifying glass with a "+" in the center of the glass.
250    ZoomIn,
251
252    /// Indicates that something can be zoomed in. Often rendered as a
253    /// magnifying glass with a "-" in the center of the glass.
254    ZoomOut,
255
256    /// Indicates that the user will select the action that will be carried out.
257    ///
258    /// This is a non-standard extension of the w3c standard used in freedesktop
259    /// cursor icon themes.
260    DndAsk,
261
262    /// Indicates that something can be moved or resized in any direction.
263    ///
264    /// This is a non-standard extension of the w3c standard used in freedesktop
265    /// cursor icon themes.
266    AllResize,
267}
268
269impl CursorIcon {
270    /// The name of the cursor icon as defined in the w3c standard.
271    /// Non-standard cursors such as "DndAsk" and "AllResize" are translated as
272    /// "dnd-ask" and "all-resize" respectively.
273    ///
274    /// This name most of the time could be passed as is to cursor loading
275    /// libraries on X11/Wayland and could be used as-is on web.
276    ///
277    /// # Examples
278    ///
279    /// ```no_run
280    /// use cursor_icon::CursorIcon;
281    /// use wayland_cursor::CursorTheme;
282    ///
283    /// # use wayland_client::Connection;
284    /// # use wayland_client::protocol::wl_shm::WlShm;
285    /// # fn test(conn: &Connection, shm: WlShm) -> Result<(), Box<dyn std::error::Error>> {
286    /// // Choose a cursor to load.
287    /// let cursor = CursorIcon::Help;
288    ///
289    /// // Load the Wayland cursor theme.
290    /// let mut cursor_theme = CursorTheme::load(conn, shm, 32)?;
291    ///
292    /// // Load the cursor.
293    /// let cursor = cursor_theme.get_cursor(cursor.name());
294    /// if let Some(cursor) = cursor {
295    ///     println!("Total number of images: {}", cursor.image_count());
296    /// }
297    /// # Ok(())
298    /// # }
299    /// ```
300    pub fn name(&self) -> &'static str {
301        match self {
302            CursorIcon::Default => "default",
303            CursorIcon::ContextMenu => "context-menu",
304            CursorIcon::Help => "help",
305            CursorIcon::Pointer => "pointer",
306            CursorIcon::Progress => "progress",
307            CursorIcon::Wait => "wait",
308            CursorIcon::Cell => "cell",
309            CursorIcon::Crosshair => "crosshair",
310            CursorIcon::Text => "text",
311            CursorIcon::VerticalText => "vertical-text",
312            CursorIcon::Alias => "alias",
313            CursorIcon::Copy => "copy",
314            CursorIcon::Move => "move",
315            CursorIcon::NoDrop => "no-drop",
316            CursorIcon::NotAllowed => "not-allowed",
317            CursorIcon::Grab => "grab",
318            CursorIcon::Grabbing => "grabbing",
319            CursorIcon::EResize => "e-resize",
320            CursorIcon::NResize => "n-resize",
321            CursorIcon::NeResize => "ne-resize",
322            CursorIcon::NwResize => "nw-resize",
323            CursorIcon::SResize => "s-resize",
324            CursorIcon::SeResize => "se-resize",
325            CursorIcon::SwResize => "sw-resize",
326            CursorIcon::WResize => "w-resize",
327            CursorIcon::EwResize => "ew-resize",
328            CursorIcon::NsResize => "ns-resize",
329            CursorIcon::NeswResize => "nesw-resize",
330            CursorIcon::NwseResize => "nwse-resize",
331            CursorIcon::ColResize => "col-resize",
332            CursorIcon::RowResize => "row-resize",
333            CursorIcon::AllScroll => "all-scroll",
334            CursorIcon::ZoomIn => "zoom-in",
335            CursorIcon::ZoomOut => "zoom-out",
336            CursorIcon::DndAsk => "dnd-ask",
337            CursorIcon::AllResize => "all-resize",
338        }
339    }
340
341    /// A list of alternative names for the cursor icon as commonly found in
342    /// legacy Xcursor themes.
343    ///
344    /// This should only be used as a fallback in case the cursor theme does not
345    /// adhere to the w3c standard.
346    pub fn alt_names(&self) -> &[&'static str] {
347        match self {
348            CursorIcon::Default => &["left_ptr", "arrow", "top_left_arrow", "left_arrow"],
349            CursorIcon::ContextMenu => &[],
350            CursorIcon::Help => &["question_arrow", "whats_this"],
351            CursorIcon::Pointer => &["hand2", "hand1", "hand", "pointing_hand"],
352            CursorIcon::Progress => &["left_ptr_watch", "half-busy"],
353            CursorIcon::Wait => &["watch"],
354            CursorIcon::Cell => &["plus"],
355            CursorIcon::Crosshair => &["cross"],
356            CursorIcon::Text => &["xterm", "ibeam"],
357            CursorIcon::VerticalText => &[],
358            CursorIcon::Alias => &["link"],
359            CursorIcon::Copy => &[],
360            CursorIcon::Move => &[],
361            CursorIcon::NoDrop => &["circle"],
362            CursorIcon::NotAllowed => &["crossed_circle", "forbidden"],
363            CursorIcon::Grab => &["openhand", "fleur"],
364            CursorIcon::Grabbing => &["closedhand"],
365            CursorIcon::EResize => &["right_side"],
366            CursorIcon::NResize => &["top_side"],
367            CursorIcon::NeResize => &["top_right_corner"],
368            CursorIcon::NwResize => &["top_left_corner"],
369            CursorIcon::SResize => &["bottom_side"],
370            CursorIcon::SeResize => &["bottom_right_corner"],
371            CursorIcon::SwResize => &["bottom_left_corner"],
372            CursorIcon::WResize => &["left_side"],
373            CursorIcon::EwResize => &["h_double_arrow", "size_hor"],
374            CursorIcon::NsResize => &["v_double_arrow", "size_ver"],
375            CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
376            CursorIcon::NwseResize => &["bd_double_arrow", "size_fdiag"],
377            CursorIcon::ColResize => &["split_h", "h_double_arrow", "sb_h_double_arrow"],
378            CursorIcon::RowResize => &["split_v", "v_double_arrow", "sb_v_double_arrow"],
379            CursorIcon::AllScroll => &["size_all"],
380            CursorIcon::ZoomIn => &[],
381            CursorIcon::ZoomOut => &[],
382            CursorIcon::DndAsk => &["copy"],
383            CursorIcon::AllResize => &["move"],
384        }
385    }
386}
387
388impl core::fmt::Display for CursorIcon {
389    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
390        f.write_str(self.name())
391    }
392}
393
394impl core::str::FromStr for CursorIcon {
395    type Err = ParseError;
396
397    /// Parse a string slice into [`CursorIcon`].
398    ///
399    /// The `name` is a lower kebab case [`CursorIcon`] variant name, e.g.
400    /// `nesw-resize`. The set of possible valid `name` values matches exactly
401    /// the set of [`CursorIcon::name`] outputs.
402    fn from_str(name: &str) -> Result<Self, Self::Err> {
403        match name {
404            "default" => Ok(CursorIcon::Default),
405            "context-menu" => Ok(CursorIcon::ContextMenu),
406            "help" => Ok(CursorIcon::Help),
407            "pointer" => Ok(CursorIcon::Pointer),
408            "progress" => Ok(CursorIcon::Progress),
409            "wait" => Ok(CursorIcon::Wait),
410            "cell" => Ok(CursorIcon::Cell),
411            "crosshair" => Ok(CursorIcon::Crosshair),
412            "text" => Ok(CursorIcon::Text),
413            "vertical-text" => Ok(CursorIcon::VerticalText),
414            "alias" => Ok(CursorIcon::Alias),
415            "copy" => Ok(CursorIcon::Copy),
416            "move" => Ok(CursorIcon::Move),
417            "no-drop" => Ok(CursorIcon::NoDrop),
418            "not-allowed" => Ok(CursorIcon::NotAllowed),
419            "grab" => Ok(CursorIcon::Grab),
420            "grabbing" => Ok(CursorIcon::Grabbing),
421            "e-resize" => Ok(CursorIcon::EResize),
422            "n-resize" => Ok(CursorIcon::NResize),
423            "ne-resize" => Ok(CursorIcon::NeResize),
424            "nw-resize" => Ok(CursorIcon::NwResize),
425            "s-resize" => Ok(CursorIcon::SResize),
426            "se-resize" => Ok(CursorIcon::SeResize),
427            "sw-resize" => Ok(CursorIcon::SwResize),
428            "w-resize" => Ok(CursorIcon::WResize),
429            "ew-resize" => Ok(CursorIcon::EwResize),
430            "ns-resize" => Ok(CursorIcon::NsResize),
431            "nesw-resize" => Ok(CursorIcon::NeswResize),
432            "nwse-resize" => Ok(CursorIcon::NwseResize),
433            "col-resize" => Ok(CursorIcon::ColResize),
434            "row-resize" => Ok(CursorIcon::RowResize),
435            "all-scroll" => Ok(CursorIcon::AllScroll),
436            "zoom-in" => Ok(CursorIcon::ZoomIn),
437            "zoom-out" => Ok(CursorIcon::ZoomOut),
438            _ => Err(ParseError { _private: () }),
439        }
440    }
441}
442
443/// An error which could be returned when parsing [`CursorIcon`].
444///
445/// This occurs when the [`FromStr`] implementation of [`CursorIcon`] fails.
446///
447/// [`FromStr`]: core::str::FromStr
448#[derive(Debug, PartialEq, Eq, Hash)]
449pub struct ParseError {
450    _private: (),
451}
452
453impl core::fmt::Display for ParseError {
454    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
455        f.write_str("failed to parse cursor icon")
456    }
457}
458
459#[cfg(feature = "std")]
460impl std::error::Error for ParseError {}