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
61type Registry = HashMap<String, Arc<dyn Surface>>;
62
63static SURFACES: Mutex<Option<Registry>> = Mutex::new(None);
64
65/// Makes `surface` what `<Surface source="id"/>` widgets draw (replacing
66/// the one registered before).
67pub fn register(id: impl Into<String>, surface: Arc<dyn Surface>) {
68    SURFACES
69        .lock()
70        .unwrap_or_else(|e| e.into_inner())
71        .get_or_insert_with(HashMap::new)
72        .insert(id.into(), surface);
73}
74
75/// Drops the surface registered under `id`.
76pub fn unregister(id: &str) {
77    if let Some(map) = SURFACES.lock().unwrap_or_else(|e| e.into_inner()).as_mut() {
78        map.remove(id);
79    }
80}
81
82/// The surface registered under `id`.
83pub fn get(id: &str) -> Option<Arc<dyn Surface>> {
84    SURFACES
85        .lock()
86        .unwrap_or_else(|e| e.into_inner())
87        .as_ref()?
88        .get(id)
89        .cloned()
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use std::sync::atomic::{AtomicU32, Ordering};
96
97    struct Counter(AtomicU32);
98    impl Surface for Counter {
99        fn paint(&self, _: &GfxContext, _: Rect) {}
100        fn pointer(&self, _: Pointer, _: (f32, f32)) -> bool {
101            self.0.fetch_add(1, Ordering::Relaxed);
102            true
103        }
104    }
105
106    #[test]
107    fn registry_finds_by_id() {
108        assert!(get("test:none").is_none());
109        let c = Arc::new(Counter(AtomicU32::new(0)));
110        register("test:counter", c.clone());
111        let s = get("test:counter").unwrap();
112        assert!(s.pointer(Pointer::Leave, (1.0, 1.0)));
113        assert_eq!(c.0.load(Ordering::Relaxed), 1);
114        unregister("test:counter");
115        assert!(get("test:counter").is_none());
116    }
117}