rpi_tui/
tui_main_screen.rs1use std::any::Any;
7use std::sync::{Arc, Mutex};
8
9use super::component::Component;
10use super::container::Container;
11use super::layout::composite_tui_line;
12use super::overlay::OverlayManager;
13use super::tui::{OverlayHandle, OverlayOptions, TuiMode, TuiStopOptions, TUI};
14use crate::terminal::Terminal;
15
16pub struct TuiMainScreen {
18 terminal: Box<dyn Terminal>,
19 container: Container,
20 running: Mutex<bool>,
21 show_hardware_cursor: Mutex<bool>,
22 clear_on_shrink: Mutex<bool>,
23 focused: Mutex<Option<Arc<dyn Component>>>,
24 full_redraw_count: Mutex<usize>,
25 overlays: Arc<OverlayManager>,
26}
27
28impl TuiMainScreen {
29 pub fn new(
31 terminal: Box<dyn Terminal>,
32 show_hardware_cursor: bool,
33 _log_directory: Option<&str>,
34 ) -> Self {
35 Self {
36 terminal,
37 container: Container::new(),
38 running: Mutex::new(false),
39 show_hardware_cursor: Mutex::new(show_hardware_cursor),
40 clear_on_shrink: Mutex::new(false),
41 focused: Mutex::new(None),
42 full_redraw_count: Mutex::new(0),
43 overlays: Arc::new(OverlayManager::new()),
44 }
45 }
46}
47
48impl Component for TuiMainScreen {
49 fn render(&self, width: usize) -> Vec<String> {
50 self.container.render(width)
51 }
52
53 fn invalidate(&self) {
54 self.container.invalidate();
55 }
56
57 fn as_any(&self) -> &dyn Any {
58 self
59 }
60}
61
62impl TUI for TuiMainScreen {
63 fn mode(&self) -> TuiMode {
64 TuiMode::Regular
65 }
66
67 fn terminal(&self) -> &dyn Terminal {
68 self.terminal.as_ref()
69 }
70
71 fn children(&self) -> Vec<Arc<dyn Component>> {
72 self.container.get_children()
73 }
74
75 fn add_child(&self, component: Arc<dyn Component>) {
76 self.container.add_child(component);
77 }
78
79 fn remove_child(&self, component: &Arc<dyn Component>) {
80 self.container.remove_child(component);
81 }
82
83 fn clear(&self) {
84 self.container.clear();
85 }
86
87 fn get_show_hardware_cursor(&self) -> bool {
88 self.show_hardware_cursor
89 .lock()
90 .map(|s| *s)
91 .unwrap_or(false)
92 }
93
94 fn set_show_hardware_cursor(&self, enabled: bool) {
95 if let Ok(mut show) = self.show_hardware_cursor.lock() {
96 *show = enabled;
97 }
98 }
99
100 fn get_clear_on_shrink(&self) -> bool {
101 self.clear_on_shrink.lock().map(|s| *s).unwrap_or(false)
102 }
103
104 fn set_clear_on_shrink(&self, enabled: bool) {
105 if let Ok(mut clear) = self.clear_on_shrink.lock() {
106 *clear = enabled;
107 }
108 }
109
110 fn set_focus(&self, component: Option<Arc<dyn Component>>) {
111 if let Ok(mut focused) = self.focused.lock() {
112 *focused = component;
113 }
114 }
115
116 fn get_focus(&self) -> Option<Arc<dyn Component>> {
117 self.focused.lock().ok()?.clone()
118 }
119
120 fn show_overlay(
121 &self,
122 component: Arc<dyn Component>,
123 options: Option<OverlayOptions>,
124 ) -> Arc<dyn OverlayHandle> {
125 let handle = self.overlays.add(component, options.unwrap_or_default());
126 self.request_render(false);
127 Arc::new(ManagedOverlayHandle {
128 handle,
129 hidden: Mutex::new(false),
130 focused: Mutex::new(true),
131 })
132 }
133
134 fn hide_overlay(&self) {
135 self.overlays.remove_topmost();
136 self.request_render(false);
137 }
138
139 fn has_overlay(&self) -> bool {
140 !self.overlays.get_visible().is_empty()
141 }
142
143 fn start(&self) {
144 if let Ok(mut running) = self.running.lock() {
145 *running = true;
146 }
147 self.terminal.show_cursor();
148 self.request_render(false);
149 }
150
151 fn stop(&self, _options: TuiStopOptions) {
152 if let Ok(mut running) = self.running.lock() {
153 *running = false;
154 }
155 self.terminal.show_cursor();
156 self.terminal.stop();
157 }
158
159 fn render_now(&self, force: bool) {
160 if force {
161 if let Ok(mut count) = self.full_redraw_count.lock() {
162 *count += 1;
163 }
164 }
165
166 let width = self.terminal.columns();
167 let lines = self.render(width);
168
169 for (i, line) in lines.iter().enumerate() {
173 if i > 0 {
174 self.terminal.write("\r\n");
175 }
176 self.terminal.write(line);
177 }
178
179 self.paint_overlays(&lines, width);
180
181 self.terminal.flush();
182 }
183
184 fn request_render(&self, _force: bool) {
185 self.render_now(false);
187 }
188
189 fn full_redraws(&self) -> usize {
190 self.full_redraw_count.lock().map(|c| *c).unwrap_or(0)
191 }
192}
193
194impl TuiMainScreen {
195 fn paint_overlays(&self, base_lines: &[String], width: usize) {
196 let height = self.terminal.rows().max(1);
197 let overlays = self.overlays.render_visible(width, height);
198 if overlays.is_empty() || base_lines.is_empty() {
199 return;
200 }
201
202 let base_height = base_lines.len().min(height);
207 self.terminal.write("\r\n");
208 self.terminal.write(&format!("\x1b[{}A\x1b[s", base_height));
209
210 for overlay in overlays {
211 let (x, y, overlay_width) = (overlay.x, overlay.y, overlay.width);
212 let overlay_lines = overlay.lines;
213 let overlay_height = overlay_lines.len();
214 self.terminal.write("\x1b[u");
217 self.terminal.write(&format!("\x1b[{}B", y));
218 for (index, line) in overlay_lines.iter().take(overlay_height).enumerate() {
219 if index > 0 {
220 self.terminal.write("\x1b[1B");
221 }
222 let row = y + index;
223 let base_line = base_lines.get(row).map(String::as_str).unwrap_or("");
224 let composite = composite_tui_line(base_line, line, x, overlay_width, width);
225 self.terminal.write("\r");
226 self.terminal.write(&composite);
227 }
228 }
229 self.terminal.write("\x1b[u");
231 self.terminal.write(&format!("\x1b[{}B", base_height));
232 }
233}
234
235struct ManagedOverlayHandle {
236 handle: super::overlay::OverlayHandle,
237 hidden: Mutex<bool>,
238 focused: Mutex<bool>,
239}
240
241impl OverlayHandle for ManagedOverlayHandle {
242 fn hide(&self) {
243 self.handle.hide();
244 if let Ok(mut hidden) = self.hidden.lock() {
245 *hidden = true;
246 }
247 }
248 fn set_hidden(&self, hidden: bool) {
249 self.handle.set_visible(!hidden);
250 if let Ok(mut current) = self.hidden.lock() {
251 *current = hidden;
252 }
253 }
254 fn is_hidden(&self) -> bool {
255 self.hidden.lock().map(|hidden| *hidden).unwrap_or(true)
256 }
257 fn focus(&self) {
258 if let Ok(mut focused) = self.focused.lock() {
259 *focused = true;
260 }
261 }
262 fn is_focused(&self) -> bool {
263 self.focused.lock().map(|focused| *focused).unwrap_or(false)
264 }
265}