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 /// Hit-tests all widgets for the given position.
123 /// Obscured is set to false if the widget-area is hit
124 /// and there are no containers/widgets with a *higher*
125 /// z-index.
126 ///
127 /// This flag is set by [Focus::handle] for every mouse-event.
128 ///
129 /// The purpose is to disable mouse-event handling where a
130 /// popup is covering some widget.
131 obscured: Cell<bool>,
132}
133
134impl Debug for FocusFlag {
135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136 f.debug_struct("FocusFlag")
137 .field("name", &self.0.name)
138 .field("focus", &self.0.focus.get())
139 .field("widget_id", &self.widget_id())
140 .field("gained", &self.0.gained.get())
141 .field("on_gained", &self.0.on_gained.borrow().is_some())
142 .field("lost", &self.0.lost.get())
143 .field("on_lost", &self.0.on_lost.borrow().is_some())
144 .finish()
145 }
146}
147
148impl FocusFlag {
149 /// Create a default flag.
150 pub fn new() -> Self {
151 Self::default()
152 }
153
154 /// Create a deep copy of the FocusFlag.
155 ///
156 /// Caution
157 ///
158 /// It will lose the on_gained() and on_lost() callbacks.
159 /// Those can not be replicated/cloned as they
160 /// might hold some Rc's to somewhere.
161 /// You need to set them anew.
162 pub fn new_instance(&self) -> Self {
163 Self(Rc::new(self.0.new_instance()))
164 }
165
166 /// Return an identity value.
167 ///
168 /// This uses the memory address of the backing Rc so it will
169 /// be unique during the runtime but will be different for each
170 /// run.
171 pub fn widget_id(&self) -> usize {
172 Rc::as_ptr(&self.0) as usize
173 }
174
175 /// Create a named flag.
176 ///
177 /// The name is only used for debugging.
178 #[deprecated(
179 since = "1.4.0",
180 note = "to dangerous, use FocusFlag::new().with_name(..) or FocusFlag::fake_clone(..) for a clone."
181 )]
182 pub fn named(name: impl AsRef<str>) -> Self {
183 Self(Rc::new(FocusFlagCore::default().named(name.as_ref())))
184 }
185
186 /// Set a name for a FocusFlag.
187 pub fn with_name(self, name: &str) -> Self {
188 self.set_name(name);
189 self
190 }
191
192 /// Has the focus.
193 #[inline]
194 pub fn get(&self) -> bool {
195 self.0.focus.get()
196 }
197
198 /// Set the focus.
199 #[inline]
200 pub fn set(&self, focus: bool) {
201 self.0.focus.set(focus);
202 }
203
204 /// Get the field-name.
205 #[inline]
206 pub fn name(&self) -> Box<str> {
207 self.0.name.borrow().clone().unwrap_or_default()
208 }
209
210 /// Set the field-name.
211 #[inline]
212 pub fn set_name(&self, name: &str) {
213 *self.0.name.borrow_mut() = Some(Box::from(name))
214 }
215
216 /// Just lost the focus.
217 #[inline]
218 pub fn lost(&self) -> bool {
219 self.0.lost.get()
220 }
221
222 /// Set the lost-flag.
223 ///
224 /// This doesn't call the on_lost callback.
225 #[inline]
226 pub fn set_lost(&self, lost: bool) {
227 self.0.lost.set(lost);
228 }
229
230 /// Set an on_lost callback. The intention is that widget-creators
231 /// can use this to get guaranteed notifications on focus-changes.
232 ///
233 /// This is not an api for widget *users.
234 #[inline]
235 pub fn on_lost(&self, on_lost: impl Fn() + 'static) {
236 *(self.0.on_lost.borrow_mut()) = Some(Box::new(on_lost));
237 }
238
239 /// Notify an on_lost() tragedy.
240 #[inline]
241 pub fn call_on_lost(&self) -> bool {
242 let borrow = self.0.on_lost.borrow();
243 if let Some(f) = borrow.as_ref() {
244 f();
245 true
246 } else {
247 false
248 }
249 }
250
251 /// Just gained the focus.
252 #[inline]
253 pub fn gained(&self) -> bool {
254 self.0.gained.get()
255 }
256
257 /// Set the gained-flag.
258 ///
259 /// This doesn't call the on_gained callback.
260 #[inline]
261 pub fn set_gained(&self, gained: bool) {
262 self.0.gained.set(gained);
263 }
264
265 /// Set an on_gained callback. The intention is that widget-creators
266 /// can use this to get guaranteed notifications on focus-changes.
267 ///
268 /// This is not an api for widget *users.
269 #[inline]
270 pub fn on_gained(&self, on_gained: impl Fn() + 'static) {
271 *(self.0.on_gained.borrow_mut()) = Some(Box::new(on_gained));
272 }
273
274 /// Notify an on_gained() comedy.
275 #[inline]
276 pub fn call_on_gained(&self) -> bool {
277 let borrow = self.0.on_gained.borrow();
278 if let Some(f) = borrow.as_ref() {
279 f();
280 true
281 } else {
282 false
283 }
284 }
285
286 /// Set the obscured flag.
287 #[inline]
288 pub fn set_obscured(&self, mf: bool) {
289 self.0.obscured.set(mf);
290 }
291
292 /// Is the widget area hit by the current mouse-position
293 /// and is there no other widget/container with a
294 /// higher z-index. Then this widget is *not* obscured.
295 ///
296 /// This flag is set when handling a mouse-event.
297 ///
298 /// See [HasFocus::is_obscured()]
299 #[inline]
300 pub fn obscured(&self) -> bool {
301 self.0.obscured.get()
302 }
303
304 /// Reset all flags to false.
305 #[inline]
306 pub fn clear(&self) {
307 self.0.focus.set(false);
308 self.0.lost.set(false);
309 self.0.gained.set(false);
310 self.0.obscured.set(false);
311 }
312}
313
314impl Default for FocusFlagCore {
315 fn default() -> Self {
316 Self {
317 name: RefCell::new(None),
318 focus: Cell::new(false),
319 gained: Cell::new(false),
320 on_gained: RefCell::new(None),
321 lost: Cell::new(false),
322 on_lost: RefCell::new(None),
323 obscured: Cell::new(false),
324 }
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 /// Create a deep clone.
336 pub(crate) fn new_instance(&self) -> Self {
337 Self {
338 name: self.name.clone(),
339 focus: Cell::new(self.focus.get()),
340 gained: Cell::new(self.gained.get()),
341 on_gained: RefCell::new(None),
342 lost: Cell::new(self.lost.get()),
343 on_lost: RefCell::new(None),
344 obscured: Cell::new(self.obscured.get()),
345 }
346 }
347}