iced_selector/
lib.rs

1//! Select data from the widget tree.
2use iced_core as core;
3
4mod find;
5mod target;
6
7pub use find::{Find, FindAll};
8pub use target::{Bounded, Candidate, Target, Text};
9
10use crate::core::Point;
11use crate::core::widget;
12
13/// A type that traverses the widget tree to "select" data and produce some output.
14pub trait Selector {
15    /// The output type of the [`Selector`].
16    ///
17    /// For most selectors, this will normally be a [`Target`]. However, some
18    /// selectors may want to return a more limited type to encode the selection
19    /// guarantees in the type system.
20    ///
21    /// For instance, the implementations of [`String`] and [`str`] of [`Selector`]
22    /// return a [`target::Text`] instead of a generic [`Target`], since they are
23    /// guaranteed to only select text.
24    type Output;
25
26    /// Performs a selection of the given [`Candidate`], if applicable.
27    ///
28    /// This method traverses the widget tree in depth-first order.
29    fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output>;
30
31    /// Returns a short description of the [`Selector`] for debugging purposes.
32    fn description(&self) -> String;
33
34    /// Returns a [`widget::Operation`] that runs the [`Selector`] and stops after
35    /// the first [`Output`](Self::Output) is produced.
36    fn find(self) -> Find<Self>
37    where
38        Self: Sized,
39    {
40        Find::new(find::One::new(self))
41    }
42
43    /// Returns a [`widget::Operation`] that runs the [`Selector`] for the entire
44    /// widget tree and aggregates all of its [`Output`](Self::Output).
45    fn find_all(self) -> FindAll<Self>
46    where
47        Self: Sized,
48    {
49        FindAll::new(find::All::new(self))
50    }
51}
52
53impl Selector for &str {
54    type Output = target::Text;
55
56    fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
57        match candidate {
58            Candidate::TextInput {
59                id,
60                bounds,
61                visible_bounds,
62                state,
63            } if state.text() == *self => Some(target::Text::Input {
64                id: id.cloned(),
65                bounds,
66                visible_bounds,
67            }),
68            Candidate::Text {
69                id,
70                bounds,
71                visible_bounds,
72                content,
73            } if content == *self => Some(target::Text::Raw {
74                id: id.cloned(),
75                bounds,
76                visible_bounds,
77            }),
78            _ => None,
79        }
80    }
81
82    fn description(&self) -> String {
83        format!("text == {self:?}")
84    }
85}
86
87impl Selector for String {
88    type Output = target::Text;
89
90    fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
91        self.as_str().select(candidate)
92    }
93
94    fn description(&self) -> String {
95        self.as_str().description()
96    }
97}
98
99impl Selector for widget::Id {
100    type Output = Target;
101
102    fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
103        if candidate.id() != Some(self) {
104            return None;
105        }
106
107        Some(Target::from(candidate))
108    }
109
110    fn description(&self) -> String {
111        format!("id == {self:?}")
112    }
113}
114
115impl Selector for Point {
116    type Output = Target;
117
118    fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
119        candidate
120            .visible_bounds()
121            .is_some_and(|visible_bounds| visible_bounds.contains(*self))
122            .then(|| Target::from(candidate))
123    }
124
125    fn description(&self) -> String {
126        format!("bounds contains {self:?}")
127    }
128}
129
130impl<F, T> Selector for F
131where
132    F: FnMut(Candidate<'_>) -> Option<T>,
133{
134    type Output = T;
135
136    fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
137        (self)(candidate)
138    }
139
140    fn description(&self) -> String {
141        format!("custom selector: {}", std::any::type_name_of_val(self))
142    }
143}
144
145/// Creates a new [`Selector`] that matches widgets with the given [`widget::Id`].
146pub fn id(id: impl Into<widget::Id>) -> impl Selector<Output = Target> {
147    id.into()
148}
149
150/// Returns a [`Selector`] that matches widgets that are currently focused.
151pub fn is_focused() -> impl Selector<Output = Target> {
152    struct IsFocused;
153
154    impl Selector for IsFocused {
155        type Output = Target;
156
157        fn select(&mut self, candidate: Candidate<'_>) -> Option<Self::Output> {
158            if let Candidate::Focusable { state, .. } = candidate
159                && state.is_focused()
160            {
161                Some(Target::from(candidate))
162            } else {
163                None
164            }
165        }
166
167        fn description(&self) -> String {
168            "is focused".to_owned()
169        }
170    }
171
172    IsFocused
173}