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.iter().any(move |&w| w == 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    /// Search for the interactivity value associated with the widget in 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    /// Interactivity of the widget.
323    pub fn interactivity(&self) -> Interactivity {
324        let mut interactivity = Interactivity::ENABLED;
325        let len = self.path.path.len();
326        if self.blocked < len {
327            interactivity |= Interactivity::BLOCKED;
328        }
329        if self.disabled < len {
330            interactivity |= Interactivity::DISABLED;
331        }
332        interactivity
333    }
334
335    /// Zip widgets and interactivity.
336    pub fn zip(&self) -> impl DoubleEndedIterator<Item = (WidgetId, Interactivity)> + ExactSizeIterator + '_ {
337        self.path.widgets_path().iter().copied().zip(self.interaction_path())
338    }
339
340    /// Gets the [`ENABLED`] or [`DISABLED`] part of the path, or none if the widget is blocked at the root.
341    ///
342    /// [`ENABLED`]: Interactivity::ENABLED
343    /// [`DISABLED`]: Interactivity::DISABLED
344    pub fn unblocked(self) -> Option<InteractionPath> {
345        if self.blocked < self.path.path.len() {
346            if self.blocked == 0 {
347                return None;
348            }
349            Some(InteractionPath {
350                path: WidgetPath {
351                    window_id: self.path.window_id,
352                    path: self.path.path[..self.blocked].to_vec().into(),
353                },
354                blocked: self.blocked,
355                disabled: self.disabled,
356            })
357        } else {
358            Some(self)
359        }
360    }
361
362    /// Gets the [`ENABLED`] part of the path, or none if the widget is not enabled at the root.
363    ///
364    /// [`ENABLED`]: Interactivity::ENABLED
365    pub fn enabled(self) -> Option<WidgetPath> {
366        let enabled_end = self.blocked.min(self.disabled);
367
368        if enabled_end < self.path.path.len() {
369            if enabled_end == 0 {
370                return None;
371            }
372            Some(WidgetPath {
373                window_id: self.path.window_id,
374                path: self.path.path[..enabled_end].to_vec().into(),
375            })
376        } else {
377            Some(self.path)
378        }
379    }
380
381    /// Make a path to an ancestor id that is contained in the current path.
382    pub fn ancestor_path(&self, ancestor_id: WidgetId) -> Option<Cow<InteractionPath>> {
383        self.widgets_path().iter().position(|&id| id == ancestor_id).map(|i| {
384            if i == self.path.path.len() - 1 {
385                Cow::Borrowed(self)
386            } else {
387                Cow::Owned(InteractionPath {
388                    path: WidgetPath {
389                        window_id: self.window_id,
390                        path: self.path.path[..=i].to_vec().into(),
391                    },
392                    blocked: self.blocked,
393                    disabled: self.disabled,
394                })
395            }
396        })
397    }
398
399    /// Get the inner most widget parent shared by both `self` and `other` with the same interactivity.
400    pub fn shared_ancestor<'a>(&'a self, other: &'a InteractionPath) -> Option<Cow<'a, InteractionPath>> {
401        if self.window_id == other.window_id {
402            if let Some(i) = self.zip().zip(other.zip()).position(|(a, b)| a != b) {
403                if i == 0 {
404                    None
405                } else {
406                    let path = self.path.path[..i].to_vec().into();
407                    Some(Cow::Owned(InteractionPath {
408                        path: WidgetPath {
409                            window_id: self.window_id,
410                            path,
411                        },
412                        blocked: self.blocked,
413                        disabled: self.disabled,
414                    }))
415                }
416            } else if self.path.path.len() <= other.path.path.len() {
417                Some(Cow::Borrowed(self))
418            } else {
419                Some(Cow::Borrowed(other))
420            }
421        } else {
422            None
423        }
424    }
425
426    /// Gets a path to the root widget of this path.
427    pub fn root_path(&self) -> Cow<InteractionPath> {
428        if self.path.path.len() == 1 {
429            Cow::Borrowed(self)
430        } else {
431            Cow::Owned(InteractionPath {
432                path: WidgetPath {
433                    window_id: self.window_id,
434                    path: Arc::new(vec![self.path.path[0]]),
435                },
436                blocked: self.blocked,
437                disabled: self.disabled,
438            })
439        }
440    }
441
442    /// Gets a sub-path up to `widget_id` (inclusive), or `None` if the widget is not in the path.
443    pub fn sub_path(&self, widget_id: WidgetId) -> Option<Cow<InteractionPath>> {
444        if widget_id == self.widget_id() {
445            Some(Cow::Borrowed(self))
446        } else {
447            let path = self.path.sub_path(widget_id)?;
448            Some(Cow::Owned(Self {
449                path: path.into_owned(),
450                blocked: self.blocked,
451                disabled: self.disabled,
452            }))
453        }
454    }
455}
456impl ops::Deref for InteractionPath {
457    type Target = WidgetPath;
458
459    fn deref(&self) -> &Self::Target {
460        &self.path
461    }
462}
463impl From<InteractionPath> for WidgetPath {
464    fn from(p: InteractionPath) -> Self {
465        p.path
466    }
467}