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}