rush_sync_server/ui/
screen.rs1use crate::commands::history::HistoryKeyboardHandler;
3use crate::commands::lang::LanguageManager;
4use crate::core::prelude::*;
5use crate::input::{
6 event::{AppEvent, EventHandler},
7 input::InputState,
8 keyboard::{KeyAction, KeyboardManager},
9};
10use crate::output::{logging::AppLogger, message::MessageManager, output::create_output_widget};
11use crate::ui::{color::AppColor, terminal::TerminalManager, widget::Widget};
12
13use crossterm::event::KeyEvent;
14use ratatui::{
15 backend::CrosstermBackend,
16 layout::{Constraint, Direction, Layout},
17 Terminal,
18};
19use std::io::{self, Stdout};
20
21pub type TerminalBackend = Terminal<CrosstermBackend<Stdout>>;
22
23pub struct ScreenManager<'a> {
24 terminal: TerminalBackend,
25 message_manager: MessageManager<'a>,
26 input_state: Box<dyn Widget + 'a>,
27 terminal_size: (u16, u16),
28 config: &'a Config,
29 terminal_mgr: TerminalManager,
30 events: EventHandler,
31 keyboard_manager: KeyboardManager,
32 waiting_for_restart_confirmation: bool,
33}
34
35impl<'a> ScreenManager<'a> {
36 pub async fn new(config: &'a Config) -> Result<Self> {
37 let mut terminal_mgr = TerminalManager::new().await?;
38 terminal_mgr.setup().await?;
39
40 let backend = CrosstermBackend::new(io::stdout());
41 let terminal = Terminal::new(backend)?;
42 let size = terminal.size()?;
43
44 let initial_height = size.height.saturating_sub(4) as usize;
45 let mut message_manager = MessageManager::new(config);
46 message_manager
47 .scroll_state
48 .update_dimensions(initial_height, 0);
49
50 Ok(Self {
51 terminal,
52 terminal_mgr,
53 message_manager,
54 input_state: Box::new(InputState::new(&config.prompt.text, config)),
55 terminal_size: (size.width, size.height),
56 config,
57 events: EventHandler::new(config.poll_rate),
58 keyboard_manager: KeyboardManager::new(),
59 waiting_for_restart_confirmation: false,
60 })
61 }
62
63 pub async fn run(&mut self) -> Result<()> {
65 let result = loop {
66 if let Some(event) = self.events.next().await {
67 match event {
68 AppEvent::Input(key) => {
69 if self.handle_input_event(key).await? {
70 self.events.shutdown().await;
71 break Ok(());
72 }
73 }
74 AppEvent::Resize(width, height) => {
75 self.handle_resize_event(width, height).await?;
76 }
77 AppEvent::Tick => {
78 self.handle_tick_event().await?;
79 }
80 }
81 }
82
83 self.process_pending_logs().await;
84 self.render().await?;
85 };
86
87 self.terminal_mgr.cleanup().await?;
88 result
89 }
90
91 async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
93 if HistoryKeyboardHandler::get_history_action(&key).is_some() {
95 if let Some(new_input) = self.input_state.handle_input(key) {
96 if let Some(processed) = LanguageManager::process_save_message(&new_input).await {
97 self.message_manager.add_message(processed);
98 return Ok(false);
99 }
100 self.message_manager.add_message(new_input.clone());
101
102 if new_input.starts_with("__CLEAR__") {
103 self.message_manager.clear_messages();
104 } else if new_input.starts_with("__EXIT__") {
105 return Ok(true);
106 }
107 }
108 return Ok(false);
109 }
110
111 match self.keyboard_manager.get_action(&key) {
113 KeyAction::ScrollUp
114 | KeyAction::ScrollDown
115 | KeyAction::PageUp
116 | KeyAction::PageDown => {
117 let window_height = self.get_content_height();
118 self.message_manager
119 .handle_scroll(self.keyboard_manager.get_action(&key), window_height);
120 }
121 KeyAction::Submit => {
122 if let Some(new_input) = self.input_state.handle_input(key) {
123 if let Some(processed) = LanguageManager::process_save_message(&new_input).await
124 {
125 self.message_manager.add_message(processed);
126 return Ok(false);
127 }
128
129 self.message_manager.add_message(new_input.clone());
130 if new_input.starts_with("__CLEAR__") {
131 self.message_manager.clear_messages();
132 } else if new_input.starts_with("__EXIT__") {
133 return Ok(true);
134 } else if new_input.starts_with("__RESTART_FORCE__")
135 || new_input == "__RESTART__"
136 {
137 if let Err(e) = self.perform_restart().await {
138 self.message_manager
139 .add_message(format!("Restart failed: {}", e));
140 }
141 }
142 }
143 }
144 KeyAction::Quit => return Ok(true),
145 _ => {
146 if let Some(new_input) = self.input_state.handle_input(key) {
147 if let Some(processed) = LanguageManager::process_save_message(&new_input).await
148 {
149 self.message_manager.add_message(processed);
150 return Ok(false);
151 }
152 self.message_manager.add_message(new_input);
153 }
154 }
155 }
156 Ok(false)
157 }
158
159 async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
161 self.terminal_size = (width, height);
162 let window_height = self.get_content_height();
163 self.message_manager
164 .scroll_state
165 .update_dimensions(window_height, self.message_manager.get_content_height());
166 Ok(())
167 }
168
169 async fn handle_tick_event(&mut self) -> Result<()> {
171 self.message_manager.update_typewriter();
172 if let Some(input_state) = self.input_state.as_input_state() {
173 input_state.update_cursor_blink();
174 }
175 Ok(())
176 }
177
178 fn get_content_height(&self) -> usize {
179 self.terminal_size.1.saturating_sub(4) as usize
180 }
181
182 async fn process_pending_logs(&mut self) {
183 match AppLogger::get_messages() {
184 Ok(messages) => {
185 for log_msg in messages {
186 self.message_manager.add_message(log_msg.formatted());
187 }
188 }
189 Err(e) => {
190 self.message_manager.add_message(
191 AppColor::from_any("error")
192 .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
193 );
194 }
195 }
196 }
197
198 async fn render(&mut self) -> Result<()> {
199 self.terminal.draw(|frame| {
200 let size = frame.size();
201 if size.width < 20 || size.height < 10 {
202 return;
203 }
204
205 let chunks = Layout::default()
206 .direction(Direction::Vertical)
207 .margin(1)
208 .constraints([Constraint::Min(3), Constraint::Length(3)])
209 .split(size);
210
211 let available_height = chunks[0].height as usize;
212 self.message_manager
213 .scroll_state
214 .update_dimensions(available_height, self.message_manager.get_content_height());
215
216 let messages = self.message_manager.get_messages();
217 let output_widget =
218 create_output_widget(&messages, available_height as u16, self.config);
219 frame.render_widget(output_widget, chunks[0]);
220
221 let input_widget = self.input_state.render();
222 frame.render_widget(input_widget, chunks[1]);
223 })?;
224 Ok(())
225 }
226
227 async fn perform_restart(&mut self) -> Result<()> {
228 self.terminal_mgr.cleanup().await?;
229 self.terminal_mgr = TerminalManager::new().await?;
230 self.terminal_mgr.setup().await?;
231
232 let backend = CrosstermBackend::new(io::stdout());
233 self.terminal = Terminal::new(backend)?;
234
235 self.message_manager.clear_messages();
236 self.input_state = Box::new(InputState::new(&self.config.prompt.text, self.config));
237 self.waiting_for_restart_confirmation = false;
238
239 self.message_manager
240 .add_message(crate::i18n::get_command_translation(
241 "system.commands.restart.success",
242 &[],
243 ));
244 log::info!("Internal restart completed successfully");
245 Ok(())
246 }
247}
248