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