zng_app/widget/info/
path.rs

1use crate::{widget::WidgetId, window::WindowId};
2
3use super::*;
4
5/// Full address of a widget.
6///
7/// The path is reference counted, cloning this struct does not alloc.
8#[derive(Clone)]
9pub struct WidgetPath {
10    window_id: WindowId,
11    path: Arc<Vec<WidgetId>>,
12}
13impl PartialEq for WidgetPath {
14    /// Paths are equal if they share the same [window](Self::window_id) and [widget paths](Self::widgets_path).
15    fn eq(&self, other: &Self) -> bool {
16        self.window_id == other.window_id && self.path == other.path
17    }
18}
19impl Eq for WidgetPath {}
20impl PartialEq<InteractionPath> for WidgetPath {
21    fn eq(&self, other: &InteractionPath) -> bool {
22        other == self
23    }
24}
25impl fmt::Debug for WidgetPath {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        if f.alternate() {
28            f.debug_struct("WidgetPath")
29                .field("window_id", &self.window_id)
30                .field("path", &self.path)
31                .finish_non_exhaustive()
32        } else {
33            write!(f, "{self}")
34        }
35    }
36}
37impl fmt::Display for WidgetPath {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "{}//", self.window_id)?;
40        for w in self.ancestors() {
41            write!(f, "{w}/")?;
42        }
43        write!(f, "{}", self.widget_id())
44    }
45}
46impl WidgetPath {
47    /// New custom widget path.
48    pub fn new(window_id: WindowId, path: Arc<Vec<WidgetId>>) -> WidgetPath {
49        WidgetPath { window_id, path }
50    }
51
52    /// Into internal parts.
53    pub fn into_parts(self) -> (WindowId, Arc<Vec<WidgetId>>) {
54        (self.window_id, self.path)
55    }
56
57    /// Id of the window that contains the widgets.
58    pub fn window_id(&self) -> WindowId {
59        self.window_id
60    }
61
62    /// Widgets that contain [`widget_id`](WidgetPath::widget_id), root first.
63    pub fn ancestors(&self) -> &[WidgetId] {
64        &self.path[..self.path.len() - 1]
65    }
66
67    /// The widget.
68    pub fn widget_id(&self) -> WidgetId {
69        self.path[self.path.len() - 1]
70    }
71
72    /// The widget parent, if it is not the root widget.
73    pub fn parent_id(&self) -> Option<WidgetId> {
74        self.ancestors().iter().copied().next_back()
75    }
76
77    /// [`ancestors`](WidgetPath::ancestors) and [`widget_id`](WidgetPath::widget_id), root first.
78    pub fn widgets_path(&self) -> &[WidgetId] {
79        &self.path[..]
80    }
81
82    /// If the `widget_id` is part of the path.
83    pub fn contains(&self, widget_id: WidgetId) -> bool {
84        self.path.contains(&widget_id)
85    }
86
87    /// Make a path to an ancestor id that is contained in the current path.
88    pub fn ancestor_path(&self, ancestor_id: WidgetId) -> Option<Cow<'_, WidgetPath>> {
89        self.path.iter().position(|&id| id == ancestor_id).map(|i| {
90            if i == self.path.len() - 1 {
91                Cow::Borrowed(self)
92            } else {
93                Cow::Owned(WidgetPath {
94                    window_id: self.window_id,
95                    path: self.path[..i].to_vec().into(),
96                })
97            }
98        })
99    }
100
101    /// Get the inner most widget parent shared by both `self` and `other`.
102    pub fn shared_ancestor<'a>(&'a self, other: &'a WidgetPath) -> Option<Cow<'a, WidgetPath>> {
103        if self.window_id == other.window_id {
104            if let Some(i) = self.path.iter().zip(other.path.iter()).position(|(a, b)| a != b) {
105                if i == 0 {
106                    None
107                } else {
108                    let path = self.path[..i].to_vec().into();
109                    Some(Cow::Owned(WidgetPath {
110                        window_id: self.window_id,
111                        path,
112                    }))
113                }
114            } else if self.path.len() <= other.path.len() {
115                Some(Cow::Borrowed(self))
116            } else {
117                Some(Cow::Borrowed(other))
118            }
119        } else {
120            None
121        }
122    }
123
124    /// Gets a path to the root widget of this path.
125    pub fn root_path(&self) -> Cow<'_, WidgetPath> {
126        if self.path.len() == 1 {
127            Cow::Borrowed(self)
128        } else {
129            Cow::Owned(WidgetPath {
130                window_id: self.window_id,
131                path: Arc::new(vec![self.path[0]]),
132            })
133        }
134    }
135
136    /// Gets a path to the `widget_id` of this path.
137    pub fn sub_path(&self, widget_id: WidgetId) -> Option<Cow<'_, WidgetPath>> {
138        if self.widget_id() == widget_id {
139            Some(Cow::Borrowed(self))
140        } else {
141            let i = self.path.iter().position(|&id| id == widget_id)?;
142            let path = Self::new(self.window_id, Arc::new(self.path[..=i].to_vec()));
143            Some(Cow::Owned(path))
144        }
145    }
146}
147
148/// Represents a [`WidgetPath`] annotated with each widget's [`Interactivity`].
149#[derive(Clone)]
150pub struct InteractionPath {
151    path: WidgetPath,
152    blocked: usize,
153    disabled: usize,
154}
155impl PartialEq for InteractionPath {
156    /// Paths are equal if the are the same window, widgets and interactivity.
157    fn eq(&self, other: &Self) -> bool {
158        self.as_path() == other.as_path() && self.blocked == other.blocked && self.disabled == other.disabled
159    }
160}
161impl Eq for InteractionPath {}
162impl PartialEq<WidgetPath> for InteractionPath {
163    /// Paths are equal if the are the same window, widgets and interactivity.
164    fn eq(&self, other: &WidgetPath) -> bool {
165        self.as_path() == other
166    }
167}
168impl fmt::Debug for InteractionPath {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        if f.alternate() {
171            f.debug_struct("InteractionPath")
172                .field("window_id", &self.window_id)
173                .field("path", &self.path)
174                .field("blocked", &self.blocked_index())
175                .field("disabled", &self.disabled_index())
176                .finish_non_exhaustive()
177        } else {
178            write!(f, "{self}")
179        }
180    }
181}
182impl fmt::Display for InteractionPath {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        write!(f, "{}//", self.window_id)?;
185        let mut sep = "";
186        for (w, i) in self.zip() {
187            write!(f, "{sep}{w}{{{i:?}}}")?;
188            sep = "/";
189        }
190        Ok(())
191    }
192}
193impl InteractionPath {
194    pub(super) fn new_internal(path: WidgetPath, blocked: usize, disabled: usize) -> Self {
195        Self { path, blocked, disabled }
196    }
197
198    /// New custom path.
199    pub fn new<P: IntoIterator<Item = (WidgetId, Interactivity)>>(window_id: WindowId, path: P) -> InteractionPath {
200        let iter = path.into_iter();
201        let mut path = Vec::with_capacity(iter.size_hint().0);
202        let mut blocked = None;
203        let mut disabled = None;
204        for (i, (w, interactivity)) in iter.enumerate() {
205            path.push(w);
206            if blocked.is_none() && interactivity.contains(Interactivity::BLOCKED) {
207                blocked = Some(i);
208            }
209            if disabled.is_none() && interactivity.contains(Interactivity::DISABLED) {
210                disabled = Some(i);
211            }
212        }
213        let len = path.len();
214        InteractionPath {
215            path: WidgetPath::new(window_id, path.into()),
216            blocked: blocked.unwrap_or(len),
217            disabled: disabled.unwrap_or(len),
218        }
219    }
220
221    /// New custom path with all widgets enabled.
222    pub fn new_enabled(window_id: WindowId, path: Arc<Vec<WidgetId>>) -> InteractionPath {
223        let path = WidgetPath::new(window_id, path);
224        Self::from_enabled(path)
225    }
226
227    /// New interactivity path with all widgets enabled.
228    pub fn from_enabled(path: WidgetPath) -> InteractionPath {
229        let len = path.path.len();
230        InteractionPath {
231            path,
232            blocked: len,
233            disabled: len,
234        }
235    }
236
237    /// Dereferences to the path.
238    pub fn as_path(&self) -> &WidgetPath {
239        &self.path
240    }
241
242    /// Index of first [`BLOCKED`].
243    ///
244    /// [`BLOCKED`]: Interactivity::BLOCKED
245    pub fn blocked_index(&self) -> Option<usize> {
246        if self.blocked < self.path.path.len() {
247            Some(self.blocked)
248        } else {
249            None
250        }
251    }
252    /// Index of first [`DISABLED`].
253    ///
254    /// [`DISABLED`]: Interactivity::DISABLED
255    pub fn disabled_index(&self) -> Option<usize> {
256        if self.disabled < self.path.path.len() {
257            Some(self.disabled)
258        } else {
259            None
260        }
261    }
262
263    /// Interactivity for each widget, root first.
264    pub fn interaction_path(&self) -> impl DoubleEndedIterator<Item = Interactivity> + ExactSizeIterator {
265        struct InteractivityIter {
266            range: ops::Range<usize>,
267            blocked: usize,
268            disabled: usize,
269        }
270
271        impl InteractivityIter {
272            fn interactivity(&self, i: usize) -> Interactivity {
273                let mut interactivity = Interactivity::ENABLED;
274                if self.blocked <= i {
275                    interactivity |= Interactivity::BLOCKED;
276                }
277                if self.disabled <= i {
278                    interactivity |= Interactivity::DISABLED;
279                }
280                interactivity
281            }
282        }
283        impl Iterator for InteractivityIter {
284            type Item = Interactivity;
285
286            fn next(&mut self) -> Option<Self::Item> {
287                self.range.next().map(|i| self.interactivity(i))
288            }
289
290            fn size_hint(&self) -> (usize, Option<usize>) {
291                (self.range.len(), Some(self.range.len()))
292            }
293        }
294        impl ExactSizeIterator for InteractivityIter {}
295        impl DoubleEndedIterator for InteractivityIter {
296            fn next_back(&mut self) -> Option<Self::Item> {
297                self.range.next_back().map(|i| self.interactivity(i))
298            }
299        }
300
301        InteractivityIter {
302            range: 0..self.path.path.len(),
303            blocked: self.blocked,
304            disabled: self.disabled,
305        }
306    }
307
308    /// Gets the interactivity of `widget_id`, if its present on the path.
309    pub fn interactivity_of(&self, widget_id: WidgetId) -> Option<Interactivity> {
310        self.path.widgets_path().iter().position(|&w| w == widget_id).map(|i| {
311            let mut interactivity = Interactivity::ENABLED;
312            if self.blocked <= i {
313                interactivity |= Interactivity::BLOCKED;
314            }
315            if self.disabled <= i {
316                interactivity |= Interactivity::DISABLED;
317            }
318            interactivity
319        })
320    }
321
322    /// Gets if the `widget_id` is on the path and enabled.
323    ///
324    /// Returns `false` if the `widget_id` is disabled or not in the path.
325    pub fn contains_enabled(&self, widget_id: WidgetId) -> bool {
326        self.interactivity_of(widget_id).map(Interactivity::is_enabled).unwrap_or(false)
327    }
328
329    /// Gets if the `widget_id` is on the path and visually enabled.
330    ///
331    /// Returns `false` if the `widget_id` is not visually enabled or not in the path.
332    pub fn contains_vis_enabled(&self, widget_id: WidgetId) -> bool {
333        self.interactivity_of(widget_id).map(Interactivity::is_vis_enabled).unwrap_or(false)
334    }
335
336    /// Gets if the `widget_id` is on the path and disabled.
337    ///
338    /// Returns `false` if the `widget_id` is enabled or not in the path.
339    pub fn contains_disabled(&self, widget_id: WidgetId) -> bool {
340        self.interactivity_of(widget_id).map(Interactivity::is_disabled).unwrap_or(false)
341    }
342
343    /// Gets if the `widget_id` is on the path and blocked.
344    ///
345    /// Returns `false` if the `widget_id` is not blocked or not in the path.
346    pub fn contains_blocked(&self, widget_id: WidgetId) -> bool {
347        self.interactivity_of(widget_id).map(Interactivity::is_blocked).unwrap_or(false)
348    }
349
350    /// Interactivity of the widget.
351    pub fn interactivity(&self) -> Interactivity {
352        let mut interactivity = Interactivity::ENABLED;
353        let len = self.path.path.len();
354        if self.blocked < len {
355            interactivity |= Interactivity::BLOCKED;
356        }
357        if self.disabled < len {
358            interactivity |= Interactivity::DISABLED;
359        }
360        interactivity
361    }
362
363    /// Zip widgets and interactivity.
364    pub fn zip(&self) -> impl DoubleEndedIterator<Item = (WidgetId, Interactivity)> + ExactSizeIterator + '_ {
365        self.path.widgets_path().iter().copied().zip(self.interaction_path())
366    }
367
368    /// Gets the [`ENABLED`] or [`DISABLED`] part of the path, or none if the widget is blocked at the root.
369    ///
370    /// [`ENABLED`]: Interactivity::ENABLED
371    /// [`DISABLED`]: Interactivity::DISABLED
372    pub fn unblocked(self) -> Option<InteractionPath> {
373        if self.blocked < self.path.path.len() {
374            if self.blocked == 0 {
375                return None;
376            }
377            Some(InteractionPath {
378                path: WidgetPath {
379                    window_id: self.path.window_id,
380                    path: self.path.path[..self.blocked].to_vec().into(),
381                },
382                blocked: self.blocked,
383                disabled: self.disabled,
384            })
385        } else {
386            Some(self)
387        }
388    }
389
390    /// Gets the [`ENABLED`] part of the path, or none if the widget is not enabled at the root.
391    ///
392    /// [`ENABLED`]: Interactivity::ENABLED
393    pub fn enabled(self) -> Option<WidgetPath> {
394        let enabled_end = self.blocked.min(self.disabled);
395
396        if enabled_end < self.path.path.len() {
397            if enabled_end == 0 {
398                return None;
399            }
400            Some(WidgetPath {
401                window_id: self.path.window_id,
402                path: self.path.path[..enabled_end].to_vec().into(),
403            })
404        } else {
405            Some(self.path)
406        }
407    }
408
409    /// Make a path to an ancestor id that is contained in the current path.
410    pub fn ancestor_path(&self, ancestor_id: WidgetId) -> Option<Cow<'_, InteractionPath>> {
411        self.widgets_path().iter().position(|&id| id == ancestor_id).map(|i| {
412            if i == self.path.path.len() - 1 {
413                Cow::Borrowed(self)
414            } else {
415                Cow::Owned(InteractionPath {
416                    path: WidgetPath {
417                        window_id: self.window_id,
418                        path: self.path.path[..=i].to_vec().into(),
419                    },
420                    blocked: self.blocked,
421                    disabled: self.disabled,
422                })
423            }
424        })
425    }
426
427    /// Get the inner most widget parent shared by both `self` and `other` with the same interactivity.
428    pub fn shared_ancestor<'a>(&'a self, other: &'a InteractionPath) -> Option<Cow<'a, InteractionPath>> {
429        if self.window_id == other.window_id {
430            if let Some(i) = self.zip().zip(other.zip()).position(|(a, b)| a != b) {
431                if i == 0 {
432                    None
433                } else {
434                    let path = self.path.path[..i].to_vec().into();
435                    Some(Cow::Owned(InteractionPath {
436                        path: WidgetPath {
437                            window_id: self.window_id,
438                            path,
439                        },
440                        blocked: self.blocked,
441                        disabled: self.disabled,
442                    }))
443                }
444            } else if self.path.path.len() <= other.path.path.len() {
445                Some(Cow::Borrowed(self))
446            } else {
447                Some(Cow::Borrowed(other))
448            }
449        } else {
450            None
451        }
452    }
453
454    /// Gets a path to the root widget of this path.
455    pub fn root_path(&self) -> Cow<'_, InteractionPath> {
456        if self.path.path.len() == 1 {
457            Cow::Borrowed(self)
458        } else {
459            Cow::Owned(InteractionPath {
460                path: WidgetPath {
461                    window_id: self.window_id,
462                    path: Arc::new(vec![self.path.path[0]]),
463                },
464                blocked: self.blocked,
465                disabled: self.disabled,
466            })
467        }
468    }
469
470    /// Gets a sub-path up to `widget_id` (inclusive), or `None` if the widget is not in the path.
471    pub fn sub_path(&self, widget_id: WidgetId) -> Option<Cow<'_, InteractionPath>> {
472        if widget_id == self.widget_id() {
473            Some(Cow::Borrowed(self))
474        } else {
475            let path = self.path.sub_path(widget_id)?;
476            Some(Cow::Owned(Self {
477                path: path.into_owned(),
478                blocked: self.blocked,
479                disabled: self.disabled,
480            }))
481        }
482    }
483}
484impl ops::Deref for InteractionPath {
485    type Target = WidgetPath;
486
487    fn deref(&self) -> &Self::Target {
488        &self.path
489    }
490}
491impl From<InteractionPath> for WidgetPath {
492    fn from(p: InteractionPath) -> Self {
493        p.path
494    }
495}