1mod app;
11pub mod input;
12pub mod scroll;
13pub mod state;
14pub mod widgets;
15
16use anyhow::Result;
17use app::App;
18use crossterm::event::{KeyCode, KeyModifiers};
19use ralph_adapters::pty_handle::PtyHandle;
20use ralph_proto::{Event, HatId};
21use std::collections::HashMap;
22use std::sync::{Arc, Mutex};
23
24pub use state::{LoopMode, TuiState};
25pub use widgets::terminal::TerminalWidget;
26pub use widgets::{footer, header};
27
28pub struct Tui {
30 state: Arc<Mutex<TuiState>>,
31 pty_handle: Option<PtyHandle>,
32 prefix_key: KeyCode,
33 prefix_modifiers: KeyModifiers,
34}
35
36impl Tui {
37 pub fn new() -> Self {
39 Self {
40 state: Arc::new(Mutex::new(TuiState::new())),
41 pty_handle: None,
42 prefix_key: KeyCode::Char('a'),
43 prefix_modifiers: KeyModifiers::CONTROL,
44 }
45 }
46
47 #[must_use]
49 pub fn with_prefix(mut self, prefix_key: KeyCode, prefix_modifiers: KeyModifiers) -> Self {
50 self.prefix_key = prefix_key;
51 self.prefix_modifiers = prefix_modifiers;
52 self
53 }
54
55 #[must_use]
57 pub fn with_pty(mut self, pty_handle: PtyHandle) -> Self {
58 self.pty_handle = Some(pty_handle);
59 self
60 }
61
62 #[must_use]
67 pub fn with_hat_map(self, hat_map: HashMap<String, (HatId, String)>) -> Self {
68 if let Ok(mut state) = self.state.lock() {
69 *state = TuiState::with_hat_map(hat_map);
70 }
71 self
72 }
73
74 pub fn observer(&self) -> impl Fn(&Event) + Send + 'static {
76 let state = Arc::clone(&self.state);
77 move |event: &Event| {
78 if let Ok(mut s) = state.lock() {
79 s.update(event);
80 }
81 }
82 }
83
84 pub async fn run(self) -> Result<()> {
95 let pty_handle = self
96 .pty_handle
97 .expect("PTY handle not set - call with_pty() first");
98 let app = App::with_prefix(
99 Arc::clone(&self.state),
100 pty_handle,
101 self.prefix_key,
102 self.prefix_modifiers,
103 );
104 app.run().await
105 }
106}
107
108impl Default for Tui {
109 fn default() -> Self {
110 Self::new()
111 }
112}