Skip to main content

ez_tui/types/
event.rs

1use crate::{Error, EzCptId, EzCptIds, Matcher};
2use crossterm::event::{Event as CrosstermEvent, KeyEventKind};
3use crossterm::event::{KeyEvent, MouseEvent};
4use std::fmt::Debug;
5use std::hash::Hash;
6use tracing::trace;
7/// Trait to be defined on your client's messages enum
8pub trait EzMsg: PartialEq + Debug + Clone + Send + 'static {}
9
10/// A dummy type to be used when you don't have any messages
11#[derive(PartialEq, Eq, Clone, Debug, Hash)]
12pub struct NoClientMsg;
13impl EzMsg for NoClientMsg {}
14
15/// All the events that can be sent across the application.
16/// Your custom events will be wrapped inside [`EzEvent::Client`].
17#[derive(Debug, Eq, PartialEq, Clone)]
18pub enum EzEvent<CID, CM>
19where
20    CID: EzCptIds,
21    CM: EzMsg,
22{
23    /// A keyboard event from crossterm
24    Keyboard(KeyEvent),
25    /// A mouse event from crossterm
26    Mouse(MouseEvent),
27    /// A window resize event from crossterm
28    WindowResize(u16, u16),
29
30    /// Force the focus to a given component ID.
31    ForceFocus(EzCptId<CID>),
32    /// Force the blur of the current focused component.
33    ForceBlur(),
34    /// A focus gained event from crossterm. This is unrelated to the focus in-app; it means the terminal holding your app received focus from your window manager.
35    FocusGained,
36    /// A focus lost event from crossterm. This is unrelated to the focus in-app; it means the terminal holding your app received focus from your window manager.
37    FocusLost,
38    /// A paste event from crossterm
39    Paste(String),
40
41    /// The event send after app is started
42    Init,
43    /// The event send on each tick (not used by the lib)
44    Tick,
45    /// The event send to trigger a render
46    Render,
47    /// The vent sent to order the app to gracefully exit
48    Quit,
49
50    /// An empty event to be returned when no event is emited, but we want to prevent the default app event handling (focus/quit)
51    None,
52
53    /// Sent when an unexpected error occurs
54    Error(Error),
55
56    /// Set the dynamic legend
57    UpdateLegend(String, Vec<Matcher>),
58
59    /// Wraps any events related to your business logic.
60    Client(CM),
61}
62impl<CID, CM> EzEvent<CID, CM>
63where
64    CID: EzCptIds,
65    CM: EzMsg,
66{
67    /// Map this event to [`Some`]([`KeyEvent`]) if possible; [`None`] otherwise.
68    pub fn as_kb(&self) -> Option<&KeyEvent> {
69        if let EzEvent::Keyboard(k) = self {
70            Some(k)
71        } else {
72            None
73        }
74    }
75
76    /// Map this event to [`Some`]([`MouseEvent`]) if possible; [`None`] otherwise.
77    pub fn as_mouse(&self) -> Option<&MouseEvent> {
78        if let EzEvent::Mouse(m) = self {
79            Some(m)
80        } else {
81            None
82        }
83    }
84    /// Map this event to [`Some`]([`(&u16, &u16)`]) if it's a [`EzEvent::WindowResize`]; [`None`] otherwise.
85    pub fn as_resize(&self) -> Option<(&u16, &u16)> {
86        if let EzEvent::WindowResize(x, y) = self {
87            Some((x, y))
88        } else {
89            None
90        }
91    }
92
93    /// Map this event to your business logic event if possible; [`None`] otherwise.
94    pub fn as_client(&self) -> Option<&CM> {
95        if let EzEvent::Client(msg) = self {
96            Some(msg)
97        } else {
98            None
99        }
100    }
101}
102
103impl<CID, CM> EzEvent<CID, CM>
104where
105    CID: EzCptIds,
106    CM: EzMsg,
107{
108    /// Try to convert this event to a crossterm event.
109    pub fn try_original(&self) -> Option<CrosstermEvent> {
110        match self {
111            EzEvent::Keyboard(key) => Some(CrosstermEvent::Key(*key)),
112            EzEvent::Mouse(mouse) => Some(CrosstermEvent::Mouse(*mouse)),
113            EzEvent::WindowResize(x, y) => Some(CrosstermEvent::Resize(*x, *y)),
114            EzEvent::FocusLost => Some(CrosstermEvent::FocusLost),
115            EzEvent::FocusGained => Some(CrosstermEvent::FocusGained),
116            EzEvent::Paste(s) => Some(CrosstermEvent::Paste(s.to_string())),
117            _ => None,
118        }
119    }
120}
121
122/// Convert a crossterm event to a `Event`.
123/// Only the key events are not a one to one conversion;
124/// we have to differentiate between key press, key repeat, and key release.
125impl<CID, CM> From<CrosstermEvent> for EzEvent<CID, CM>
126where
127    CID: EzCptIds,
128    CM: EzMsg,
129{
130    fn from(event: CrosstermEvent) -> Self {
131        trace!("{event:?}");
132        match event {
133            CrosstermEvent::Key(key) => match key.kind {
134                //TODO distinguish
135                KeyEventKind::Press | KeyEventKind::Repeat | KeyEventKind::Release => {
136                    EzEvent::Keyboard(key)
137                }
138            },
139            CrosstermEvent::Mouse(mouse) => EzEvent::Mouse(mouse),
140            CrosstermEvent::Resize(x, y) => EzEvent::WindowResize(x, y),
141            CrosstermEvent::FocusLost => EzEvent::FocusLost,
142            CrosstermEvent::FocusGained => EzEvent::FocusGained,
143            CrosstermEvent::Paste(s) => EzEvent::Paste(s),
144        }
145    }
146}