Skip to main content

rat_event/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3use std::cmp::max;
4
5pub mod crossterm;
6pub mod util;
7
8/// All the regular and expected event-handling a widget can do.
9///
10/// All the normal key-handling dependent on an internal focus-state
11/// All the mouse-handling.
12#[derive(Debug, Default, Clone, Copy)]
13pub struct Regular;
14
15/// Handle mouse-events only. Useful whenever you want to write new key-bindings,
16/// but keep the mouse-events.
17#[derive(Debug, Default, Clone, Copy)]
18pub struct MouseOnly;
19
20/// With the latest enhancement of `Focus` this is obsolete.
21///
22/// Focus collects all the areas of widgets and containers and can set a z-index
23/// for each area. With this information it runs a hit-test for each mouse event
24/// and sets and sets the `mouse-focus` flag for each widget and container.
25/// This works well with popups and overlapping widgets. This makes the
26/// differentiation between regular widgets and widgets that might show a popup
27/// that overlaps other widgets obsolete.
28///
29/// If you don't use Focus you will still have to consider the information below.
30///
31/// __Obsoleted__
32///
33/// Popup/Overlays are a bit difficult to handle, as there is no z-order/area tree,
34/// which would direct mouse interactions. We can simulate a z-order in the
35/// event-handler by trying the things with a higher z-order first.
36///
37/// If a widget might show a popup, you have to call its event-handling
38/// before any other widgets that might be (partially) hidden behind the
39/// widget. This applies for e.g. Menubar, Choice and ComboBox.
40/// To make this difference visible in the application code, these widgets
41/// use `Popup` as marker for their event-handling instead of `Regular`.
42///
43/// If you implement a widget with a popup, you must make sure to consume __all__
44/// mouse-events within the widget area to prevent any interaction with
45/// a hidden widget.
46#[derive(Debug, Default, Clone, Copy)]
47pub struct Popup;
48
49/// Event-handling for a dialog like widget.
50///
51/// Similar to [Popup] but with the extra that it consumes _all_ events when active.
52/// No regular widget gets any event, and we have modal behaviour.
53#[derive(Debug, Default, Clone, Copy)]
54pub struct Dialog;
55
56/// Event-handler for double-click on a widget.
57///
58/// Events for this handler must be processed *before* calling
59/// any other event-handling routines for the same widget.
60/// Otherwise, the regular event-handling might interfere with
61/// recognition of double-clicks by consuming the first click.
62///
63/// This event-handler doesn't consume the first click, just
64/// the second one.
65#[derive(Debug, Default, Clone, Copy)]
66pub struct DoubleClick;
67
68///
69/// A very broad trait for an event handler.
70///
71/// Ratatui widgets have two separate structs, one that implements
72/// Widget/StatefulWidget and the associated State. As the StatefulWidget
73/// has a lifetime and is not meant to be kept, HandleEvent should be
74/// implemented for the state struct. It can then modify some state and
75/// the tui can be rendered anew with that changed state.
76///
77/// HandleEvent is not limited to State structs of course, any Type
78/// that wants to interact with events can implement it.
79///
80/// * Event - The actual event type.
81/// * Qualifier - The qualifier allows creating more than one event-handler
82///   for a widget.
83///
84///   This can be used as a variant of type-state, where the type given
85///   selects the widget's behaviour, or to give some external context
86///   to the widget, or to write your own key-bindings for a widget.
87///
88/// * R - Result of event-handling. This can give information to the
89///   application what changed due to handling the event. This can
90///   be very specific for each widget, but there is one general [Outcome]
91///   that describes a minimal set of results.
92///
93///   There should be one value that indicates 'I don't know this event'.
94///   This is expressed with the ConsumedEvent trait.
95///
96pub trait HandleEvent<Event, Qualifier, Return>
97where
98    Return: ConsumedEvent,
99{
100    /// Handle an event.
101    ///
102    /// * self - The widget state.
103    /// * event - Event type.
104    /// * qualifier - Event handling qualifier.
105    ///   This library defines some standard values [Regular], [MouseOnly].
106    ///   Further ideas:
107    ///     * ReadOnly
108    ///     * Special behaviour like DoubleClick, HotKey.
109    /// * Returns some result, see [Outcome]
110    fn handle(&mut self, event: &Event, qualifier: Qualifier) -> Return;
111}
112
113/// Catch all event-handler for the null state `()`.
114impl<E, Q> HandleEvent<E, Q, Outcome> for () {
115    fn handle(&mut self, _event: &E, _qualifier: Q) -> Outcome {
116        Outcome::Continue
117    }
118}
119
120/// When calling multiple event-handlers, the minimum information required
121/// from the result is 'has consumed/didn't consume' the event.
122///
123/// The event-handler **may** also react to the event and not call it
124/// 'consuming the event'. But this is tricky, non-obvious and frowned upon.
125/// The caller **may** also just ignore the fact.
126///
127/// See also [flow] and [try_flow] and the extra [break_flow].
128pub trait ConsumedEvent {
129    /// Is this the 'consumed' result.
130    fn is_consumed(&self) -> bool;
131
132    /// Or-Else chaining with `is_consumed()` as the split.
133    #[inline(always)]
134    fn or_else<F>(self, f: F) -> Self
135    where
136        F: FnOnce() -> Self,
137        Self: Sized,
138    {
139        if self.is_consumed() { self } else { f() }
140    }
141
142    /// Or-Else chaining with `is_consumed()` as the split.
143    #[inline(always)]
144    fn or_else_try<F, E>(self, f: F) -> Result<Self, E>
145    where
146        Self: Sized,
147        F: FnOnce() -> Result<Self, E>,
148    {
149        if self.is_consumed() {
150            Ok(self)
151        } else {
152            Ok(f()?)
153        }
154    }
155
156    /// And_then-chaining based on is_consumed().
157    /// Returns max(self, f()).
158    #[inline(always)]
159    fn and_then<F>(self, f: F) -> Self
160    where
161        Self: Sized + Ord,
162        F: FnOnce() -> Self,
163    {
164        if self.is_consumed() {
165            max(self, f())
166        } else {
167            self
168        }
169    }
170
171    /// And_then-chaining based on is_consumed().
172    /// Returns max(self, f()).
173    #[inline(always)]
174    fn and_then_try<F, E>(self, f: F) -> Result<Self, E>
175    where
176        Self: Sized + Ord,
177        F: FnOnce() -> Result<Self, E>,
178    {
179        if self.is_consumed() {
180            Ok(max(self, f()?))
181        } else {
182            Ok(self)
183        }
184    }
185
186    /// Then-chaining. Returns max(self, f()).
187    #[inline(always)]
188    #[deprecated(since = "1.2.2", note = "use and_then()")]
189    fn and<F>(self, f: F) -> Self
190    where
191        Self: Sized + Ord,
192        F: FnOnce() -> Self,
193    {
194        if self.is_consumed() {
195            max(self, f())
196        } else {
197            self
198        }
199    }
200
201    /// Then-chaining. Returns max(self, f()).
202    #[inline(always)]
203    #[deprecated(since = "1.2.2", note = "use and_then_try()")]
204    fn and_try<F, E>(self, f: F) -> Result<Self, E>
205    where
206        Self: Sized + Ord,
207        F: FnOnce() -> Result<Self, E>,
208    {
209        if self.is_consumed() {
210            Ok(max(self, f()?))
211        } else {
212            Ok(self)
213        }
214    }
215}
216
217impl<V, E> ConsumedEvent for Result<V, E>
218where
219    V: ConsumedEvent,
220{
221    fn is_consumed(&self) -> bool {
222        match self {
223            Ok(v) => v.is_consumed(),
224            Err(_) => true,
225        }
226    }
227}
228
229/// The baseline outcome for an event-handler.
230///
231/// A widget can define its own type, if it has more things to report.
232/// It would be nice if those types are convertible to/from `Outcome`
233/// and implement `ConsumedEvent` as well.
234#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
235pub enum Outcome {
236    /// The given event has not been used at all.
237    #[default]
238    Continue,
239    /// The event has been recognized, but nothing noticeable has changed.
240    /// Further processing for this event may stop.
241    /// Rendering the ui is not necessary.
242    Unchanged,
243    /// The event has been recognized and there is some change due to it.
244    /// Further processing for this event may stop.
245    /// Rendering the ui is advised.
246    Changed,
247}
248
249impl ConsumedEvent for Outcome {
250    fn is_consumed(&self) -> bool {
251        *self != Outcome::Continue
252    }
253}
254
255/// Widgets often define functions that return bool to indicate a changed state.
256/// This converts `true` / `false` to `Outcome::Changed` / `Outcome::Unchanged`.
257impl From<bool> for Outcome {
258    fn from(value: bool) -> Self {
259        if value {
260            Outcome::Changed
261        } else {
262            Outcome::Unchanged
263        }
264    }
265}
266
267/// __Experimental__
268///
269/// Convert to Outcome from bool.
270///
271/// The problem is that there are two possible combinations
272/// * true/false -> Changed/Unchanged
273/// * true/false -> Changed/Continue
274///
275/// Which one, depends on the use-case. Do you want the event
276/// to be handled finally or do you want to give a parent widget
277/// the chance to react to an event unless your widget really
278/// does something worth-wile.
279///
280/// This occurs for navigation keys.
281/// - Left navigates backwards in your widget.
282///   - When the start is reached the widget is done.
283///     - There may be a super widget that can do it's super navigation
284///       if it knows that fact.
285///       => return Outcome::Changed as long as you change your location
286///          and Outcome::Continue otherwise.
287///
288/// For the Changed/Unchanged pair I don't have a good example, so
289/// maybe that was the wrong default anyway. But I can't change the
290/// `From<bool>` behavior as that would *really* break things.
291///
292/// Hence, this trait.  
293///
294pub trait FromBool {
295    fn as_changed_unchanged(self) -> Outcome;
296    fn as_changed_continue(self) -> Outcome;
297}
298
299impl FromBool for bool {
300    fn as_changed_unchanged(self) -> Outcome {
301        match self {
302            true => Outcome::Changed,
303            false => Outcome::Unchanged,
304        }
305    }
306
307    fn as_changed_continue(self) -> Outcome {
308        match self {
309            true => Outcome::Changed,
310            false => Outcome::Continue,
311        }
312    }
313}
314
315/// Tries to unify the currently 3 flow! constructs.
316///
317/// * `flow!(expr) -> event_flow!(return expr)`
318///   The non Result case stays gets a `return`. It's rather uncommon
319///   to __not__ have a Result during event-handling, so this should be fine.
320/// * `try_flow!(expr) -> event_flow!(expr)`
321///   This becomes the main branch.
322/// * `break_flow!('x: expr) -> event_flow!(break 'x expr)`
323///   This now matches actual rust syntax, which is good for rustfmt.
324///   The `break 'x` is stripped and reapplied after result-conversion.
325///
326/// __...___
327///
328/// I'll try this out... If it's fine I'll leave the other macros
329/// with a discouraging remark and promote this variant.
330///
331/// note: of course the default is diametrical when you write
332/// library code for a new widget. as usual :-|. stick with it
333/// though as libraries should be used more often than written.
334#[macro_export]
335macro_rules! event_flow {
336    (log $n:ident: return $x:expr) => {{
337        use log::debug;
338        use $crate::ConsumedEvent;
339        let r = $x;
340        if r.is_consumed() {
341            debug!("{} {:#?}", stringify!($n), r);
342            return r.into();
343        } else {
344            debug!("{} continue", stringify!($n));
345        }
346    }};
347    (log $n:ident: break $l:lifetime $x:expr) => {{
348        use log::debug;
349        use $crate::ConsumedEvent;
350        let r = $x;
351        if r.is_consumed() {
352            debug!("{} {:#?}", stringify!($n), r);
353            break $l r.into();
354        } else {
355            debug!("{} continue", stringify!($n));
356        }
357    }};
358    (log $n:ident: $x:expr) => {{
359        use log::debug;
360        use $crate::ConsumedEvent;
361        let r = $x;
362        if r.is_consumed() {
363            debug!("{} {:#?}", stringify!($n), r);
364            return Ok(r.into());
365        } else {
366            debug!("{} continue", stringify!($n));
367        }
368    }};
369    (break $l:lifetime $x:expr) => {{
370        use $crate::ConsumedEvent;
371        let r = $x;
372        if r.is_consumed() {
373            break $l r.into();
374        }
375    }};
376    (return $x:expr) => {{
377        use $crate::ConsumedEvent;
378        let r = $x;
379        if r.is_consumed() {
380            return r.into();
381        }
382    }};
383    ($x:expr) => {{
384        use $crate::ConsumedEvent;
385        let r = $x;
386        if r.is_consumed() {
387            return Ok(r.into());
388        }
389    }};
390
391}
392
393/// Use `event_flow!` instead.
394///
395/// Returns from the current function if the block returns
396/// a value for which `[ConsumedEvent::is_consumed] == true`.
397///
398/// This breaks the control-flow of the current function effectively.
399///
400/// As the return type of the current function can differ from
401/// whatever function has been called, an `ìnto()` conversion
402/// is thrown in too.  
403///
404/// *The difference to [try_flow] is that this on doesn't Ok-wrap the result.*
405///
406/// Extras: If you add a marker as in `flow!(log ident: {...});`
407/// the result of the operation is written to the log.
408#[macro_export]
409macro_rules! flow {
410    (log $n:ident: $x:expr) => {{
411        use log::debug;
412        use $crate::ConsumedEvent;
413        let r = $x;
414        if r.is_consumed() {
415            debug!("{} {:#?}", stringify!($n), r);
416            return r.into();
417        } else {
418            debug!("{} continue", stringify!($n));
419        }
420    }};
421    ($x:expr) => {{
422        use $crate::ConsumedEvent;
423        let r = $x;
424        if r.is_consumed() {
425            return r.into();
426        }
427    }};
428}
429
430/// Use `event_flow!` instead.
431///
432/// Returns from the current function if the block returns
433/// a value for which `[ConsumedEvent::is_consumed] == true`.
434///
435/// This breaks the control-flow of the current function effectively.
436///
437/// As the return type of the current function can differ from
438/// whatever function has been called, an `ìnto()` conversion
439/// is thrown in too.
440///
441/// *The difference to [flow] is that this one Ok-wraps the result.*
442///
443/// Extras: If you add a marker as in `try_flow!(log ident: {...});`
444/// the result of the operation is written to the log.
445#[macro_export]
446macro_rules! try_flow {
447    (log $n:ident: $x:expr) => {{
448        use log::debug;
449        use $crate::ConsumedEvent;
450        let r = $x;
451        if r.is_consumed() {
452            debug!("{} {:#?}", stringify!($n), r);
453            return Ok(r.into());
454        } else {
455            debug!("{} continue", stringify!($n));
456        }
457    }};
458    ($x:expr) => {{
459        use $crate::ConsumedEvent;
460        let r = $x;
461        if r.is_consumed() {
462            return Ok(r.into());
463        }
464    }};
465}
466
467/// Use `event_flow!` instead.
468///
469/// This macro doesn't return from the current function, but
470/// does a labeled break if the block returns a value for
471/// which `[ConsumedEvent::is_consumed] == true`.
472///
473/// It also does and `into()` conversion and *breaks* with the
474/// result to the enclosing block given as a `'l:{}` labeled block.
475///
476/// * The difference to [try_flow] is that this on doesn't Ok-wrap the result.*
477/// * The difference to [flow] is that this breaks instead of return_ing.
478///
479/// Extras: If you add a marker as in `break_flow!(log 'f: ident: {...});`
480/// the result of the operation is written to the log.
481#[macro_export]
482macro_rules! break_flow {
483    (log $n:ident: $l:lifetime: $x:expr) => {{
484        use log::debug;
485        use $crate::ConsumedEvent;
486        let r = $x;
487        if r.is_consumed() {
488            debug!("{} {:#?}", stringify!($n), r);
489            break $l r.into();
490        } else {
491            debug!("{} continue", stringify!($n));
492        }
493    }};
494    ($l:lifetime: $x:expr) => {{
495        use $crate::ConsumedEvent;
496        let r = $x;
497        if r.is_consumed() {
498            break $l r.into();
499        }
500    }};
501}
502
503mod _private {
504    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
505    pub struct NonExhaustive;
506}