rat_focus/flag.rs
1use crate::{FocusBuilder, HasFocus, Navigation, ratatui};
2use std::cell::{Cell, RefCell};
3use std::fmt::{Debug, Display, Formatter};
4use std::hash::{Hash, Hasher};
5use std::ptr;
6use std::rc::Rc;
7
8/// Holds the flags for the focus.
9///
10/// Add this to the widget state and implement [HasFocus] to
11/// manage your widgets focus state.
12///
13/// __Note__
14///
15/// This struct is intended to be cloned and uses a Rc internally
16/// to share the state.
17///
18/// __Note__
19///
20/// Equality and Hash and the id() function use the memory address of the
21/// FocusFlag behind the internal Rc<>.
22///
23/// __See__
24/// [HasFocus], [on_gained!](crate::on_gained!) and
25/// [on_lost!](crate::on_lost!).
26///
27#[derive(Clone, Default)]
28pub struct FocusFlag(Rc<FocusFlagCore>);
29
30/// Equality for FocusFlag means pointer equality of the underlying
31/// Rc using Rc::ptr_eq.
32impl PartialEq for FocusFlag {
33 fn eq(&self, other: &Self) -> bool {
34 Rc::ptr_eq(&self.0, &other.0)
35 }
36}
37
38impl Eq for FocusFlag {}
39
40impl Hash for FocusFlag {
41 fn hash<H: Hasher>(&self, state: &mut H) {
42 ptr::hash(Rc::as_ptr(&self.0), state);
43 }
44}
45
46impl Display for FocusFlag {
47 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48 let name = self.0.name.borrow();
49 if let Some(name) = &*name {
50 write!(f, "|{}|", name)
51 } else {
52 write!(f, "")
53 }
54 }
55}
56
57impl HasFocus for FocusFlag {
58 fn build(&self, builder: &mut FocusBuilder) {
59 builder.leaf_widget(self);
60 }
61
62 fn focus(&self) -> FocusFlag {
63 self.clone()
64 }
65
66 fn area(&self) -> ratatui::layout::Rect {
67 ratatui::layout::Rect::default()
68 }
69
70 fn area_z(&self) -> u16 {
71 0
72 }
73
74 fn navigable(&self) -> Navigation {
75 Navigation::Regular
76 }
77}
78
79struct FocusFlagCore {
80 /// Field name for debugging purposes.
81 name: RefCell<Option<Box<str>>>,
82 /// Does this widget have the focus.
83 /// Or, if the flag is used for a container, does any of
84 /// widget inside the container have the focus.
85 ///
86 /// This flag is set by [Focus::handle].
87 focus: Cell<bool>,
88 /// This widget just gained the focus. This flag is set by [Focus::handle]
89 /// if there is a focus transfer, and will be reset by the next
90 /// call to [Focus::handle].
91 ///
92 /// See [on_gained!](crate::on_gained!)
93 gained: Cell<bool>,
94 /// Callback for set of gained.
95 ///
96 /// A widget can set this callback and will be notified
97 /// by Focus whenever it gains the focus.
98 ///
99 /// It's a bit crude, as you have set up any widget-state
100 /// you want to change as shared state with the callback-closure.
101 /// But it's still preferable to relying on the fact that
102 /// the `handle` event for a widget will be called while
103 /// the gained flag is still set.
104 on_gained: RefCell<Option<Box<dyn Fn()>>>,
105 /// This widget just lost the focus. This flag is set by [Focus::handle]
106 /// if there is a focus transfer, and will be reset by the next
107 /// call to [Focus::handle].
108 ///
109 /// See [on_lost!](crate::on_lost!)
110 lost: Cell<bool>,
111 /// Callback for set of lost.
112 ///
113 /// A widget can set this callback and will be notified
114 /// by Focus whenever it looses the focus.
115 ///
116 /// It's a bit crude, as you have set up any widget-state
117 /// you want to change as shared state with the callback-closure.
118 /// But it's still preferable to relying on the fact that
119 /// the `handle` event for a widget will be called while
120 /// the lost flag is still set.
121 on_lost: RefCell<Option<Box<dyn Fn()>>>,
122 /// This flag is set by [Focus::handle], if a mouse-event
123 /// matches one of the areas associated with a widget.
124 ///
125 /// > It searches all containers for an area-match. All
126 /// matching areas will have the flag set.
127 /// If an area with a higher z is found, all previously
128 /// found areas are discarded.
129 ///
130 /// > The z value for the last container is taken as a baseline.
131 /// Only widgets with a z greater or equal are considered.
132 /// If multiple widget areas are matching, the last one
133 /// will get the flag set.
134 ///
135 /// This rules enable popup-windows with complex ui's.
136 /// The popup-container starts with a z=1 and all widgets
137 /// within also get the same z. With the given rules, all
138 /// widgets underneath the popup are ignored.
139 ///
140 /// * This flag starts with a default `true`. This allows
141 /// widgets to work, even if Focus is not used.
142 /// * Mouse drag events are not bound to any area.
143 /// Instead, they set the mouse-focus to true for all
144 /// widgets and containers.
145 mouse_focus: Cell<bool>,
146}
147
148impl Debug for FocusFlag {
149 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("FocusFlag")
151 .field("name", &self.0.name)
152 .field("focus", &self.0.focus.get())
153 .field("widget_id", &self.widget_id())
154 .field("gained", &self.0.gained.get())
155 .field("on_gained", &self.0.on_gained.borrow().is_some())
156 .field("lost", &self.0.lost.get())
157 .field("on_lost", &self.0.on_lost.borrow().is_some())
158 .finish()
159 }
160}
161
162impl FocusFlag {
163 /// Create a default flag.
164 pub fn new() -> Self {
165 Self::default()
166 }
167
168 /// Create a deep copy of the FocusFlag.
169 ///
170 /// Caution
171 ///
172 /// It will lose the on_gained() and on_lost() callbacks.
173 /// Those can not be replicated/cloned as they will
174 /// most probably hold some Rc's to somewhere.
175 ///
176 /// You will need to set them anew.
177 pub fn new_instance(&self) -> Self {
178 Self(Rc::new(self.0.fake_clone()))
179 }
180
181 /// Return an identity value.
182 ///
183 /// This uses the memory address of the backing Rc so it will
184 /// be unique during the runtime but will be different for each
185 /// run.
186 pub fn widget_id(&self) -> usize {
187 Rc::as_ptr(&self.0) as usize
188 }
189
190 /// Create a named flag.
191 ///
192 /// The name is only used for debugging.
193 #[deprecated(
194 since = "1.4.0",
195 note = "to dangerous, use FocusFlag::new().with_name(..) or FocusFlag::fake_clone(..) for a clone."
196 )]
197 pub fn named(name: impl AsRef<str>) -> Self {
198 Self(Rc::new(FocusFlagCore::default().named(name.as_ref())))
199 }
200
201 /// Set a name for a FocusFlag.
202 pub fn with_name(self, name: &str) -> Self {
203 self.set_name(name);
204 self
205 }
206
207 /// Has the focus.
208 #[inline]
209 pub fn get(&self) -> bool {
210 self.0.focus.get()
211 }
212
213 /// Set the focus.
214 #[inline]
215 pub fn set(&self, focus: bool) {
216 self.0.focus.set(focus);
217 }
218
219 /// Get the field-name.
220 #[inline]
221 pub fn name(&self) -> Box<str> {
222 self.0.name.borrow().clone().unwrap_or_default()
223 }
224
225 /// Set the field-name.
226 #[inline]
227 pub fn set_name(&self, name: &str) {
228 *self.0.name.borrow_mut() = Some(Box::from(name))
229 }
230
231 /// Just lost the focus.
232 #[inline]
233 pub fn lost(&self) -> bool {
234 self.0.lost.get()
235 }
236
237 /// Set the lost-flag.
238 ///
239 /// This doesn't call the on_lost callback.
240 #[inline]
241 pub fn set_lost(&self, lost: bool) {
242 self.0.lost.set(lost);
243 }
244
245 /// Set an on_lost callback. The intention is that widget-creators
246 /// can use this to get guaranteed notifications on focus-changes.
247 ///
248 /// This is not an api for widget *users.
249 #[inline]
250 pub fn on_lost(&self, on_lost: impl Fn() + 'static) {
251 *(self.0.on_lost.borrow_mut()) = Some(Box::new(on_lost));
252 }
253
254 /// Notify an on_lost() tragedy.
255 #[inline]
256 pub fn call_on_lost(&self) -> bool {
257 let borrow = self.0.on_lost.borrow();
258 if let Some(f) = borrow.as_ref() {
259 f();
260 true
261 } else {
262 false
263 }
264 }
265
266 /// Just gained the focus.
267 #[inline]
268 pub fn gained(&self) -> bool {
269 self.0.gained.get()
270 }
271
272 /// Set the gained-flag.
273 ///
274 /// This doesn't call the on_gained callback.
275 #[inline]
276 pub fn set_gained(&self, gained: bool) {
277 self.0.gained.set(gained);
278 }
279
280 /// Set an on_gained callback. The intention is that widget-creators
281 /// can use this to get guaranteed notifications on focus-changes.
282 ///
283 /// This is not an api for widget *users.
284 #[inline]
285 pub fn on_gained(&self, on_gained: impl Fn() + 'static) {
286 *(self.0.on_gained.borrow_mut()) = Some(Box::new(on_gained));
287 }
288
289 /// Notify an on_gained() comedy.
290 #[inline]
291 pub fn call_on_gained(&self) -> bool {
292 let borrow = self.0.on_gained.borrow();
293 if let Some(f) = borrow.as_ref() {
294 f();
295 true
296 } else {
297 false
298 }
299 }
300
301 /// Set the mouse-focus for this widget.
302 #[inline]
303 pub fn set_mouse_focus(&self, mf: bool) {
304 self.0.mouse_focus.set(mf);
305 }
306
307 /// Is the mouse-focus set for this widget.
308 ///
309 /// This function will return true, if [Focus::handle] is never called.
310 ///
311 /// See [HasFocus::has_mouse_focus()]
312 #[inline]
313 pub fn mouse_focus(&self) -> bool {
314 self.0.mouse_focus.get()
315 }
316
317 /// Reset all flags to false.
318 #[inline]
319 pub fn clear(&self) {
320 self.0.focus.set(false);
321 self.0.lost.set(false);
322 self.0.gained.set(false);
323 self.0.mouse_focus.set(true);
324 }
325}
326
327impl Default for FocusFlagCore {
328 fn default() -> Self {
329 Self {
330 name: RefCell::new(None),
331 focus: Cell::new(false),
332 gained: Cell::new(false),
333 on_gained: RefCell::new(None),
334 lost: Cell::new(false),
335 on_lost: RefCell::new(None),
336 mouse_focus: Cell::new(true),
337 }
338 }
339}
340
341impl FocusFlagCore {
342 #[inline(always)]
343 pub(crate) fn named(self, name: &str) -> Self {
344 *self.name.borrow_mut() = Some(Box::from(name));
345 self
346 }
347
348 pub(crate) fn fake_clone(&self) -> Self {
349 Self {
350 name: self.name.clone(),
351 focus: Cell::new(self.focus.get()),
352 gained: Cell::new(self.gained.get()),
353 on_gained: RefCell::new(None),
354 lost: Cell::new(self.lost.get()),
355 on_lost: RefCell::new(None),
356 mouse_focus: Cell::new(self.mouse_focus.get()),
357 }
358 }
359}