iced_core/widget/operation/
focusable.rs

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