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::from_edges(rect.x, rect.y, rect.x + rect.w, rect.y + 1.0), Color(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}