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
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}