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 container(
52            &mut self,
53            _id: Option<&Id>,
54            _bounds: Rectangle,
55            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
56        ) {
57            operate_on_children(self);
58        }
59    }
60
61    Focus { target }
62}
63
64/// Produces an [`Operation`] that unfocuses the focused widget.
65pub fn unfocus<T>() -> impl Operation<T> {
66    struct Unfocus;
67
68    impl<T> Operation<T> for Unfocus {
69        fn focusable(
70            &mut self,
71            _id: Option<&Id>,
72            _bounds: Rectangle,
73            state: &mut dyn Focusable,
74        ) {
75            state.unfocus();
76        }
77
78        fn container(
79            &mut self,
80            _id: Option<&Id>,
81            _bounds: Rectangle,
82            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
83        ) {
84            operate_on_children(self);
85        }
86    }
87
88    Unfocus
89}
90
91/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
92/// provided function to build a new [`Operation`].
93pub fn count() -> impl Operation<Count> {
94    struct CountFocusable {
95        count: Count,
96    }
97
98    impl Operation<Count> for CountFocusable {
99        fn focusable(
100            &mut self,
101            _id: Option<&Id>,
102            _bounds: Rectangle,
103            state: &mut dyn Focusable,
104        ) {
105            if state.is_focused() {
106                self.count.focused = Some(self.count.total);
107            }
108
109            self.count.total += 1;
110        }
111
112        fn container(
113            &mut self,
114            _id: Option<&Id>,
115            _bounds: Rectangle,
116            operate_on_children: &mut dyn FnMut(&mut dyn Operation<Count>),
117        ) {
118            operate_on_children(self);
119        }
120
121        fn finish(&self) -> Outcome<Count> {
122            Outcome::Some(self.count)
123        }
124    }
125
126    CountFocusable {
127        count: Count::default(),
128    }
129}
130
131/// Produces an [`Operation`] that searches for the current focused widget, and
132/// - if found, focuses the previous focusable widget.
133/// - if not found, focuses the last focusable widget.
134pub fn focus_previous<T>() -> impl Operation<T>
135where
136    T: Send + 'static,
137{
138    struct FocusPrevious {
139        count: Count,
140        current: usize,
141    }
142
143    impl<T> Operation<T> for FocusPrevious {
144        fn focusable(
145            &mut self,
146            _id: Option<&Id>,
147            _bounds: Rectangle,
148            state: &mut dyn Focusable,
149        ) {
150            if self.count.total == 0 {
151                return;
152            }
153
154            match self.count.focused {
155                None if self.current == self.count.total - 1 => state.focus(),
156                Some(0) if self.current == 0 => state.unfocus(),
157                Some(0) => {}
158                Some(focused) if focused == self.current => state.unfocus(),
159                Some(focused) if focused - 1 == self.current => state.focus(),
160                _ => {}
161            }
162
163            self.current += 1;
164        }
165
166        fn container(
167            &mut self,
168            _id: Option<&Id>,
169            _bounds: Rectangle,
170            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
171        ) {
172            operate_on_children(self);
173        }
174    }
175
176    operation::then(count(), |count| FocusPrevious { count, current: 0 })
177}
178
179/// Produces an [`Operation`] that searches for the current focused widget, and
180/// - if found, focuses the next focusable widget.
181/// - if not found, focuses the first focusable widget.
182pub fn focus_next<T>() -> impl Operation<T>
183where
184    T: Send + 'static,
185{
186    struct FocusNext {
187        count: Count,
188        current: usize,
189    }
190
191    impl<T> Operation<T> for FocusNext {
192        fn focusable(
193            &mut self,
194            _id: Option<&Id>,
195            _bounds: Rectangle,
196            state: &mut dyn Focusable,
197        ) {
198            match self.count.focused {
199                None if self.current == 0 => state.focus(),
200                Some(focused) if focused == self.current => state.unfocus(),
201                Some(focused) if focused + 1 == self.current => state.focus(),
202                _ => {}
203            }
204
205            self.current += 1;
206        }
207
208        fn container(
209            &mut self,
210            _id: Option<&Id>,
211            _bounds: Rectangle,
212            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
213        ) {
214            operate_on_children(self);
215        }
216    }
217
218    operation::then(count(), |count| FocusNext { count, current: 0 })
219}
220
221/// Produces an [`Operation`] that searches for the current focused widget
222/// and stores its ID. This ignores widgets that do not have an ID.
223pub fn find_focused() -> impl Operation<Id> {
224    struct FindFocused {
225        focused: Option<Id>,
226    }
227
228    impl Operation<Id> for FindFocused {
229        fn focusable(
230            &mut self,
231            id: Option<&Id>,
232            _bounds: Rectangle,
233            state: &mut dyn Focusable,
234        ) {
235            if state.is_focused() && id.is_some() {
236                self.focused = id.cloned();
237            }
238        }
239
240        fn container(
241            &mut self,
242            _id: Option<&Id>,
243            _bounds: Rectangle,
244            operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
245        ) {
246            operate_on_children(self);
247        }
248
249        fn finish(&self) -> Outcome<Id> {
250            if let Some(id) = &self.focused {
251                Outcome::Some(id.clone())
252            } else {
253                Outcome::None
254            }
255        }
256    }
257
258    FindFocused { focused: None }
259}
260
261/// Produces an [`Operation`] that searches for the focusable widget
262/// and stores whether it is focused or not. This ignores widgets that
263/// do not have an ID.
264pub fn is_focused(target: Id) -> impl Operation<bool> {
265    struct IsFocused {
266        target: Id,
267        is_focused: Option<bool>,
268    }
269
270    impl Operation<bool> for IsFocused {
271        fn focusable(
272            &mut self,
273            id: Option<&Id>,
274            _bounds: Rectangle,
275            state: &mut dyn Focusable,
276        ) {
277            if id.is_some_and(|id| *id == self.target) {
278                self.is_focused = Some(state.is_focused());
279            }
280        }
281
282        fn container(
283            &mut self,
284            _id: Option<&Id>,
285            _bounds: Rectangle,
286            operate_on_children: &mut dyn FnMut(&mut dyn Operation<bool>),
287        ) {
288            if self.is_focused.is_some() {
289                return;
290            }
291
292            operate_on_children(self);
293        }
294
295        fn finish(&self) -> Outcome<bool> {
296            self.is_focused.map_or(Outcome::None, Outcome::Some)
297        }
298    }
299
300    IsFocused {
301        target,
302        is_focused: None,
303    }
304}