1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
#![doc = include_str!("../readme.md")]
use std::cmp::max;
pub mod crossterm;
pub mod util;
/// All the regular and expected event-handling a widget can do.
///
/// All the normal key-handling, maybe dependent on an internal
/// focus-state, all the mouse-handling.
#[derive(Debug, Default)]
pub struct Regular;
/// Handle mouse-events only. Useful whenever you want to write new key-bindings,
/// but keep the mouse-events.
#[derive(Debug, Default)]
pub struct MouseOnly;
/// Popup/Overlays are a bit difficult to handle, as there is no z-order/area tree,
/// which would direct mouse interactions. We can simulate a z-order in the
/// event-handler by trying the things with a higher z-order first.
///
/// If a widget should be seen as pure overlay, it would define only a Popup
/// event-handler. In the event-handling functions you would call all Popup
/// event-handlers before the regular ones.
///
/// Example:
/// * Context menu. If the context-menu is active, it can consume all mouse-events
/// that fall into its range, and the widgets behind it only get the rest.
/// * Menubar. Would define _two_ event-handlers, a regular one for all events
/// on the main menu bar, and a popup event-handler for the menus. The event-handling
/// function calls the popup handler first and the regular one at some time later.
#[derive(Debug)]
pub struct Popup;
/// Event-handling for a dialog like widget.
///
/// Similar to [Popup] but with the extra that it consumes _all_ events when active.
/// No regular widget gets any event, and we have modal behaviour.
#[derive(Debug)]
pub struct Dialog;
/// Event-handler for double-click on a widget.
///
/// Events for this handler must be processed *before* calling
/// any other event-handling routines for the same table.
/// Otherwise, the regular event-handling might interfere with
/// recognition of double-clicks by consuming the first click.
///
/// This event-handler doesn't consume the first click, just
/// the second one.
#[derive(Debug, Default)]
pub struct DoubleClick;
///
/// A very broad trait for an event handler.
///
/// Ratatui widgets have two separate structs, one that implements
/// Widget/StatefulWidget and the associated State. As the StatefulWidget
/// has a lifetime and is not meant to be kept, HandleEvent should be
/// implemented for the state struct. It can then modify some state and
/// the tui can be rendered anew with that changed state.
///
/// HandleEvent is not limited to State structs of course, any Type
/// that wants to interact with events can implement it.
///
/// * Event - The actual event type.
/// * Qualifier - The qualifier allows creating more than one event-handler
/// for a widget.
///
/// This can be used as a variant of type-state, where the type given
/// selects the widget's behaviour, or to give some external context
/// to the widget, or to write your own key-bindings for a widget.
///
/// * R - Result of event-handling. This can give information to the
/// application what changed due to handling the event. This can
/// be very specific for each widget, but there is one general [Outcome]
/// that describes a minimal set of results.
///
/// There should be one value that indicates 'I don't know this event'.
/// This is expressed with the ConsumedEvent trait.
///
pub trait HandleEvent<Event, Qualifier, R: ConsumedEvent> {
/// Handle an event.
///
/// * self - The widget state.
/// * event - Event struct.
/// * qualifier - Event handling qualifier.
/// This library defines some standard values [Regular], [MouseOnly],
/// [Popup] and [Dialog].
///
/// Further ideas:
/// * ReadOnly
/// * Special behaviour like DoubleClick, HotKey.
/// * Returns some result, see [Outcome]
fn handle(&mut self, event: &Event, qualifier: Qualifier) -> R;
}
/// Catch all event-handler for the null state `()`.
impl<E, Q> HandleEvent<E, Q, Outcome> for () {
fn handle(&mut self, _event: &E, _qualifier: Q) -> Outcome {
Outcome::Continue
}
}
/// When calling multiple event-handlers, the minimum information required
/// from the result is consumed the event/didn't consume the event.
///
/// See also [flow], [flow_ok] and [or_else] macros.
pub trait ConsumedEvent {
/// Is this the 'consumed' result.
fn is_consumed(&self) -> bool;
/// Or-Else chaining with `is_consumed()` as the split.
fn or_else<F>(self, f: F) -> Self
where
F: FnOnce() -> Self,
Self: Sized,
{
if self.is_consumed() {
self
} else {
f()
}
}
/// Then-chaining. Returns max(self, f()).
fn then<F>(self, f: F) -> Self
where
Self: Sized + Ord,
F: FnOnce() -> Self,
{
max(self, f())
}
}
impl<V, E> ConsumedEvent for Result<V, E>
where
V: ConsumedEvent,
{
fn is_consumed(&self) -> bool {
match self {
Ok(v) => v.is_consumed(),
Err(_) => true, // this can somewhat be argued for.
}
}
}
/// The baseline outcome for an event-handler.
///
/// A widget can define its own type, if it has more things to report.
/// It would be nice if those types are convertible to/from Outcome.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Outcome {
/// The given event has not been used at all.
Continue,
/// The event has been recognized, but nothing noticeable has changed.
/// Further processing for this event may stop.
/// Rendering the ui is not necessary.
Unchanged,
/// The event has been recognized and there is some change due to it.
/// Further processing for this event may stop.
/// Rendering the ui is advised.
Changed,
}
impl ConsumedEvent for Outcome {
fn is_consumed(&self) -> bool {
!matches!(self, Outcome::Continue)
}
}
/// Widgets often define functions that return bool to indicate a changed state.
/// This converts `true` / `false` to `Outcome::Changed` / `Outcome::Unchanged`.
impl From<bool> for Outcome {
fn from(value: bool) -> Self {
if value {
Outcome::Changed
} else {
Outcome::Unchanged
}
}
}
/// Breaks the control-flow if the block returns a value
/// for which [ConsumedEvent::is_consumed] is true.
///
/// It does the classic `into()`-conversion and returns the result.
///
/// *The difference to [flow_ok] is that this on doesn't Ok-wrap the result.*
///
/// Extras: If you add a marker as in `flow_ok!(log ident: {...});`
/// the result of the operation is written to the log.
///
/// Extras: Focus handling is tricky, see [rat-focus](https://docs.rs/rat-focus/).
/// The result of focus handling is a second result of an event-handler,
/// that must be combined to form the single return value that a function
/// can have.
/// Therefore, one more extension for this macro:
/// `flow_ok!(_do_something_with_an_outcome(), consider focus_outcome)`.
/// This returns max(outcome, focus_outcome).
#[macro_export]
macro_rules! flow {
(log $n:ident: $x:expr) => {{
use log::debug;
use $crate::ConsumedEvent;
let r = $x;
if r.is_consumed() {
debug!("{} {:#?}", stringify!($n), r);
return r.into();
} else {
debug!("{} continue", stringify!($n));
_ = r;
}
}};
($x:expr, consider $f:expr) => {{
use std::cmp::max;
use $crate::ConsumedEvent;
let r = $x;
if r.is_consumed() {
return max(r.into(), $f);
} else {
_ = r;
}
}};
($x:expr) => {{
use $crate::ConsumedEvent;
let r = $x;
if r.is_consumed() {
return r.into();
} else {
_ = r;
}
}};
}
/// Breaks the control-flow if the block returns a value
/// for which [ConsumedEvent::is_consumed] is true.
///
/// It does the classic `into()`-conversion and returns the result.
///
/// *The difference to [flow] is that this one Ok-wraps the result.*
///
/// Extras: If you add a marker as in `flow_ok!(log ident: {...});`
/// the result of the operation is written to the log.
///
/// Extras: Focus handling is tricky, see [rat-focus](https://docs.rs/rat-focus/).
/// The result of focus handling is a second result of an event-handler,
/// that must be combined to form the single return value that a function
/// can have.
/// Therefore, one more extension for this macro:
/// `flow_ok!(_do_something_with_an_outcome(), consider focus_outcome)`.
/// This returns max(outcome, focus_outcome).
#[macro_export]
macro_rules! flow_ok {
(log $n:ident: $x:expr) => {{
use log::debug;
use $crate::ConsumedEvent;
let r = $x;
if r.is_consumed() {
debug!("{} {:#?}", stringify!($n), r);
return Ok(r.into());
} else {
debug!("{} continue", stringify!($n));
_ = r;
}
}};
($x:expr, consider $f:expr) => {{
use std::cmp::max;
use $crate::ConsumedEvent;
let r = $x;
if r.is_consumed() {
return Ok(max(r.into(), $f));
} else {
_ = r;
}
}};
($x:expr) => {{
use $crate::ConsumedEvent;
let r = $x;
if r.is_consumed() {
return Ok(r.into());
} else {
_ = r;
}
}};
}
/// Another control-flow macro based on [ConsumedEvent].
///
/// If you don't want to return early from event-handling, you can use this.
///
/// Define a mut that holds the result, and `or_else!` through all
/// event-handlers.
///
/// ```not_rust
/// let mut r;
///
/// r = first_activity();
/// or_else!(r, second_activity());
/// or_else!(r, third_activity());
/// ```
///
/// This executes `second_activity()` if `!r.is_consumed()` and stores the
/// result in r. The same with `third_activity` ...
///
#[macro_export]
macro_rules! or_else {
($x:ident, $e:expr) => {
if !$crate::ConsumedEvent::is_consumed(&$x) {
$x = $e;
}
};
}