rat_focus/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3mod focus;
4
5use ratatui::layout::Rect;
6use std::cell::Cell;
7use std::fmt::{Debug, Display, Formatter};
8use std::hash::{Hash, Hasher};
9use std::ptr;
10use std::rc::Rc;
11
12pub use crate::focus::{handle_focus, Focus, FocusBuilder};
13
14pub mod event {
15    //! Rexported eventhandling traits.
16    pub use rat_event::*;
17}
18
19/// Holds the flags for the focus.
20///
21/// Add this to the widget state.
22///
23/// This struct is intended to be cloned and uses a Rc internally
24/// to share the state.
25///
26/// __Attention__
27/// Equality for FocusFlag means pointer-equality of the underlying
28/// Rc using Rc::ptr_eq.
29///
30/// __See__
31/// [HasFocus], [on_gained!](crate::on_gained!) and
32/// [on_lost!](crate::on_lost!).
33///
34#[derive(Clone, Default)]
35pub struct FocusFlag(Rc<FocusFlagCore>);
36
37/// Equality for FocusFlag means pointer equality of the underlying
38/// Rc using Rc::ptr_eq.
39impl PartialEq for FocusFlag {
40    fn eq(&self, other: &Self) -> bool {
41        Rc::ptr_eq(&self.0, &other.0)
42    }
43}
44
45impl Eq for FocusFlag {}
46
47impl Hash for FocusFlag {
48    fn hash<H: Hasher>(&self, state: &mut H) {
49        ptr::hash(Rc::as_ptr(&self.0), state);
50    }
51}
52
53impl Display for FocusFlag {
54    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
55        write!(f, "|{}|", self.0.name)
56    }
57}
58
59impl HasFocus for FocusFlag {
60    fn build(&self, builder: &mut FocusBuilder) {
61        builder.leaf_widget(self);
62    }
63
64    fn focus(&self) -> FocusFlag {
65        self.clone()
66    }
67
68    fn area(&self) -> Rect {
69        Rect::default()
70    }
71
72    fn area_z(&self) -> u16 {
73        0
74    }
75
76    fn navigable(&self) -> Navigation {
77        Navigation::Regular
78    }
79}
80
81// not Clone, always Rc<>
82#[derive(Default)]
83struct FocusFlagCore {
84    /// Field name for debugging purposes.
85    name: Box<str>,
86    /// Focus.
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    /// This widget just lost the focus. This flag is set by [Focus::handle]
95    /// if there is a focus transfer, and will be reset by the next
96    /// call to [Focus::handle].
97    ///
98    /// See [on_lost!](crate::on_lost!)
99    lost: Cell<bool>,
100}
101
102/// Focus navigation for widgets.
103///
104/// The effects that hinder focus-change (`Reach*`, `Lock`) only work
105/// when navigation changes via next()/prev()/focus_at().
106///
107/// Programmatic focus changes are always possible.
108///
109#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
110pub enum Navigation {
111    /// Widget is not reachable with normal keyboard or mouse navigation.
112    None,
113    /// Focus is locked to stay with this widget. No mouse or keyboard navigation
114    /// can change that.
115    Lock,
116    /// Widget is not reachable with keyboard navigation, but can be focused with the mouse.
117    Mouse,
118    /// Widget cannot be reached with normal keyboard navigation, but can be left.
119    /// (e.g. Tabs, Split-Divider)
120    Leave,
121    /// Widget can be reached with normal keyboard navigation, but not left.
122    /// (e.g. TextArea)
123    Reach,
124    /// Widget can be reached with normal keyboard navigation, but only be left with
125    /// backward navigation.
126    /// (e.g. some widget with internal structure)
127    ReachLeaveFront,
128    /// Widget can be reached with normal keyboard navigation, but only be left with
129    /// forward navigation.
130    /// (e.g. some widget with internal structure)
131    ReachLeaveBack,
132    /// Widget can be reached and left with normal keyboard navigation.
133    #[default]
134    Regular,
135}
136
137/// Trait for a widget that takes part of focus handling.
138///
139/// When used for a simple widget implement
140/// - build()
141/// - focus()
142/// - area()
143///
144/// and optionally
145///
146/// - area_z() and navigable()
147///
148/// ```rust no_run
149/// use ratatui::layout::Rect;
150/// use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
151///
152/// struct MyWidgetState { pub focus: FocusFlag, pub area: Rect }
153///
154/// impl HasFocus for MyWidgetState {
155///     fn build(&self, builder: &mut FocusBuilder) {
156///         builder.leaf_widget(self);
157///     }
158///
159///     fn focus(&self) -> FocusFlag {
160///         self.focus.clone()
161///     }
162///
163///     fn area(&self) -> Rect {
164///         self.area
165///     }
166/// }
167/// ```
168///
169///
170/// When used for a container widget implement
171/// - build()
172/// ```rust no_run
173/// use ratatui::layout::Rect;
174/// use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
175///
176/// struct MyWidgetState { pub focus: FocusFlag, pub area: Rect }
177/// # impl HasFocus for MyWidgetState {
178/// #     fn build(&self, builder: &mut FocusBuilder) {
179/// #         builder.leaf_widget(self);
180/// #     }
181/// #
182/// #     fn focus(&self) -> FocusFlag {
183/// #         self.focus.clone()
184/// #     }
185/// #
186/// #     fn area(&self) -> Rect {
187/// #         self.area
188/// #     }
189/// # }
190/// struct SomeWidgetState { pub focus: FocusFlag, pub area: Rect, pub component_a: MyWidgetState, pub component_b: MyWidgetState }
191///
192/// impl HasFocus for SomeWidgetState {
193///     fn build(&self, builder: &mut FocusBuilder) {
194///         let tag = builder.start(self);
195///         builder.widget(&self.component_a);
196///         builder.widget(&self.component_b);
197///         builder.end(tag);
198///     }
199///
200///     fn focus(&self) -> FocusFlag {
201///         self.focus.clone()
202///     }
203///
204///     fn area(&self) -> Rect {
205///         self.area
206///     }
207/// }
208/// ```
209/// Creates a container with an identity.
210///
211/// Or
212/// ```rust no_run
213/// use ratatui::layout::Rect;
214/// use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
215///
216/// struct MyWidgetState { pub focus: FocusFlag, pub area: Rect }
217/// # impl HasFocus for MyWidgetState {
218/// #     fn build(&self, builder: &mut FocusBuilder) {
219/// #         builder.leaf_widget(self);
220/// #     }
221/// #
222/// #     fn focus(&self) -> FocusFlag {
223/// #         self.focus.clone()
224/// #     }
225/// #
226/// #     fn area(&self) -> Rect {
227/// #         self.area
228/// #     }
229/// # }
230/// struct SomeWidgetState { pub focus: FocusFlag, pub area: Rect, pub component_a: MyWidgetState, pub component_b: MyWidgetState }
231///
232/// impl HasFocus for SomeWidgetState {
233///     fn build(&self, builder: &mut FocusBuilder) {
234///         let tag = builder.start(self);
235///         builder.widget(&self.component_a);
236///         builder.widget(&self.component_b);
237///         builder.end(tag);
238///     }
239///
240///     fn focus(&self) -> FocusFlag {
241///         unimplemented!("not in use")
242///     }
243///
244///     fn area(&self) -> Rect {
245///         unimplemented!("not in use")
246///     }
247/// }
248/// ```
249/// for an anonymous container.
250///
251/// focus(), area() and area_z() are only used for the first case.
252/// navigable() is ignored for containers, leave it at the default.
253///
254pub trait HasFocus {
255    /// Build the focus-structure for the container.
256    fn build(&self, builder: &mut FocusBuilder);
257
258    /// Access to the flag for the rest.
259    fn focus(&self) -> FocusFlag;
260
261    /// Area for mouse focus.
262    ///
263    /// This area shouldn't overlap with areas returned by other widgets.
264    /// If it does, the widget should use `z_areas()` for clarification.
265    /// Otherwise, the areas are searched in order of addition.
266    fn area(&self) -> Rect;
267
268    /// Z value for the area.
269    ///
270    /// When testing for mouse interactions the z-value is taken into
271    /// consideration too.
272    fn area_z(&self) -> u16 {
273        0
274    }
275
276    /// Declares how the widget interacts with focus.
277    ///
278    /// Default is [Navigation::Regular].
279    fn navigable(&self) -> Navigation {
280        Navigation::Regular
281    }
282
283    /// Focused?
284    fn is_focused(&self) -> bool {
285        self.focus().get()
286    }
287
288    /// Just lost focus.
289    fn lost_focus(&self) -> bool {
290        self.focus().lost()
291    }
292
293    /// Just gained focus.
294    fn gained_focus(&self) -> bool {
295        self.focus().gained()
296    }
297}
298
299impl Debug for FocusFlag {
300    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
301        f.debug_struct("FocusFlag")
302            .field("name", &self.name())
303            .field("focus", &self.get())
304            .field("gained", &self.gained())
305            .field("lost", &self.lost())
306            .finish()
307    }
308}
309
310impl FocusFlag {
311    /// Create a default flag.
312    pub fn new() -> Self {
313        Self::default()
314    }
315
316    /// Return an identity value.
317    ///
318    /// This uses the memory address of the backing Rc so it will
319    /// be unique during the runtime but will be different for each
320    /// run.
321    pub fn widget_id(&self) -> usize {
322        Rc::as_ptr(&self.0) as usize
323    }
324
325    /// Create a named flag.
326    ///
327    /// The name is only used for debugging.
328    pub fn named(name: &str) -> Self {
329        Self(Rc::new(FocusFlagCore::named(name)))
330    }
331
332    /// Has the focus.
333    #[inline]
334    pub fn get(&self) -> bool {
335        self.0.focus.get()
336    }
337
338    /// Set the focus.
339    #[inline]
340    pub fn set(&self, focus: bool) {
341        self.0.focus.set(focus);
342    }
343
344    /// Get the field-name.
345    #[inline]
346    pub fn name(&self) -> &str {
347        self.0.name.as_ref()
348    }
349
350    /// Just lost the focus.
351    #[inline]
352    pub fn lost(&self) -> bool {
353        self.0.lost.get()
354    }
355
356    #[inline]
357    pub fn set_lost(&self, lost: bool) {
358        self.0.lost.set(lost);
359    }
360
361    /// Just gained the focus.
362    #[inline]
363    pub fn gained(&self) -> bool {
364        self.0.gained.get()
365    }
366
367    #[inline]
368    pub fn set_gained(&self, gained: bool) {
369        self.0.gained.set(gained);
370    }
371
372    /// Reset all flags to false.
373    #[inline]
374    pub fn clear(&self) {
375        self.0.focus.set(false);
376        self.0.lost.set(false);
377        self.0.gained.set(false);
378    }
379}
380
381impl FocusFlagCore {
382    pub(crate) fn named(name: &str) -> Self {
383        Self {
384            name: name.into(),
385            focus: Cell::new(false),
386            gained: Cell::new(false),
387            lost: Cell::new(false),
388        }
389    }
390}
391
392/// Does a match on the state struct of a widget. If `widget_state.lost_focus()` is true
393/// the block is executed. This requires that `widget_state` implements [HasFocus],
394/// but that's the basic requirement for this whole crate.
395///
396/// ```rust ignore
397/// use rat_focus::on_lost;
398///
399/// on_lost!(
400///     state.field1 => {
401///         // do checks
402///     },
403///     state.field2 => {
404///         // do checks
405///     }
406/// );
407/// ```
408#[macro_export]
409macro_rules! on_lost {
410    ($($field:expr => $validate:expr),*) => {{
411        use $crate::HasFocus;
412        $(if $field.lost_focus() { _ = $validate })*
413    }};
414}
415
416/// Does a match on the state struct of a widget. If `widget_state.gained_focus()` is true
417/// the block is executed. This requires that `widget_state` implements [HasFocus],
418/// but that's the basic requirement for this whole crate.
419///
420/// ```rust ignore
421/// use rat_focus::on_gained;
422///
423/// on_gained!(
424///     state.field1 => {
425///         // do prep
426///     },
427///     state.field2 => {
428///         // do prep
429///     }
430/// );
431/// ```
432#[macro_export]
433macro_rules! on_gained {
434    ($($field:expr => $validate:expr),*) => {{
435        use $crate::HasFocus;
436        $(if $field.gained_focus() { _ = $validate })*
437    }};
438}
439
440/// Does a match on the state struct of a widget. If
441/// `widget_state.is_focused()` is true the block is executed.
442/// There is a `_` branch too, that is evaluated if none of the
443/// given widget-states has the focus.
444///
445/// This requires that `widget_state` implements [HasFocus],
446/// but that's the basic requirement for this whole crate.
447///
448/// ```rust ignore
449/// use rat_focus::match_focus;
450///
451/// let res = match_focus!(
452///     state.field1 => {
453///         // do this
454///         true
455///     },
456///     state.field2 => {
457///         // do that
458///         true
459///     },
460///     _ => {
461///         false
462///     }
463/// );
464///
465/// if res {
466///     // react
467/// }
468/// ```
469///
470#[macro_export]
471macro_rules! match_focus {
472    ($($field:expr => $block:expr),* $(, _ => $final:expr)?) => {{
473        use $crate::HasFocus;
474        if false {
475            unreachable!();
476        }
477        $(else if $field.is_focused() { $block })*
478        $(else { $final })?
479    }};
480}
481
482/// Create the implementation of HasFocus for the
483/// given list of struct members.
484#[macro_export]
485macro_rules! impl_has_focus {
486    ($cc:ident:$area:ident: $($n:ident),* for $ty:ty) => {
487        impl $crate::HasFocus for $ty {
488            fn build(&self, builder: &mut $crate::FocusBuilder) {
489                let tag = builder.start(self);
490                $(builder.widget(&self.$n);)*
491                builder.end(tag);
492            }
493
494            fn focus(&self) -> $crate::FocusFlag {
495                self.$cc.clone()
496            }
497
498            fn area(&self) -> Rect {
499                self.$area
500            }
501        }
502    };
503    ($cc:ident: $($n:ident),* for $ty:ty) => {
504        impl $crate::HasFocus for $ty {
505            fn build(&self, builder: &mut $crate::FocusBuilder) {
506                let tag = builder.start(self);
507                $(builder.widget(&self.$n);)*
508                builder.end(tag);
509            }
510
511            fn focus(&self) -> FocusFlag {
512                self.$cc.clone()
513            }
514
515            fn area(&self) -> Rect {
516                Rect::default()
517            }
518        }
519    };
520    ($($n:ident),* for $ty:ty) => {
521        impl $crate::HasFocus for $ty {
522            fn build(&self, builder: &mut $crate::FocusBuilder) {
523                $(builder.widget(&self.$n);)*
524            }
525
526            fn focus(&self) -> FocusFlag {
527                unimplemented!("not defined")
528            }
529
530            fn area(&self) -> Rect {
531                unimplemented!("not defined")
532            }
533        }
534    };
535}