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 `obscured` flag for each widget and container that is
25/// not hit. 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 [event_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///
294#[allow(clippy::wrong_self_convention)]
295pub trait FromBool {
296 fn as_changed_unchanged(self) -> Outcome;
297 fn as_changed_continue(self) -> Outcome;
298}
299
300impl FromBool for bool {
301 fn as_changed_unchanged(self) -> Outcome {
302 match self {
303 true => Outcome::Changed,
304 false => Outcome::Unchanged,
305 }
306 }
307
308 fn as_changed_continue(self) -> Outcome {
309 match self {
310 true => Outcome::Changed,
311 false => Outcome::Continue,
312 }
313 }
314}
315
316/// Unifies the 3 other macros, and is now the preferred way to
317/// do this.
318///
319/// * `event_flow!(expr)`
320///
321/// Returns from the current function if `{expr}.is_consumed() == true`.
322/// This *does* Ok-wrap the result of `expr`.
323///
324/// * `event_flow!(return expr)`
325///
326/// Returns from the current function if `{expr}.is_consumed() == true`.
327/// This *does not* Ok-wrap the result of `expr`.
328///
329/// * `event_flow!(break 'x expr)`
330///
331/// This doesn't return from the current function but does
332/// a labeled break if `{expr}.is_consumed() == true`.
333/// This does *not* Ok-wrap the result of `expr`.
334///
335/// In any case the resulting value gets an `into()` conversion
336/// to match `?` behavior.
337///
338#[macro_export]
339macro_rules! event_flow {
340 (log $n:ident: return $x:expr) => {{
341 use log::debug;
342 use $crate::ConsumedEvent;
343 let r = $x;
344 if r.is_consumed() {
345 debug!("{} {:#?}", stringify!($n), r);
346 return r.into();
347 } else {
348 debug!("{} continue", stringify!($n));
349 }
350 }};
351 (log $n:ident: break $l:lifetime $x:expr) => {{
352 use log::debug;
353 use $crate::ConsumedEvent;
354 let r = $x;
355 if r.is_consumed() {
356 debug!("{} {:#?}", stringify!($n), r);
357 break $l r.into();
358 } else {
359 debug!("{} continue", stringify!($n));
360 }
361 }};
362 (log $n:ident: $x:expr) => {{
363 use log::debug;
364 use $crate::ConsumedEvent;
365 let r = $x;
366 if r.is_consumed() {
367 debug!("{} {:#?}", stringify!($n), r);
368 return Ok(r.into());
369 } else {
370 debug!("{} continue", stringify!($n));
371 }
372 }};
373 (break $l:lifetime $x:expr) => {{
374 use $crate::ConsumedEvent;
375 let r = $x;
376 if r.is_consumed() {
377 break $l r.into();
378 }
379 }};
380 (return $x:expr) => {{
381 use $crate::ConsumedEvent;
382 let r = $x;
383 if r.is_consumed() {
384 return r.into();
385 }
386 }};
387 ($x:expr) => {{
388 use $crate::ConsumedEvent;
389 let r = $x;
390 if r.is_consumed() {
391 return Ok(r.into());
392 }
393 }};
394
395}
396
397/// Use `event_flow!` instead.
398///
399/// Returns from the current function if the block returns
400/// a value for which `[ConsumedEvent::is_consumed] == true`.
401///
402/// This breaks the control-flow of the current function effectively.
403///
404/// As the return type of the current function can differ from
405/// whatever function has been called, an `ìnto()` conversion
406/// is thrown in too.
407///
408/// *The difference to [try_flow] is that this on doesn't Ok-wrap the result.*
409///
410/// Extras: If you add a marker as in `flow!(log ident: {...});`
411/// the result of the operation is written to the log.
412#[macro_export]
413macro_rules! flow {
414 (log $n:ident: $x:expr) => {{
415 use log::debug;
416 use $crate::ConsumedEvent;
417 let r = $x;
418 if r.is_consumed() {
419 debug!("{} {:#?}", stringify!($n), r);
420 return r.into();
421 } else {
422 debug!("{} continue", stringify!($n));
423 }
424 }};
425 ($x:expr) => {{
426 use $crate::ConsumedEvent;
427 let r = $x;
428 if r.is_consumed() {
429 return r.into();
430 }
431 }};
432}
433
434/// Use `event_flow!` instead.
435///
436/// Returns from the current function if the block returns
437/// a value for which `[ConsumedEvent::is_consumed] == true`.
438///
439/// This breaks the control-flow of the current function effectively.
440///
441/// As the return type of the current function can differ from
442/// whatever function has been called, an `ìnto()` conversion
443/// is thrown in too.
444///
445/// *The difference to [flow] is that this one Ok-wraps the result.*
446///
447/// Extras: If you add a marker as in `try_flow!(log ident: {...});`
448/// the result of the operation is written to the log.
449#[macro_export]
450macro_rules! try_flow {
451 (log $n:ident: $x:expr) => {{
452 use log::debug;
453 use $crate::ConsumedEvent;
454 let r = $x;
455 if r.is_consumed() {
456 debug!("{} {:#?}", stringify!($n), r);
457 return Ok(r.into());
458 } else {
459 debug!("{} continue", stringify!($n));
460 }
461 }};
462 ($x:expr) => {{
463 use $crate::ConsumedEvent;
464 let r = $x;
465 if r.is_consumed() {
466 return Ok(r.into());
467 }
468 }};
469}
470
471/// Use `event_flow!` instead.
472///
473/// This macro doesn't return from the current function, but
474/// does a labeled break if the block returns a value for
475/// which `[ConsumedEvent::is_consumed] == true`.
476///
477/// It also does and `into()` conversion and *breaks* with the
478/// result to the enclosing block given as a `'l:{}` labeled block.
479///
480/// * The difference to [try_flow] is that this on doesn't Ok-wrap the result.*
481/// * The difference to [flow] is that this breaks instead of return_ing.
482///
483/// Extras: If you add a marker as in `break_flow!(log 'f: ident: {...});`
484/// the result of the operation is written to the log.
485#[macro_export]
486macro_rules! break_flow {
487 (log $n:ident: $l:lifetime: $x:expr) => {{
488 use log::debug;
489 use $crate::ConsumedEvent;
490 let r = $x;
491 if r.is_consumed() {
492 debug!("{} {:#?}", stringify!($n), r);
493 break $l r.into();
494 } else {
495 debug!("{} continue", stringify!($n));
496 }
497 }};
498 ($l:lifetime: $x:expr) => {{
499 use $crate::ConsumedEvent;
500 let r = $x;
501 if r.is_consumed() {
502 break $l r.into();
503 }
504 }};
505}
506
507mod _private {
508 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
509 pub struct NonExhaustive;
510}