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
79#[derive(Default)]
80struct FocusFlagCore {
81 /// Field name for debugging purposes.
82 name: RefCell<Option<Box<str>>>,
83 /// Does this widget have the focus.
84 /// Or, if the flag is used for a container, does any of
85 /// widget inside the container have the focus.
86 ///
87 /// This flag is set by [Focus::handle].
88 focus: Cell<bool>,
89 /// This widget just gained the focus. This flag is set by [Focus::handle]
90 /// if there is a focus transfer, and will be reset by the next
91 /// call to [Focus::handle].
92 ///
93 /// See [on_gained!](crate::on_gained!)
94 gained: Cell<bool>,
95 /// Callback for set of gained.
96 ///
97 /// A widget can set this callback and will be notified
98 /// by Focus whenever it gains the focus.
99 ///
100 /// It's a bit crude, as you have set up any widget-state
101 /// you want to change as shared state with the callback-closure.
102 /// But it's still preferable to relying on the fact that
103 /// the `handle` event for a widget will be called while
104 /// the gained flag is still set.
105 on_gained: RefCell<Option<Box<dyn Fn()>>>,
106 /// This widget just lost the focus. This flag is set by [Focus::handle]
107 /// if there is a focus transfer, and will be reset by the next
108 /// call to [Focus::handle].
109 ///
110 /// See [on_lost!](crate::on_lost!)
111 lost: Cell<bool>,
112 /// Callback for set of lost.
113 ///
114 /// A widget can set this callback and will be notified
115 /// by Focus whenever it looses the focus.
116 ///
117 /// It's a bit crude, as you have set up any widget-state
118 /// you want to change as shared state with the callback-closure.
119 /// But it's still preferable to relying on the fact that
120 /// the `handle` event for a widget will be called while
121 /// the lost flag is still set.
122 on_lost: RefCell<Option<Box<dyn Fn()>>>,
123 /// This flag is set by [Focus::handle], if a mouse-event
124 /// matches one of the areas associated with a widget.
125 ///
126 /// > It searches all containers for an area-match. All
127 /// matching areas will have the flag set.
128 /// If an area with a higher z is found, all previously
129 /// found areas are discarded.
130 ///
131 /// > The z value for the last container is taken as a baseline.
132 /// Only widgets with a z greater or equal are considered.
133 /// If multiple widget areas are matching, the last one
134 /// will get the flag set.
135 ///
136 /// This rules enable popup-windows with complex ui's.
137 /// The popup-container starts with a z=1 and all widgets
138 /// within also get the same z. With the given rules, all
139 /// widgets underneath the popup are ignored.
140 ///
141 /// * This flag starts with a default `true`. This allows
142 /// widgets to work, even if Focus is not used.
143 /// * Mouse drag events are not bound to any area.
144 /// Instead, they set the mouse-focus to true for all
145 /// widgets and containers.
146 mouse_focus: Cell<bool>,
147}
148
149impl Debug for FocusFlag {
150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151 f.debug_struct("FocusFlag")
152 .field("name", &self.0.name)
153 .field("focus", &self.0.focus.get())
154 .field("widget_id", &self.widget_id())
155 .field("gained", &self.0.gained.get())
156 .field("on_gained", &self.0.on_gained.borrow().is_some())
157 .field("lost", &self.0.lost.get())
158 .field("on_lost", &self.0.on_lost.borrow().is_some())
159 .finish()
160 }
161}
162
163impl FocusFlag {
164 /// Create a default flag.
165 pub fn new() -> Self {
166 Self::default()
167 }
168
169 /// Create a deep copy of the FocusFlag.
170 ///
171 /// Caution
172 ///
173 /// It will lose the on_gained() and on_lost() callbacks.
174 /// Those can not be replicated/cloned as they will
175 /// most probably hold some Rc's to somewhere.
176 ///
177 /// You will need to set them anew.
178 pub fn new_instance(&self) -> Self {
179 Self(Rc::new(self.0.fake_clone()))
180 }
181
182 /// Return an identity value.
183 ///
184 /// This uses the memory address of the backing Rc so it will
185 /// be unique during the runtime but will be different for each
186 /// run.
187 pub fn widget_id(&self) -> usize {
188 Rc::as_ptr(&self.0) as usize
189 }
190
191 /// Create a named flag.
192 ///
193 /// The name is only used for debugging.
194 #[deprecated(
195 since = "1.4.0",
196 note = "to dangerous, use FocusFlag::new().with_name(..) or FocusFlag::fake_clone(..) for a clone."
197 )]
198 pub fn named(name: impl AsRef<str>) -> Self {
199 Self(Rc::new(FocusFlagCore::default().named(name.as_ref())))
200 }
201
202 /// Set a name for a FocusFlag.
203 pub fn with_name(self, name: &str) -> Self {
204 self.set_name(name);
205 self
206 }
207
208 /// Has the focus.
209 #[inline]
210 pub fn get(&self) -> bool {
211 self.0.focus.get()
212 }
213
214 /// Set the focus.
215 #[inline]
216 pub fn set(&self, focus: bool) {
217 self.0.focus.set(focus);
218 }
219
220 /// Get the field-name.
221 #[inline]
222 pub fn name(&self) -> Box<str> {
223 self.0.name.borrow().clone().unwrap_or_default()
224 }
225
226 /// Set the field-name.
227 #[inline]
228 pub fn set_name(&self, name: &str) {
229 *self.0.name.borrow_mut() = Some(Box::from(name))
230 }
231
232 /// Just lost the focus.
233 #[inline]
234 pub fn lost(&self) -> bool {
235 self.0.lost.get()
236 }
237
238 /// Set the lost-flag.
239 ///
240 /// This doesn't call the on_lost callback.
241 #[inline]
242 pub fn set_lost(&self, lost: bool) {
243 self.0.lost.set(lost);
244 }
245
246 /// Set an on_lost callback. The intention is that widget-creators
247 /// can use this to get guaranteed notifications on focus-changes.
248 ///
249 /// This is not an api for widget *users.
250 #[inline]
251 pub fn on_lost(&self, on_lost: impl Fn() + 'static) {
252 *(self.0.on_lost.borrow_mut()) = Some(Box::new(on_lost));
253 }
254
255 /// Notify an on_lost() tragedy.
256 #[inline]
257 pub fn call_on_lost(&self) -> bool {
258 let borrow = self.0.on_lost.borrow();
259 if let Some(f) = borrow.as_ref() {
260 f();
261 true
262 } else {
263 false
264 }
265 }
266
267 /// Just gained the focus.
268 #[inline]
269 pub fn gained(&self) -> bool {
270 self.0.gained.get()
271 }
272
273 /// Set the gained-flag.
274 ///
275 /// This doesn't call the on_gained callback.
276 #[inline]
277 pub fn set_gained(&self, gained: bool) {
278 self.0.gained.set(gained);
279 }
280
281 /// Set an on_gained callback. The intention is that widget-creators
282 /// can use this to get guaranteed notifications on focus-changes.
283 ///
284 /// This is not an api for widget *users.
285 #[inline]
286 pub fn on_gained(&self, on_gained: impl Fn() + 'static) {
287 *(self.0.on_gained.borrow_mut()) = Some(Box::new(on_gained));
288 }
289
290 /// Notify an on_gained() comedy.
291 #[inline]
292 pub fn call_on_gained(&self) -> bool {
293 let borrow = self.0.on_gained.borrow();
294 if let Some(f) = borrow.as_ref() {
295 f();
296 true
297 } else {
298 false
299 }
300 }
301
302 /// Set the mouse-focus for this widget.
303 #[inline]
304 pub fn set_mouse_focus(&self, mf: bool) {
305 self.0.mouse_focus.set(mf);
306 }
307
308 /// Is the mouse-focus set for this widget.
309 ///
310 /// This function will return true, if [Focus::handle] is never called.
311 ///
312 /// See [HasFocus::has_mouse_focus()]
313 #[inline]
314 pub fn mouse_focus(&self) -> bool {
315 self.0.mouse_focus.get()
316 }
317
318 /// Reset all flags to false.
319 #[inline]
320 pub fn clear(&self) {
321 self.0.focus.set(false);
322 self.0.lost.set(false);
323 self.0.gained.set(false);
324 self.0.mouse_focus.set(true);
325 }
326}
327
328impl FocusFlagCore {
329 #[inline(always)]
330 pub(crate) fn named(self, name: &str) -> Self {
331 *self.name.borrow_mut() = Some(Box::from(name));
332 self
333 }
334
335 pub(crate) fn fake_clone(&self) -> Self {
336 Self {
337 name: self.name.clone(),
338 focus: Cell::new(self.focus.get()),
339 gained: Cell::new(self.gained.get()),
340 on_gained: RefCell::new(None),
341 lost: Cell::new(self.lost.get()),
342 on_lost: RefCell::new(None),
343 mouse_focus: Cell::new(self.mouse_focus.get()),
344 }
345 }
346}