adui_dioxus/components/
overlay.rs1use dioxus::prelude::*;
2use std::collections::HashMap;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum OverlayKind {
11 Dropdown,
14 Tooltip,
16 Popup,
18 Message,
19 Notification,
20 Modal,
21 Drawer,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub struct OverlayKey(u64);
27
28impl OverlayKey {
29 pub fn as_u64(self) -> u64 {
31 self.0
32 }
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct OverlayMeta {
38 pub kind: OverlayKind,
39 pub z_index: i32,
40 pub has_mask: bool,
41}
42
43#[derive(Clone, Debug)]
49pub struct OverlayManager {
50 next_key: u64,
51 base_z_index: i32,
52 step: i32,
53 entries: HashMap<OverlayKey, OverlayMeta>,
54}
55
56impl Default for OverlayManager {
57 fn default() -> Self {
58 Self {
59 next_key: 1,
60 base_z_index: 1000,
63 step: 10,
65 entries: HashMap::new(),
66 }
67 }
68}
69
70impl OverlayManager {
71 pub fn open(&mut self, kind: OverlayKind, has_mask: bool) -> (OverlayKey, OverlayMeta) {
76 let key = OverlayKey(self.next_key);
77 self.next_key += 1;
78
79 let z_index = self.next_z_index();
80 let meta = OverlayMeta {
81 kind,
82 z_index,
83 has_mask,
84 };
85 self.entries.insert(key, meta);
86 (key, meta)
87 }
88
89 pub fn update(&mut self, key: OverlayKey, has_mask: Option<bool>) -> Option<OverlayMeta> {
91 if let Some(entry) = self.entries.get_mut(&key) {
92 if let Some(mask) = has_mask {
93 entry.has_mask = mask;
94 }
95 return Some(*entry);
96 }
97 None
98 }
99
100 pub fn close(&mut self, key: OverlayKey) {
102 self.entries.remove(&key);
103 }
104
105 pub fn close_all(&mut self) {
107 self.entries.clear();
108 }
109
110 pub fn entries(&self) -> impl Iterator<Item = (&OverlayKey, &OverlayMeta)> {
114 self.entries.iter()
115 }
116
117 pub fn current_top_z_index(&self) -> i32 {
120 self.entries
121 .values()
122 .map(|m| m.z_index)
123 .max()
124 .unwrap_or(self.base_z_index)
125 }
126
127 fn next_z_index(&self) -> i32 {
128 let top = self
129 .entries
130 .values()
131 .map(|m| m.z_index)
132 .max()
133 .unwrap_or(self.base_z_index - self.step);
134 top + self.step
135 }
136}
137
138#[derive(Clone)]
143pub struct OverlayHandle {
144 state: Signal<OverlayManager>,
145}
146
147impl OverlayHandle {
148 pub fn open(&self, kind: OverlayKind, has_mask: bool) -> (OverlayKey, OverlayMeta) {
150 let mut state = self.state;
151 state.write().open(kind, has_mask)
152 }
153
154 pub fn update(&self, key: OverlayKey, has_mask: Option<bool>) -> Option<OverlayMeta> {
156 let mut state = self.state;
157 state.write().update(key, has_mask)
158 }
159
160 pub fn close(&self, key: OverlayKey) {
162 let mut state = self.state;
163 state.write().close(key);
164 }
165
166 pub fn close_all(&self) {
168 let mut state = self.state;
169 state.write().close_all();
170 }
171
172 pub fn snapshot(&self) -> OverlayManager {
175 self.state.read().clone()
176 }
177}
178
179pub fn use_overlay_provider() -> OverlayHandle {
185 let state = use_signal(OverlayManager::default);
186 let handle = OverlayHandle { state };
187 use_context_provider(|| handle.clone());
188 handle
189}
190
191pub fn use_overlay() -> Option<OverlayHandle> {
196 try_use_context::<OverlayHandle>()
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn overlay_manager_allocates_monotonic_z_indices() {
205 let mut mgr = OverlayManager::default();
206 let (_k1, m1) = mgr.open(OverlayKind::Modal, true);
207 let (_k2, m2) = mgr.open(OverlayKind::Drawer, false);
208 assert!(m2.z_index > m1.z_index);
209 assert_eq!(mgr.current_top_z_index(), m2.z_index);
210 }
211
212 #[test]
213 fn overlay_manager_update_and_close_work() {
214 let mut mgr = OverlayManager::default();
215 let (key, meta) = mgr.open(OverlayKind::Message, false);
216 assert!(!meta.has_mask);
217 let updated = mgr.update(key, Some(true)).unwrap();
218 assert!(updated.has_mask);
219 mgr.close(key);
220 assert_eq!(mgr.entries().count(), 0);
221 }
222}