Skip to main content

ui/
cursor.rs

1// cursor -- custom pointer cursors drawn by the app, at the dpi of the window
2//                                                                     [src/ui/ui_cursor.s]
3//
4//   cursor_new(f, w, h, hot) -> handle 1..CUR_USER_MAX (0 = no slot left)
5//        a cursor of w x h DIP with the hot spot hot = x | y << 16 (DIP); f(g, dpi) draws it
6//        at `dpi` into g (FMT_PARGB, transparent; physical px: ceil(w * dpi / 96) x
7//        ceil(h * dpi / 96)) -- called again for every new dpi
8//   cursor_custom(handle)      the cursor that hit boxes with CUR_CUSTOM show (their
9//                              next_flags(CUR_CUSTOM << HB_CURSOR_SHIFT)); set it each
10//                              frame before the widget; 0 = the crosshair
11//   cursor_render(handle, dpi) -> Some(surface)   the cursor drawn into a new surface
12//                              (FMT_PARGB; tests, previews)
13//   cursor_current() -> handle the handle CUR_CUSTOM stands for (0 = none)
14//   cursor_replace(handle, f, w, h, hot) -> bool   (E3) a new drawing for a live handle (the
15//                              platform's cursor is made again when it is shown next)
16//   cursor_free(handle)        (E3) the slot is free again (cursor_new may hand it out); a
17//                              hit box showing it gets the crosshair
18//   cursor_next(shape)         (E3) the cursor of the next hit box: any CUR_* -- the system
19//                              shapes CUR_X0 .. CUR_X_END (not-allowed, grab, the resize
20//                              shapes ...) that do not fit into the 4 flag bits, CUR_CUSTOM
21//   cursor_at(x, y) -> shape   (ctx.rs) the cursor at a point (tests, an engine's own hover)
22// Internal: cursor_resolve(shape) -> the plat shape for a hit box cursor (CUR_CUSTOM: the
23// custom cursor made for the current window's dpi with plat::cursor_create, rebuilt when
24// that dpi changes; the crosshair when there is none or the platform has no custom
25// cursors, e.g. headless runs).
26
27use alloc::rc::Rc;
28use alloc::vec::Vec;
29
30use super::cmd::Gfx;
31use super::consts::*;
32use super::Ui;
33use gfx::{Surface, FMT_PARGB};
34
35/// a cursor's draw function: f(g, dpi) draws into the cursor surface
36pub type CursorDraw = Rc<dyn Fn(&mut Gfx, i32)>;
37
38// one custom cursor slot  [UC_*]
39#[derive(Clone)]
40struct UCur {
41    f: CursorDraw,
42    w: u32,
43    h: u32,
44    hot: u32,
45    /// plat shape (-1 none, -2 could not be made at dpi)
46    plat: i32,
47    dpi: u32,
48}
49
50/// the cursor slots and the current custom cursor
51#[derive(Default)]
52pub(crate) struct S {
53    slots: Vec<Option<UCur>>,
54    cur: u32,
55}
56
57// uc_px(dip, dpi) -> ceil(dip * dpi / 96)
58fn uc_px(dip: u32, dpi: u32) -> u32 {
59    dip.wrapping_mul(dpi).wrapping_add(95) / 96
60}
61
62impl Ui {
63    /// ui_cursor_new(f, w, h, hot) -> handle (0 = none)
64    pub fn cursor_new(&mut self, f: CursorDraw, w: u32, h: u32, hot: u32) -> u32 {
65        let s = &mut self.ucur.slots;
66        if s.is_empty() {
67            s.resize(CUR_USER_MAX as usize, None);
68        }
69        let Some(i) = s.iter().position(|x| x.is_none()) else { return 0 };
70        s[i] = Some(UCur { f, w, h, hot, plat: -1, dpi: 0 });
71        i as u32 + 1
72    }
73
74    /// ui_cursor_custom(handle): the cursor of CUR_CUSTOM hit boxes
75    pub fn cursor_custom(&mut self, h: u32) {
76        self.ucur.cur = h;
77    }
78
79    /// ui_cursor_current -> handle
80    pub fn cursor_current(&self) -> u32 {
81        self.ucur.cur
82    }
83
84    /// (E3) the cursor of the next hit box (canvas, button, hit_rect ...): any CUR_* shape --
85    /// also those beyond CUR_CUSTOM's 4 bits (CUR_NOT_ALLOWED, CUR_GRAB, CUR_SIZE_NE ...), a
86    /// custom cursor through CUR_CUSTOM; it wins over the hit box's flags.  A platform without
87    /// a shape shows plat::cursor_fallback's
88    pub fn cursor_next(&mut self, shape: u32) {
89        self.wp.lay.next_cursor = shape;
90    }
91
92    /// (E3) a new drawing for a live handle (f, w x h DIP, hot spot x | y << 16): the platform's
93    /// cursor is made again at the next show -> false for a handle that is not live
94    pub fn cursor_replace(&mut self, handle: u32, f: CursorDraw, w: u32, h: u32, hot: u32) -> bool {
95        let i = handle.wrapping_sub(1);
96        let Some(Some(s)) = self.ucur.slots.get_mut(i as usize).filter(|_| i < CUR_USER_MAX) else { return false };
97        if s.plat >= 0 {
98            plat::cursor_destroy(s.plat);
99        }
100        *s = UCur { f, w, h, hot, plat: -1, dpi: 0 };
101        true
102    }
103
104    /// (E3) free a handle of cursor_new: its platform cursor goes, the slot may be handed out
105    /// again; when it was the custom cursor, CUR_CUSTOM shows the crosshair until another is set
106    pub fn cursor_free(&mut self, handle: u32) {
107        let i = handle.wrapping_sub(1);
108        if i >= CUR_USER_MAX {
109            return;
110        }
111        if let Some(slot) = self.ucur.slots.get_mut(i as usize) {
112            if let Some(s) = slot.take() {
113                if s.plat >= 0 {
114                    plat::cursor_destroy(s.plat);
115                }
116            }
117        }
118        if self.ucur.cur == handle {
119            self.ucur.cur = 0;
120        }
121    }
122
123    // uc_rec(handle): the slot of a live handle
124    fn uc_rec(&self, h: u32) -> Option<&UCur> {
125        let i = h.wrapping_sub(1);
126        if i >= CUR_USER_MAX {
127            return None;
128        }
129        self.ucur.slots.get(i as usize)?.as_ref()
130    }
131
132    /// ui_cursor_render(handle, dpi) -> the cursor drawn at dpi into a new surface
133    pub fn cursor_render(&mut self, h: u32, dpi: i32) -> Option<Surface> {
134        let r = self.uc_rec(h)?.clone();
135        if (dpi as u32).wrapping_sub(24) > 960 - 24 {
136            return None; // 25 % .. 1000 %
137        }
138        let w = uc_px(r.w, dpi as u32);
139        let hh = uc_px(r.h, dpi as u32);
140        if w.wrapping_sub(1) >= CUR_MAX_PX as u32 || hh.wrapping_sub(1) >= CUR_MAX_PX as u32 {
141            return None;
142        }
143        let mut sf = Surface::new(w as i32, hh as i32, FMT_PARGB);
144        if sf.px.is_empty() {
145            return None;
146        }
147        self.dc.begin(&sf);
148        {
149            let mut g = Gfx { dc: &mut self.dc, sf: &mut sf, text: &mut self.text };
150            (r.f)(&mut g, dpi);
151        }
152        self.dc.end();
153        Some(sf)
154    }
155
156    /// ui_cursor_resolve(shape) -> the plat shape: CUR_CUSTOM becomes the custom cursor made
157    /// for the current window's dpi (the crosshair without one)
158    pub(crate) fn cursor_resolve(&mut self, shape: u32) -> u32 {
159        if shape != CUR_CUSTOM {
160            return shape;
161        }
162        let h = self.ucur.cur;
163        let Some(r) = self.uc_rec(h).cloned() else { return CUR_CROSS };
164        let dpi = plat::cursor_dpi(self.wp.rt.dpi) as u32; // dpi to draw for
165        if r.dpi == dpi {
166            if r.plat >= 0 {
167                return r.plat as u32;
168            }
169            if r.plat == -2 {
170                return CUR_CROSS; // failed at this dpi already
171            }
172        }
173        // a new image: forget the old cursor (the platform shows the arrow meanwhile)
174        if r.plat >= 0 {
175            plat::cursor_destroy(r.plat);
176        }
177        let i = (h - 1) as usize;
178        if let Some(s) = self.ucur.slots[i].as_mut() {
179            s.plat = -2;
180            s.dpi = dpi;
181        }
182        let Some(sf) = self.cursor_render(h, dpi as i32) else { return CUR_CROSS };
183        // rows of exactly w * 4 bytes for the platform
184        let (w, hh) = (sf.w as usize, sf.h as usize);
185        let mut px = Vec::with_capacity(w * hh * 4);
186        for y in 0..hh {
187            let o = y * sf.stride as usize;
188            px.extend_from_slice(&sf.px[o..o + w * 4]);
189        }
190        // hot spot in px
191        let hx = ((r.hot & 0xFFFF).wrapping_mul(dpi).wrapping_add(48) / 96) as i32;
192        let hy = ((r.hot >> 16).wrapping_mul(dpi).wrapping_add(48) / 96) as i32;
193        let id = plat::cursor_create(&px, w as i32, hh as i32, hx, hy, dpi as i32);
194        if id >= 0 {
195            if let Some(s) = self.ucur.slots[i].as_mut() {
196                s.plat = id;
197            }
198            return id as u32;
199        }
200        CUR_CROSS
201    }
202}