Skip to main content

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}