Skip to main content

yog_ui/
surface.rs

1//! Surfaces — widgets that draw themselves.
2//!
3//! A [`Surface`] paints its box with any `yog-gfx` call (shaders, buffers,
4//! [`Draw2D`](yog_gfx::draw2d::Draw2D)) and may take the pointer: a
5//! casting grid, a graph, a minimap. The tree refers to one by id
6//! ([`widget::surface`](crate::widget::surface), `.yui` `<Surface
7//! source="mymod:grid"/>`); the code registers the drawing object under
8//! that id with [`register`]. An id nothing is registered under draws
9//! nothing.
10//!
11//! ```ignore
12//! struct Graph;
13//! impl yog_ui::Surface for Graph {
14//!     fn paint(&self, gfx: &GfxContext, rect: Rect) {
15//!         gfx.draw2d().rect(rect.x, rect.y, rect.x + rect.w, rect.y + 1.0, 0xFFFFFFFF);
16//!     }
17//! }
18//! yog_ui::surface::register("mymod:graph", std::sync::Arc::new(Graph));
19//! ```
20
21use std::collections::HashMap;
22use std::sync::{Arc, Mutex};
23
24use yog_gfx::GfxContext;
25
26use crate::layout::Rect;
27
28/// A pointer event on a surface, in coordinates local to its box (`0,0` =
29/// top-left corner).
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum Pointer {
32    /// A button went down over the surface; it gets the drags and the
33    /// release that follow, wherever the cursor goes.
34    Down { x: f32, y: f32 },
35    /// The cursor moved with the button held.
36    Drag { x: f32, y: f32 },
37    /// The button came up.
38    Up { x: f32, y: f32 },
39    /// The cursor moved over the surface, no button held.
40    Move { x: f32, y: f32 },
41    /// The cursor left the surface.
42    Leave,
43}
44
45/// What a surface widget draws and how it takes the pointer — see the
46/// [module docs](self).
47pub trait Surface: Send + Sync {
48    /// Draws into `rect` (screen GUI pixels, the widget's box less its
49    /// padding). Called every frame the widget is visible.
50    fn paint(&self, gfx: &GfxContext, rect: Rect);
51
52    /// A pointer event; `true` = used (a `Down` that returns `false` lets
53    /// the click through to what's under the surface). `size` is the box's
54    /// width and height.
55    fn pointer(&self, ev: Pointer, size: (f32, f32)) -> bool {
56        let _ = (ev, size);
57        false
58    }
59
60    /// The mouse wheel turned over the surface (`dy` > 0 = up); `true` =
61    /// used (scroll containers around it then don't scroll).
62    fn scroll(&self, dy: f32, size: (f32, f32)) -> bool {
63        let _ = (dy, size);
64        false
65    }
66}
67
68type Registry = HashMap<String, Arc<dyn Surface>>;
69
70static SURFACES: Mutex<Option<Registry>> = Mutex::new(None);
71
72/// Makes `surface` what `<Surface source="id"/>` widgets draw (replacing
73/// the one registered before).
74pub fn register(id: impl Into<String>, surface: Arc<dyn Surface>) {
75    SURFACES
76        .lock()
77        .unwrap_or_else(|e| e.into_inner())
78        .get_or_insert_with(HashMap::new)
79        .insert(id.into(), surface);
80}
81
82/// Drops the surface registered under `id`.
83pub fn unregister(id: &str) {
84    if let Some(map) = SURFACES.lock().unwrap_or_else(|e| e.into_inner()).as_mut() {
85        map.remove(id);
86    }
87}
88
89/// The surface registered under `id`.
90pub fn get(id: &str) -> Option<Arc<dyn Surface>> {
91    SURFACES
92        .lock()
93        .unwrap_or_else(|e| e.into_inner())
94        .as_ref()?
95        .get(id)
96        .cloned()
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::sync::atomic::{AtomicU32, Ordering};
103
104    struct Counter(AtomicU32);
105    impl Surface for Counter {
106        fn paint(&self, _: &GfxContext, _: Rect) {}
107        fn pointer(&self, _: Pointer, _: (f32, f32)) -> bool {
108            self.0.fetch_add(1, Ordering::Relaxed);
109            true
110        }
111    }
112
113    #[test]
114    fn registry_finds_by_id() {
115        assert!(get("test:none").is_none());
116        let c = Arc::new(Counter(AtomicU32::new(0)));
117        register("test:counter", c.clone());
118        let s = get("test:counter").unwrap();
119        assert!(s.pointer(Pointer::Leave, (1.0, 1.0)));
120        assert_eq!(c.0.load(Ordering::Relaxed), 1);
121        unregister("test:counter");
122        assert!(get("test:counter").is_none());
123    }
124}