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(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
36            match id {
37                Some(id) if id == &self.target => {
38                    state.focus();
39                }
40                _ => {
41                    state.unfocus();
42                }
43            }
44        }
45
46        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
47            operate(self);
48        }
49    }
50
51    Focus { target }
52}
53
54/// Produces an [`Operation`] that unfocuses the focused widget.
55pub fn unfocus<T>() -> impl Operation<T> {
56    struct Unfocus;
57
58    impl<T> Operation<T> for Unfocus {
59        fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
60            state.unfocus();
61        }
62
63        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
64            operate(self);
65        }
66    }
67
68    Unfocus
69}
70
71/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
72/// provided function to build a new [`Operation`].
73pub fn count() -> impl Operation<Count> {
74    struct CountFocusable {
75        count: Count,
76    }
77
78    impl Operation<Count> for CountFocusable {
79        fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
80            if state.is_focused() {
81                self.count.focused = Some(self.count.total);
82            }
83
84            self.count.total += 1;
85        }
86
87        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<Count>)) {
88            operate(self);
89        }
90
91        fn finish(&self) -> Outcome<Count> {
92            Outcome::Some(self.count)
93        }
94    }
95
96    CountFocusable {
97        count: Count::default(),
98    }
99}
100
101/// Produces an [`Operation`] that searches for the current focused widget, and
102/// - if found, focuses the previous focusable widget.
103/// - if not found, focuses the last focusable widget.
104pub fn focus_previous<T>() -> impl Operation<T>
105where
106    T: Send + 'static,
107{
108    struct FocusPrevious {
109        count: Count,
110        current: usize,
111    }
112
113    impl<T> Operation<T> for FocusPrevious {
114        fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
115            if self.count.total == 0 {
116                return;
117            }
118
119            match self.count.focused {
120                None if self.current == self.count.total - 1 => state.focus(),
121                Some(0) if self.current == 0 => state.unfocus(),
122                Some(0) => {}
123                Some(focused) if focused == self.current => state.unfocus(),
124                Some(focused) if focused - 1 == self.current => state.focus(),
125                _ => {}
126            }
127
128            self.current += 1;
129        }
130
131        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
132            operate(self);
133        }
134    }
135
136    operation::then(count(), |count| FocusPrevious { count, current: 0 })
137}
138
139/// Produces an [`Operation`] that searches for the current focused widget, and
140/// - if found, focuses the next focusable widget.
141/// - if not found, focuses the first focusable widget.
142pub fn focus_next<T>() -> impl Operation<T>
143where
144    T: Send + 'static,
145{
146    struct FocusNext {
147        count: Count,
148        current: usize,
149    }
150
151    impl<T> Operation<T> for FocusNext {
152        fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
153            match self.count.focused {
154                None if self.current == 0 => state.focus(),
155                Some(focused) if focused == self.current => state.unfocus(),
156                Some(focused) if focused + 1 == self.current => state.focus(),
157                _ => {}
158            }
159
160            self.current += 1;
161        }
162
163        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
164            operate(self);
165        }
166    }
167
168    operation::then(count(), |count| FocusNext { count, current: 0 })
169}
170
171/// Produces an [`Operation`] that searches for the current focused widget
172/// and stores its ID. This ignores widgets that do not have an ID.
173pub fn find_focused() -> impl Operation<Id> {
174    struct FindFocused {
175        focused: Option<Id>,
176    }
177
178    impl Operation<Id> for FindFocused {
179        fn focusable(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
180            if state.is_focused() && id.is_some() {
181                self.focused = id.cloned();
182            }
183        }
184
185        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<Id>)) {
186            operate(self);
187        }
188
189        fn finish(&self) -> Outcome<Id> {
190            if let Some(id) = &self.focused {
191                Outcome::Some(id.clone())
192            } else {
193                Outcome::None
194            }
195        }
196    }
197
198    FindFocused { focused: None }
199}
200
201/// Produces an [`Operation`] that searches for the focusable widget
202/// and stores whether it is focused or not. This ignores widgets that
203/// do not have an ID.
204pub fn is_focused(target: Id) -> impl Operation<bool> {
205    struct IsFocused {
206        target: Id,
207        is_focused: Option<bool>,
208    }
209
210    impl Operation<bool> for IsFocused {
211        fn focusable(&mut self, id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
212            if id.is_some_and(|id| *id == self.target) {
213                self.is_focused = Some(state.is_focused());
214            }
215        }
216
217        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<bool>)) {
218            if self.is_focused.is_some() {
219                return;
220            }
221
222            operate(self);
223        }
224
225        fn finish(&self) -> Outcome<bool> {
226            self.is_focused.map_or(Outcome::None, Outcome::Some)
227        }
228    }
229
230    IsFocused {
231        target,
232        is_focused: None,
233    }
234}