iced_core/widget/operation/
focusable.rs

1//! Operate on widgets that can be focused.
2use crate::Rectangle;
3use crate::widget::Id;
4use crate::widget::operation::{self, Operation, Outcome};
5
6/// The internal state of a widget that can be focused.
7pub trait Focusable {
8    /// Returns whether the widget is focused or not.
9    fn is_focused(&self) -> bool;
10
11    /// Focuses the widget.
12    fn focus(&mut self);
13
14    /// Unfocuses the widget.
15    fn unfocus(&mut self);
16}
17
18/// A summary of the focusable widgets present on a widget tree.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub struct Count {
21    /// The index of the current focused widget, if any.
22    pub focused: Option<usize>,
23
24    /// The total amount of focusable widgets.
25    pub total: usize,
26}
27
28/// Produces an [`Operation`] that focuses the widget with the given [`Id`].
29pub fn focus<T>(target: Id) -> impl Operation<T> {
30    struct Focus {
31        target: Id,
32    }
33
34    impl<T> Operation<T> for Focus {
35        fn focusable(
36            &mut self,
37            id: Option<&Id>,
38            _bounds: Rectangle,
39            state: &mut dyn Focusable,
40        ) {
41            match id {
42                Some(id) if id == &self.target => {
43                    state.focus();
44                }
45                _ => {
46                    state.unfocus();
47                }
48            }
49        }
50
51        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
52            operate(self);
53        }
54    }
55
56    Focus { target }
57}
58
59/// Produces an [`Operation`] that unfocuses the focused widget.
60pub fn unfocus<T>() -> impl Operation<T> {
61    struct Unfocus;
62
63    impl<T> Operation<T> for Unfocus {
64        fn focusable(
65            &mut self,
66            _id: Option<&Id>,
67            _bounds: Rectangle,
68            state: &mut dyn Focusable,
69        ) {
70            state.unfocus();
71        }
72
73        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
74            operate(self);
75        }
76    }
77
78    Unfocus
79}
80
81/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
82/// provided function to build a new [`Operation`].
83pub fn count() -> impl Operation<Count> {
84    struct CountFocusable {
85        count: Count,
86    }
87
88    impl Operation<Count> for CountFocusable {
89        fn focusable(
90            &mut self,
91            _id: Option<&Id>,
92            _bounds: Rectangle,
93            state: &mut dyn Focusable,
94        ) {
95            if state.is_focused() {
96                self.count.focused = Some(self.count.total);
97            }
98
99            self.count.total += 1;
100        }
101
102        fn traverse(
103            &mut self,
104            operate: &mut dyn FnMut(&mut dyn Operation<Count>),
105        ) {
106            operate(self);
107        }
108
109        fn finish(&self) -> Outcome<Count> {
110            Outcome::Some(self.count)
111        }
112    }
113
114    CountFocusable {
115        count: Count::default(),
116    }
117}
118
119/// Produces an [`Operation`] that searches for the current focused widget, and
120/// - if found, focuses the previous focusable widget.
121/// - if not found, focuses the last focusable widget.
122pub fn focus_previous<T>() -> impl Operation<T>
123where
124    T: Send + 'static,
125{
126    struct FocusPrevious {
127        count: Count,
128        current: usize,
129    }
130
131    impl<T> Operation<T> for FocusPrevious {
132        fn focusable(
133            &mut self,
134            _id: Option<&Id>,
135            _bounds: Rectangle,
136            state: &mut dyn Focusable,
137        ) {
138            if self.count.total == 0 {
139                return;
140            }
141
142            match self.count.focused {
143                None if self.current == self.count.total - 1 => state.focus(),
144                Some(0) if self.current == 0 => state.unfocus(),
145                Some(0) => {}
146                Some(focused) if focused == self.current => state.unfocus(),
147                Some(focused) if focused - 1 == self.current => state.focus(),
148                _ => {}
149            }
150
151            self.current += 1;
152        }
153
154        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
155            operate(self);
156        }
157    }
158
159    operation::then(count(), |count| FocusPrevious { count, current: 0 })
160}
161
162/// Produces an [`Operation`] that searches for the current focused widget, and
163/// - if found, focuses the next focusable widget.
164/// - if not found, focuses the first focusable widget.
165pub fn focus_next<T>() -> impl Operation<T>
166where
167    T: Send + 'static,
168{
169    struct FocusNext {
170        count: Count,
171        current: usize,
172    }
173
174    impl<T> Operation<T> for FocusNext {
175        fn focusable(
176            &mut self,
177            _id: Option<&Id>,
178            _bounds: Rectangle,
179            state: &mut dyn Focusable,
180        ) {
181            match self.count.focused {
182                None if self.current == 0 => state.focus(),
183                Some(focused) if focused == self.current => state.unfocus(),
184                Some(focused) if focused + 1 == self.current => state.focus(),
185                _ => {}
186            }
187
188            self.current += 1;
189        }
190
191        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
192            operate(self);
193        }
194    }
195
196    operation::then(count(), |count| FocusNext { count, current: 0 })
197}
198
199/// Produces an [`Operation`] that searches for the current focused widget
200/// and stores its ID. This ignores widgets that do not have an ID.
201pub fn find_focused() -> impl Operation<Id> {
202    struct FindFocused {
203        focused: Option<Id>,
204    }
205
206    impl Operation<Id> for FindFocused {
207        fn focusable(
208            &mut self,
209            id: Option<&Id>,
210            _bounds: Rectangle,
211            state: &mut dyn Focusable,
212        ) {
213            if state.is_focused() && id.is_some() {
214                self.focused = id.cloned();
215            }
216        }
217
218        fn traverse(
219            &mut self,
220            operate: &mut dyn FnMut(&mut dyn Operation<Id>),
221        ) {
222            operate(self);
223        }
224
225        fn finish(&self) -> Outcome<Id> {
226            if let Some(id) = &self.focused {
227                Outcome::Some(id.clone())
228            } else {
229                Outcome::None
230            }
231        }
232    }
233
234    FindFocused { focused: None }
235}
236
237/// Produces an [`Operation`] that searches for the focusable widget
238/// and stores whether it is focused or not. This ignores widgets that
239/// do not have an ID.
240pub fn is_focused(target: Id) -> impl Operation<bool> {
241    struct IsFocused {
242        target: Id,
243        is_focused: Option<bool>,
244    }
245
246    impl Operation<bool> for IsFocused {
247        fn focusable(
248            &mut self,
249            id: Option<&Id>,
250            _bounds: Rectangle,
251            state: &mut dyn Focusable,
252        ) {
253            if id.is_some_and(|id| *id == self.target) {
254                self.is_focused = Some(state.is_focused());
255            }
256        }
257
258        fn traverse(
259            &mut self,
260            operate: &mut dyn FnMut(&mut dyn Operation<bool>),
261        ) {
262            if self.is_focused.is_some() {
263                return;
264            }
265
266            operate(self);
267        }
268
269        fn finish(&self) -> Outcome<bool> {
270            self.is_focused.map_or(Outcome::None, Outcome::Some)
271        }
272    }
273
274    IsFocused {
275        target,
276        is_focused: None,
277    }
278}