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}