rush_sync_server/ui/
screen.rs

1// ## BEGIN ##
2use 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    /// ✅ Hauptloop: Nur Dispatcher, schlank & lesbar
64    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    /// ✅ Eingaben (History, Submit, Scroll, Restart, Quit)
92    async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
93        // History
94        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        // Normale Keys
112        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    /// ✅ Fenstergröße anpassen
160    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    /// ✅ Tick (Typewriter, Cursor-Blink)
170    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// ## END ##