adui_dioxus/components/
overlay.rs

1use dioxus::prelude::*;
2use std::collections::HashMap;
3
4/// Kinds of overlay layers that share a common z-index space.
5///
6/// This enum is intentionally small. If we need more kinds in the future we can
7/// extend it without breaking existing behaviour. Tooltip/Popover/DropdownMenu
8/// 都会以合适的 kind 挂到 OverlayManager 上,便于统一分配 z-index。
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum OverlayKind {
11    /// Lightweight dropdown-like overlays used by selector components and
12    /// other popup menus.
13    Dropdown,
14    /// Simple, non-interactive tooltip bubbles.
15    Tooltip,
16    /// Rich popup panels such as Popover/Popconfirm.
17    Popup,
18    Message,
19    Notification,
20    Modal,
21    Drawer,
22}
23
24/// Identifier for a single overlay instance managed by `OverlayManager`.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub struct OverlayKey(u64);
27
28impl OverlayKey {
29    /// Expose the raw numeric id for logging or debugging purposes.
30    pub fn as_u64(self) -> u64 {
31        self.0
32    }
33}
34
35/// Lightweight metadata attached to a registered overlay.
36#[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/// Internal state for all overlays within a single App tree.
44///
45/// The manager does not know about the concrete UI of each overlay type. It
46/// only allocates z-index values and tracks basic metadata so that visual
47/// layers can be rendered in a stable order.
48#[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            // AntD 的浮层默认 z-index 区间大致在 1000+,这里选择一个接近的起点,
61            // 后续如有需要可以通过 ConfigProvider 提供全局配置入口。
62            base_z_index: 1000,
63            // 为不同 overlay 预留间隔,便于未来在类型内部再做细分。
64            step: 10,
65            entries: HashMap::new(),
66        }
67    }
68}
69
70impl OverlayManager {
71    /// Open a new overlay of the given kind.
72    ///
73    /// Returns the allocated key together with the computed metadata so the
74    /// caller can immediately use the z-index.
75    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    /// Update a subset of the metadata for an existing overlay.
90    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    /// Close a single overlay.
101    pub fn close(&mut self, key: OverlayKey) {
102        self.entries.remove(&key);
103    }
104
105    /// Close all overlays managed by this instance.
106    pub fn close_all(&mut self) {
107        self.entries.clear();
108    }
109
110    /// Return an iterator over all active overlays. The order of iteration is
111    /// not guaranteed to be stable; callers that care about z-index ordering
112    /// should sort by `z_index`.
113    pub fn entries(&self) -> impl Iterator<Item = (&OverlayKey, &OverlayMeta)> {
114        self.entries.iter()
115    }
116
117    /// Return the highest z-index currently allocated, or the base value if
118    /// there are no overlays.
119    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/// Handle used by components to interact with the overlay manager through a
139/// Dioxus signal. This is designed to be provided once near the root of the
140/// app (for example inside the future `App` component) and then consumed via
141/// `use_overlay` in child components.
142#[derive(Clone)]
143pub struct OverlayHandle {
144    state: Signal<OverlayManager>,
145}
146
147impl OverlayHandle {
148    /// Register a new overlay and receive its key and metadata.
149    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    /// Update metadata for an existing overlay.
155    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    /// Close a specific overlay.
161    pub fn close(&self, key: OverlayKey) {
162        let mut state = self.state;
163        state.write().close(key);
164    }
165
166    /// Close all overlays.
167    pub fn close_all(&self) {
168        let mut state = self.state;
169        state.write().close_all();
170    }
171
172    /// Snapshot the current manager state. This is intended for read-only
173    /// operations such as rendering overlay layers.
174    pub fn snapshot(&self) -> OverlayManager {
175        self.state.read().clone()
176    }
177}
178
179/// Create an `OverlayHandle` and install it into the current scope's context.
180///
181/// This should typically be called once in a high-level component that owns
182/// the overlay surface (for example the `App` component in a real应用). Child
183/// components can then obtain the handle via [`use_overlay`].
184pub 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
191/// Retrieve the `OverlayHandle` from context if it has been installed.
192///
193/// This returns `None` when no provider is present. Callers may choose to
194/// fall back to local behaviour in that case.
195pub 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
223    #[test]
224    fn overlay_manager_close_all() {
225        let mut mgr = OverlayManager::default();
226        mgr.open(OverlayKind::Modal, true);
227        mgr.open(OverlayKind::Drawer, false);
228        mgr.open(OverlayKind::Tooltip, false);
229        assert_eq!(mgr.entries().count(), 3);
230        mgr.close_all();
231        assert_eq!(mgr.entries().count(), 0);
232    }
233
234    #[test]
235    fn overlay_manager_current_top_z_index_with_no_overlays() {
236        let mgr = OverlayManager::default();
237        assert_eq!(mgr.current_top_z_index(), 1000);
238    }
239
240    #[test]
241    fn overlay_manager_current_top_z_index_with_overlays() {
242        let mut mgr = OverlayManager::default();
243        let (_k1, m1) = mgr.open(OverlayKind::Modal, true);
244        let (_k2, m2) = mgr.open(OverlayKind::Drawer, false);
245        let (_k3, m3) = mgr.open(OverlayKind::Tooltip, false);
246        assert_eq!(mgr.current_top_z_index(), m3.z_index);
247        assert!(m3.z_index > m2.z_index);
248        assert!(m2.z_index > m1.z_index);
249    }
250
251    #[test]
252    fn overlay_manager_update_nonexistent_key() {
253        let mut mgr = OverlayManager::default();
254        let fake_key = OverlayKey(999);
255        assert!(mgr.update(fake_key, Some(true)).is_none());
256    }
257
258    #[test]
259    fn overlay_manager_close_nonexistent_key() {
260        let mut mgr = OverlayManager::default();
261        let fake_key = OverlayKey(999);
262        mgr.close(fake_key);
263        assert_eq!(mgr.entries().count(), 0);
264    }
265
266    #[test]
267    fn overlay_manager_update_without_mask_change() {
268        let mut mgr = OverlayManager::default();
269        let (key, _meta) = mgr.open(OverlayKind::Popup, true);
270        let updated = mgr.update(key, None);
271        assert!(updated.is_some());
272        assert_eq!(updated.unwrap().has_mask, true);
273    }
274
275    #[test]
276    fn overlay_key_as_u64() {
277        let mut mgr = OverlayManager::default();
278        let (key, _) = mgr.open(OverlayKind::Modal, true);
279        assert_eq!(key.as_u64(), 1);
280        let (key2, _) = mgr.open(OverlayKind::Drawer, false);
281        assert_eq!(key2.as_u64(), 2);
282    }
283
284    #[test]
285    fn overlay_kind_all_variants() {
286        assert_eq!(OverlayKind::Dropdown, OverlayKind::Dropdown);
287        assert_eq!(OverlayKind::Tooltip, OverlayKind::Tooltip);
288        assert_eq!(OverlayKind::Popup, OverlayKind::Popup);
289        assert_eq!(OverlayKind::Message, OverlayKind::Message);
290        assert_eq!(OverlayKind::Notification, OverlayKind::Notification);
291        assert_eq!(OverlayKind::Modal, OverlayKind::Modal);
292        assert_eq!(OverlayKind::Drawer, OverlayKind::Drawer);
293    }
294
295    #[test]
296    fn overlay_meta_clone() {
297        let meta = OverlayMeta {
298            kind: OverlayKind::Modal,
299            z_index: 1000,
300            has_mask: true,
301        };
302        let cloned = meta;
303        assert_eq!(meta, cloned);
304    }
305}