rat_focus/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3mod focus;
4
5/// Reexport of types used by a macro.
6pub mod ratatui {
7    pub mod layout {
8        pub use ratatui_core::layout::Rect;
9    }
10}
11
12pub use crate::focus::{Focus, FocusBuilder, handle_focus};
13use std::cell::{Cell, RefCell};
14use std::fmt::{Debug, Display, Formatter};
15use std::hash::{Hash, Hasher};
16use std::ptr;
17use std::rc::Rc;
18
19/// Holds the flags for the focus.
20///
21/// Add this to the widget state and implement [HasFocus] to
22/// manage your widgets focus state.
23///
24/// __Note__
25///
26/// This struct is intended to be cloned and uses a Rc internally
27/// to share the state.
28///
29/// __Note__
30///
31/// Equality and Hash and the id() function use the memory address of the
32/// FocusFlag behind the internal Rc<>.
33///
34/// __See__
35/// [HasFocus], [on_gained!](crate::on_gained!) and
36/// [on_lost!](crate::on_lost!).
37///
38#[derive(Clone, Default)]
39pub struct FocusFlag(Rc<FocusFlagCore>);
40
41/// Equality for FocusFlag means pointer equality of the underlying
42/// Rc using Rc::ptr_eq.
43impl PartialEq for FocusFlag {
44    fn eq(&self, other: &Self) -> bool {
45        Rc::ptr_eq(&self.0, &other.0)
46    }
47}
48
49impl Eq for FocusFlag {}
50
51impl Hash for FocusFlag {
52    fn hash<H: Hasher>(&self, state: &mut H) {
53        ptr::hash(Rc::as_ptr(&self.0), state);
54    }
55}
56
57impl Display for FocusFlag {
58    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59        let name = self.0.name.borrow();
60        if let Some(name) = &*name {
61            write!(f, "|{}|", name)
62        } else {
63            write!(f, "")
64        }
65    }
66}
67
68impl HasFocus for FocusFlag {
69    fn build(&self, builder: &mut FocusBuilder) {
70        builder.leaf_widget(self);
71    }
72
73    fn focus(&self) -> FocusFlag {
74        self.clone()
75    }
76
77    fn area(&self) -> ratatui::layout::Rect {
78        ratatui::layout::Rect::default()
79    }
80
81    fn area_z(&self) -> u16 {
82        0
83    }
84
85    fn navigable(&self) -> Navigation {
86        Navigation::Regular
87    }
88}
89
90#[derive(Default)]
91struct FocusFlagCore {
92    /// Field name for debugging purposes.
93    name: RefCell<Option<Box<str>>>,
94    /// Focus.
95    focus: Cell<bool>,
96    /// This widget just gained the focus. This flag is set by [Focus::handle]
97    /// if there is a focus transfer, and will be reset by the next
98    /// call to [Focus::handle].
99    ///
100    /// See [on_gained!](crate::on_gained!)
101    gained: Cell<bool>,
102    /// Callback for set of gained.
103    on_gained: RefCell<Option<Box<dyn Fn()>>>,
104    /// This widget just lost the focus. This flag is set by [Focus::handle]
105    /// if there is a focus transfer, and will be reset by the next
106    /// call to [Focus::handle].
107    ///
108    /// See [on_lost!](crate::on_lost!)
109    lost: Cell<bool>,
110    /// Callback for set of lost.
111    on_lost: RefCell<Option<Box<dyn Fn()>>>,
112}
113
114/// Focus navigation for widgets.
115///
116/// The effects that hinder focus-change (`Reach*`, `Lock`) only work
117/// when navigation changes via next()/prev()/focus_at().
118///
119/// Programmatic focus changes are always possible.
120#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
121pub enum Navigation {
122    /// Widget is not reachable with normal keyboard or mouse navigation.
123    None,
124    /// Focus is locked to stay with this widget. No mouse or keyboard navigation
125    /// can change that.
126    Lock,
127    /// Widget is not reachable with keyboard navigation, but can be focused with the mouse.
128    Mouse,
129    /// Widget cannot be reached with normal keyboard navigation, but can be left.
130    /// (e.g. Tabs, Split-Divider)
131    Leave,
132    /// Widget can be reached with normal keyboard navigation, but not left.
133    /// (e.g. TextArea)
134    Reach,
135    /// Widget can be reached with normal keyboard navigation, but only be left with
136    /// backward navigation.
137    /// (e.g. some widget with internal structure)
138    ReachLeaveFront,
139    /// Widget can be reached with normal keyboard navigation, but only be left with
140    /// forward navigation.
141    /// (e.g. some widget with internal structure)
142    ReachLeaveBack,
143    /// Widget can be reached and left with normal keyboard navigation.
144    #[default]
145    Regular,
146}
147
148/// Trait for a widget that takes part of focus handling.
149///
150/// When used for a simple widget implement
151/// - build()
152/// - focus()
153/// - area()
154///
155/// and optionally
156///
157/// - area_z() and navigable()
158///
159/// ```rust no_run
160/// use rat_focus::ratatui::layout::Rect;
161/// use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
162///
163/// struct MyWidgetState { pub focus: FocusFlag, pub area: Rect }
164///
165/// impl HasFocus for MyWidgetState {
166///     fn build(&self, builder: &mut FocusBuilder) {
167///         builder.leaf_widget(self);
168///     }
169///
170///     fn focus(&self) -> FocusFlag {
171///         self.focus.clone()
172///     }
173///
174///     fn area(&self) -> Rect {
175///         self.area
176///     }
177/// }
178/// ```
179///
180///
181/// When used for a container widget implement
182/// - build()
183/// ```rust no_run
184/// use rat_focus::ratatui::layout::Rect;
185/// use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
186///
187/// struct MyWidgetState { pub focus: FocusFlag, pub area: Rect }
188/// # impl HasFocus for MyWidgetState {
189/// #     fn build(&self, builder: &mut FocusBuilder) {
190/// #         builder.leaf_widget(self);
191/// #     }
192/// #
193/// #     fn focus(&self) -> FocusFlag {
194/// #         self.focus.clone()
195/// #     }
196/// #
197/// #     fn area(&self) -> Rect {
198/// #         self.area
199/// #     }
200/// # }
201/// struct SomeWidgetState { pub focus: FocusFlag, pub area: Rect, pub component_a: MyWidgetState, pub component_b: MyWidgetState }
202///
203/// impl HasFocus for SomeWidgetState {
204///     fn build(&self, builder: &mut FocusBuilder) {
205///         let tag = builder.start(self);
206///         builder.widget(&self.component_a);
207///         builder.widget(&self.component_b);
208///         builder.end(tag);
209///     }
210///
211///     fn focus(&self) -> FocusFlag {
212///         self.focus.clone()
213///     }
214///
215///     fn area(&self) -> Rect {
216///         self.area
217///     }
218/// }
219/// ```
220/// Creates a container with an identity.
221///
222/// Or
223/// ```rust no_run
224/// use rat_focus::ratatui::layout::Rect;
225/// use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
226///
227/// struct MyWidgetState { pub focus: FocusFlag, pub area: Rect }
228/// # impl HasFocus for MyWidgetState {
229/// #     fn build(&self, builder: &mut FocusBuilder) {
230/// #         builder.leaf_widget(self);
231/// #     }
232/// #
233/// #     fn focus(&self) -> FocusFlag {
234/// #         self.focus.clone()
235/// #     }
236/// #
237/// #     fn area(&self) -> Rect {
238/// #         self.area
239/// #     }
240/// # }
241/// struct SomeWidgetState { pub focus: FocusFlag, pub area: Rect, pub component_a: MyWidgetState, pub component_b: MyWidgetState }
242///
243/// impl HasFocus for SomeWidgetState {
244///     fn build(&self, builder: &mut FocusBuilder) {
245///         builder.widget(&self.component_a);
246///         builder.widget(&self.component_b);
247///     }
248///
249///     fn focus(&self) -> FocusFlag {
250///         unimplemented!("not in use")
251///     }
252///
253///     fn area(&self) -> Rect {
254///         unimplemented!("not in use")
255///     }
256/// }
257/// ```
258/// for an anonymous container.
259///
260/// focus(), area() and area_z() are only used for the first case.
261/// navigable() is ignored for containers, leave it at the default.
262///
263pub trait HasFocus {
264    /// Build the focus-structure for the container/widget.
265    fn build(&self, builder: &mut FocusBuilder);
266
267    /// Build the focus-structure for the container/widget.
268    /// This is called when the default navigation will be
269    /// overridden by the builder.
270    ///
271    /// It defaults to calling build and ignoring the navigable flag.
272    ///
273    /// You still have to implement build() for the baseline functionality.
274    /// This is just an extra.
275    #[allow(unused_variables)]
276    fn build_nav(&self, navigable: Navigation, builder: &mut FocusBuilder) {
277        self.build(builder);
278    }
279
280    /// Access to the flag for the rest.
281    fn focus(&self) -> FocusFlag;
282
283    /// Provide a unique id for the widget.
284    fn id(&self) -> usize {
285        self.focus().widget_id()
286    }
287
288    /// Area for mouse focus.
289    ///
290    /// This area shouldn't overlap with areas returned by other widgets.
291    /// If it does, the widget should use `area_z()` for clarification.
292    /// Otherwise, the areas are searched in order of addition.
293    fn area(&self) -> ratatui::layout::Rect;
294
295    /// Z value for the area.
296    ///
297    /// When testing for mouse interactions the z-value is taken into
298    /// account too.
299    fn area_z(&self) -> u16 {
300        0
301    }
302
303    /// Declares how the widget interacts with focus.
304    ///
305    /// Default is [Navigation::Regular].
306    fn navigable(&self) -> Navigation {
307        Navigation::Regular
308    }
309
310    /// Focused?
311    fn is_focused(&self) -> bool {
312        self.focus().get()
313    }
314
315    /// Just lost focus.
316    fn lost_focus(&self) -> bool {
317        self.focus().lost()
318    }
319
320    /// Just gained focus.
321    fn gained_focus(&self) -> bool {
322        self.focus().gained()
323    }
324}
325
326impl Debug for FocusFlag {
327    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
328        f.debug_struct("FocusFlag")
329            .field("name", &self.0.name)
330            .field("focus", &self.0.focus.get())
331            .field("widget_id", &self.widget_id())
332            .field("gained", &self.0.gained.get())
333            .field("on_gained", &self.0.on_gained.borrow().is_some())
334            .field("lost", &self.0.lost.get())
335            .field("on_lost", &self.0.on_lost.borrow().is_some())
336            .finish()
337    }
338}
339
340impl FocusFlag {
341    /// Create a default flag.
342    pub fn new() -> Self {
343        Self::default()
344    }
345
346    /// Create a deep copy of the FocusFlag.
347    ///
348    /// Caution
349    ///
350    /// It will lose the on_gained() and on_lost() callbacks.
351    /// Those can not be replicated/cloned as they will
352    /// most probably hold some Rc's to somewhere.
353    ///
354    /// You will need to set them anew.
355    pub fn new_instance(&self) -> Self {
356        Self(Rc::new(self.0.fake_clone()))
357    }
358
359    /// Return an identity value.
360    ///
361    /// This uses the memory address of the backing Rc so it will
362    /// be unique during the runtime but will be different for each
363    /// run.
364    pub fn widget_id(&self) -> usize {
365        Rc::as_ptr(&self.0) as usize
366    }
367
368    /// Create a named flag.
369    ///
370    /// The name is only used for debugging.
371    #[deprecated(
372        since = "1.4.0",
373        note = "to dangerous, use FocusFlag::new().with_name(..) or FocusFlag::fake_clone(..) for a clone."
374    )]
375    pub fn named(name: impl AsRef<str>) -> Self {
376        Self(Rc::new(FocusFlagCore::default().named(name.as_ref())))
377    }
378
379    /// Set a name for a FocusFlag.
380    pub fn with_name(self, name: &str) -> Self {
381        self.set_name(name);
382        self
383    }
384
385    /// Has the focus.
386    #[inline]
387    pub fn get(&self) -> bool {
388        self.0.focus.get()
389    }
390
391    /// Set the focus.
392    #[inline]
393    pub fn set(&self, focus: bool) {
394        self.0.focus.set(focus);
395    }
396
397    /// Get the field-name.
398    #[inline]
399    pub fn name(&self) -> Box<str> {
400        self.0.name.borrow().clone().unwrap_or_default()
401    }
402
403    /// Set the field-name.
404    #[inline]
405    pub fn set_name(&self, name: &str) {
406        *self.0.name.borrow_mut() = Some(Box::from(name))
407    }
408
409    /// Just lost the focus.
410    #[inline]
411    pub fn lost(&self) -> bool {
412        self.0.lost.get()
413    }
414
415    /// Set the lost-flag.
416    ///
417    /// This doesn't call the on_lost callback.
418    #[inline]
419    pub fn set_lost(&self, lost: bool) {
420        self.0.lost.set(lost);
421    }
422
423    /// Set an on_lost callback. The intention is that widget-creators
424    /// can use this to get guaranteed notifications on focus-changes.
425    ///
426    /// This is not an api for widget *users.
427    #[inline]
428    pub fn on_lost(&self, on_lost: impl Fn() + 'static) {
429        *(self.0.on_lost.borrow_mut()) = Some(Box::new(on_lost));
430    }
431
432    /// Notify an on_lost() tragedy.
433    #[inline]
434    pub fn call_on_lost(&self) {
435        let borrow = self.0.on_lost.borrow();
436        if let Some(f) = borrow.as_ref() {
437            f();
438        }
439    }
440
441    /// Just gained the focus.
442    #[inline]
443    pub fn gained(&self) -> bool {
444        self.0.gained.get()
445    }
446
447    /// Set the gained-flag.
448    ///
449    /// This doesn't call the on_gained callback.
450    #[inline]
451    pub fn set_gained(&self, gained: bool) {
452        self.0.gained.set(gained);
453    }
454
455    /// Set an on_gained callback. The intention is that widget-creators
456    /// can use this to get guaranteed notifications on focus-changes.
457    ///
458    /// This is not an api for widget *users.
459    #[inline]
460    pub fn on_gained(&self, on_gained: impl Fn() + 'static) {
461        *(self.0.on_gained.borrow_mut()) = Some(Box::new(on_gained));
462    }
463
464    /// Notify an on_gained() comedy.
465    #[inline]
466    pub fn call_on_gained(&self) {
467        let borrow = self.0.on_gained.borrow();
468        if let Some(f) = borrow.as_ref() {
469            f();
470        }
471    }
472
473    /// Reset all flags to false.
474    #[inline]
475    pub fn clear(&self) {
476        self.0.focus.set(false);
477        self.0.lost.set(false);
478        self.0.gained.set(false);
479    }
480}
481
482impl FocusFlagCore {
483    #[inline(always)]
484    pub(crate) fn named(self, name: &str) -> Self {
485        *self.name.borrow_mut() = Some(Box::from(name));
486        self
487    }
488
489    pub(crate) fn fake_clone(&self) -> Self {
490        Self {
491            name: self.name.clone(),
492            focus: Cell::new(self.focus.get()),
493            gained: Cell::new(self.gained.get()),
494            on_gained: RefCell::new(None),
495            lost: Cell::new(self.lost.get()),
496            on_lost: RefCell::new(None),
497        }
498    }
499}
500
501/// Does a match on the state struct of a widget. If `widget_state.lost_focus()` is true
502/// the block is executed. This requires that `widget_state` implements [HasFocus],
503/// but that's the basic requirement for this whole crate.
504///
505/// ```rust ignore
506/// use rat_focus::on_lost;
507///
508/// on_lost!(
509///     state.field1 => {
510///         // do checks
511///     },
512///     state.field2 => {
513///         // do checks
514///     }
515/// );
516/// ```
517#[macro_export]
518macro_rules! on_lost {
519    ($($field:expr => $validate:expr),*) => {{
520        use $crate::HasFocus;
521        $(if $field.lost_focus() { _ = $validate })*
522    }};
523}
524
525/// Does a match on the state struct of a widget. If `widget_state.gained_focus()` is true
526/// the block is executed. This requires that `widget_state` implements [HasFocus],
527/// but that's the basic requirement for this whole crate.
528///
529/// ```rust ignore
530/// use rat_focus::on_gained;
531///
532/// on_gained!(
533///     state.field1 => {
534///         // do prep
535///     },
536///     state.field2 => {
537///         // do prep
538///     }
539/// );
540/// ```
541#[macro_export]
542macro_rules! on_gained {
543    ($($field:expr => $validate:expr),*) => {{
544        use $crate::HasFocus;
545        $(if $field.gained_focus() { _ = $validate })*
546    }};
547}
548
549/// Does a match on several fields and can return a result.
550/// Does a `widget_state.is_focused()` for each field and returns
551/// the first that is true. There is an `else` branch too.
552///
553/// This requires that `widget_state` implements [HasFocus],
554/// but that's the basic requirement for this whole crate.
555///
556/// ```rust ignore
557/// use rat_focus::match_focus;
558///
559/// let res = match_focus!(
560///     state.field1 => {
561///         // do this
562///         true
563///     },
564///     state.field2 => {
565///         // do that
566///         true
567///     },
568///     else => {
569///         false
570///     }
571/// );
572///
573/// if res {
574///     // react
575/// }
576/// ```
577///
578#[macro_export]
579macro_rules! match_focus {
580    ($($field:expr => $block:expr),* $(, else => $final:expr)?) => {{
581        use $crate::HasFocus;
582        if false {
583            unreachable!();
584        }
585        $(else if $field.is_focused() { $block })*
586        $(else { $final })?
587    }};
588}
589
590/// Create the implementation of HasFocus for the
591/// given list of struct members.
592///
593/// Create a container with no identity.
594/// ```
595/// # use rat_focus::{impl_has_focus, FocusFlag};
596/// # struct MyState { field1: FocusFlag, field2: FocusFlag, field3: FocusFlag }
597/// impl_has_focus!(field1, field2, field3 for MyState);
598/// ```
599///
600/// Create a container with an identity.
601/// ```
602/// # use rat_focus::{impl_has_focus, FocusFlag};
603/// # struct MyState { container: FocusFlag, field1: FocusFlag, field2: FocusFlag, field3: FocusFlag }
604/// impl_has_focus!(container: field1, field2, field3 for MyState);
605/// ```
606///
607/// Create a container with an identity and an area that will react to mouse clicks.
608/// ```
609/// # use rat_focus::ratatui::layout::Rect;
610/// # use rat_focus::{impl_has_focus, FocusFlag};
611/// # struct MyState { container: FocusFlag, area: Rect, field1: FocusFlag, field2: FocusFlag, field3: FocusFlag }
612/// impl_has_focus!(container:area: field1, field2, field3 for MyState);
613/// ```
614#[macro_export]
615macro_rules! impl_has_focus {
616    ($cc:ident:$area:ident: $($n:ident),* for $ty:ty) => {
617        impl $crate::HasFocus for $ty {
618            fn build(&self, builder: &mut $crate::FocusBuilder) {
619                let tag = builder.start(self);
620                $(builder.widget(&self.$n);)*
621                builder.end(tag);
622            }
623
624            fn focus(&self) -> $crate::FocusFlag {
625                self.$cc.clone()
626            }
627
628            fn area(&self) -> $crate::ratatui::layout::Rect {
629                self.$area
630            }
631        }
632    };
633    ($cc:ident: $($n:ident),* for $ty:ty) => {
634        impl $crate::HasFocus for $ty {
635            fn build(&self, builder: &mut $crate::FocusBuilder) {
636                let tag = builder.start(self);
637                $(builder.widget(&self.$n);)*
638                builder.end(tag);
639            }
640
641            fn focus(&self) -> $crate::FocusFlag {
642                self.$cc.clone()
643            }
644
645            fn area(&self) -> $crate::ratatui::layout::Rect {
646                $crate::ratatui::layout::Rect::default()
647            }
648        }
649    };
650    ($($n:ident),* for $ty:ty) => {
651        impl $crate::HasFocus for $ty {
652            fn build(&self, builder: &mut $crate::FocusBuilder) {
653                $(builder.widget(&self.$n);)*
654            }
655
656            fn focus(&self) -> $crate::FocusFlag {
657                unimplemented!("not defined")
658            }
659
660            fn area(&self) -> $crate::ratatui::layout::Rect {
661                unimplemented!("not defined")
662            }
663        }
664    };
665}