aetna_core/cursor.rs
1//! Pointer-cursor model.
2//!
3//! Widgets opt into a non-default cursor by setting [`crate::tree::El::cursor`].
4//! Per-frame the host runner reads [`crate::state::UiState::cursor`] and forwards
5//! the resolved value to the windowing backend (winit's
6//! `Window::set_cursor_icon`, the browser's `canvas.style.cursor`).
7//!
8//! The variants line up with the CSS `cursor` property — same names
9//! winit's `CursorIcon` already uses — so backend bridges are dumb
10//! `From` impls.
11//!
12//! # Resolution
13//!
14//! [`crate::state::UiState::cursor`] picks the active cursor each frame:
15//!
16//! 1. If a press is captured (button drag, slider thumb, scrollbar
17//! drag, text selection, …), the cursor follows the *press target*'s
18//! declared cursor and ignores whatever the pointer is currently
19//! over. Drag off a button onto a text input and the cursor stays
20//! [`Cursor::Pointer`] — matches native press-and-hold behaviour.
21//! 2. Else the hovered node and its ancestors are walked root-ward,
22//! returning the first explicit `.cursor(...)`. So a panel that
23//! sets `.cursor(Move)` once propagates to children that don't
24//! override.
25//! 3. Else [`Cursor::Default`].
26//!
27//! Disabled state is *not* auto-mapped to [`Cursor::NotAllowed`] —
28//! widgets that want that affordance branch in their build closure.
29
30/// Pointer cursor. Variant names mirror CSS `cursor` so the
31/// backend mapping is a 1:1 translation.
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
33#[non_exhaustive]
34pub enum Cursor {
35 /// Platform default arrow.
36 #[default]
37 Default,
38 /// Hand / pointing finger — clickable surfaces (buttons, links,
39 /// checkboxes, switches, radios).
40 Pointer,
41 /// I-beam — text inputs and selectable text regions.
42 Text,
43 /// Slashed circle — disabled / unavailable affordances.
44 NotAllowed,
45 /// Open hand — a draggable target at rest.
46 Grab,
47 /// Closed hand — a draggable target while dragging.
48 Grabbing,
49 /// Generic "drag in any direction" (pan handles, view-port grabs).
50 Move,
51 /// Horizontal resize (←→).
52 EwResize,
53 /// Vertical resize (↑↓).
54 NsResize,
55 /// Diagonal resize (↖↘).
56 NwseResize,
57 /// Anti-diagonal resize (↗↙).
58 NeswResize,
59 /// Column boundary resize (table column dividers).
60 ColResize,
61 /// Row boundary resize (table row dividers).
62 RowResize,
63 /// Crosshair — picker / area-select tools.
64 Crosshair,
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn default_is_default_variant() {
73 assert_eq!(Cursor::default(), Cursor::Default);
74 }
75}