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;