Skip to main content

urushi_tui_app/
source.rs

1//! What the runtime's own sources observe.
2//!
3//! An application never reads a terminal, a clock, or a signal handler itself.
4//! It declares a subscription to one of the runtime's sources and supplies the
5//! function that turns the source's own value — an [`Input`], a [`Surface`], a
6//! [`Signal`] — into the application's message. These are those values.
7
8use urushi_terminal::WindowSize;
9pub use urushi_terminal::{
10    FocusChange, KeyCode, KeyEvent, KeyEventState, KeyKind, MediaKeyCode, ModifierKeyCode,
11    Modifiers, MouseButton, MouseEvent, MouseKind, PixelSize as CellPixels,
12    TerminalSize as SurfaceSize,
13};
14
15/// Terminal input as it reaches an application.
16///
17/// The terminal session decides which of these the terminal produces at all:
18/// bracketed paste turns a paste into one [`Input::Paste`] instead of a run of
19/// keys, focus reporting produces [`Input::Focus`], and keyboard enhancement
20/// decides whether a key release can be reported at all. Those are options on
21/// the runtime's entry point, not on the subscription.
22///
23/// Text arrives as [`KeyCode::Char`] keys and as pastes; there is no separate
24/// text event. A size change is not input — it is the [`Surface`] source.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum Input {
27    /// A key the terminal reported.
28    Key(KeyEvent),
29    /// A bracketed paste, delivered whole rather than as its keys.
30    Paste(String),
31    /// The terminal window gained or lost focus.
32    Focus(FocusChange),
33    /// A mouse action reported while capture is enabled.
34    Mouse(MouseEvent),
35}
36
37/// What an application can observe about the surface it draws on.
38///
39/// An application needs these facts only where they carry application meaning —
40/// a viewport measured in cells, a layout that changes with the width. They
41/// reach `update` as a message and are read from the model by `view`, which
42/// receives no surface input of its own.
43///
44/// What the terminal can do with graphics is not here. Protocol capabilities
45/// select a runtime presentation strategy; they are not application state.
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
47pub struct Surface {
48    /// The drawable area, in cells.
49    pub size: SurfaceSize,
50    /// How large one cell is in pixels, where the terminal reports it.
51    pub cell_pixels: Option<CellPixels>,
52}
53
54impl Surface {
55    /// A surface of `columns` by `rows` cells, whose pixel geometry the
56    /// terminal did not report.
57    pub const fn new(columns: usize, rows: usize) -> Self {
58        Self {
59            size: SurfaceSize::new(columns, rows),
60            cell_pixels: None,
61        }
62    }
63
64    pub(crate) fn from_window_size(window: WindowSize) -> Self {
65        let size = window.cells();
66        let cell_pixels = window.cell_pixels();
67        Self { size, cell_pixels }
68    }
69}
70
71/// A process signal an application can subscribe to.
72///
73/// A handler is installed only for a signal the application declared, so a
74/// signal it did not declare keeps the process's default disposition.
75///
76/// `SIGWINCH` is not here: a size change is the [`Surface`] source. Nor does
77/// [`Signal::Interrupt`] cover `Ctrl-C`, which under raw mode is an ordinary
78/// key on the [`Input`] source; it covers the `SIGINT` another process sends.
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub enum Signal {
81    /// `SIGINT`, sent from outside the terminal.
82    Interrupt,
83    /// `SIGTERM`, the ordinary request to terminate.
84    Terminate,
85    /// `SIGHUP`, the terminal that carried this process went away.
86    Hangup,
87    /// `SIGQUIT`.
88    Quit,
89}
90
91#[cfg(test)]
92#[path = "source_tests.rs"]
93mod tests;