tuiserial_core/
state.rs

1//! Application state management
2//!
3//! This module defines the main application state structure that holds all
4//! runtime data, UI state, and configuration for the tuiserial application.
5
6use ratatui::widgets::ListState;
7use serde_json;
8use std::collections::VecDeque;
9
10use crate::config::SerialConfig;
11use crate::log::MessageLog;
12use crate::notification::Notification;
13use crate::types::{
14    AppendMode, DisplayMode, FlowControl, FocusedField, Language, MenuState, Parity, TxMode,
15};
16
17/// Main application state
18pub struct AppState {
19    // Serial configuration
20    pub config: SerialConfig,
21    pub message_log: MessageLog,
22    pub display_mode: DisplayMode,
23    pub is_connected: bool,
24    pub config_locked: bool,
25
26    // Available ports
27    pub ports: Vec<String>,
28
29    // Scroll state
30    pub scroll_offset: u16,
31    pub auto_scroll: bool,
32
33    // UI State for dropdowns
34    pub port_list_state: ListState,
35    pub baud_rate_options: Vec<u32>,
36    pub baud_rate_state: ListState,
37    pub parity_options: Vec<Parity>,
38    pub parity_state: ListState,
39    pub flow_control_options: Vec<FlowControl>,
40    pub flow_control_state: ListState,
41    pub data_bits_options: Vec<u8>,
42    pub data_bits_state: ListState,
43    pub stop_bits_options: Vec<u8>,
44    pub stop_bits_state: ListState,
45
46    // TX Input state
47    pub tx_input: String,
48    pub tx_mode: TxMode,
49    pub tx_append_mode: AppendMode,
50    pub tx_cursor: usize,
51    pub append_mode_options: Vec<AppendMode>,
52    pub append_mode_state: ListState,
53
54    // UI Focus
55    pub focused_field: FocusedField,
56
57    // Notification system
58    pub notifications: VecDeque<Notification>,
59
60    // Debug info
61    pub debug_mode: bool,
62    pub last_mouse_event: String,
63
64    // Menu and Language
65    pub menu_state: MenuState,
66    pub language: Language,
67
68    // Help overlay
69    pub show_shortcuts_help: bool,
70}
71
72impl Default for AppState {
73    fn default() -> Self {
74        let baud_rate_options = vec![
75            300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400,
76        ];
77        let parity_options = vec![Parity::None, Parity::Even, Parity::Odd];
78        let flow_control_options = vec![
79            FlowControl::None,
80            FlowControl::Hardware,
81            FlowControl::Software,
82        ];
83        let data_bits_options = vec![5, 6, 7, 8];
84        let stop_bits_options = vec![1, 2];
85        let append_mode_options = AppendMode::all();
86
87        Self {
88            config: SerialConfig::default(),
89            message_log: MessageLog::new(),
90            display_mode: DisplayMode::Hex,
91            is_connected: false,
92            config_locked: false,
93            ports: Vec::new(),
94            scroll_offset: 0,
95            auto_scroll: true,
96            port_list_state: ListState::default().with_selected(Some(0)),
97            baud_rate_state: ListState::default().with_selected(Some(4)), // 9600
98            parity_state: ListState::default().with_selected(Some(0)),    // None
99            flow_control_state: ListState::default().with_selected(Some(0)), // None
100            data_bits_state: ListState::default().with_selected(Some(3)), // 8
101            stop_bits_state: ListState::default().with_selected(Some(0)), // 1
102            baud_rate_options,
103            parity_options,
104            flow_control_options,
105            data_bits_options,
106            stop_bits_options,
107            tx_input: String::new(),
108            tx_mode: TxMode::Ascii,
109            tx_append_mode: AppendMode::None,
110            tx_cursor: 0,
111            append_mode_options,
112            append_mode_state: ListState::default().with_selected(Some(0)),
113            focused_field: FocusedField::Port,
114            notifications: VecDeque::new(),
115            debug_mode: false,
116            last_mouse_event: String::new(),
117            menu_state: MenuState::None,
118            language: Language::English,
119            show_shortcuts_help: false,
120        }
121    }
122}
123
124impl AppState {
125    /// Create a new application state with default values
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    // Configuration management
131
132    /// Lock configuration (called when connecting)
133    pub fn lock_config(&mut self) {
134        self.config_locked = true;
135    }
136
137    /// Unlock configuration (called when disconnecting)
138    pub fn unlock_config(&mut self) {
139        self.config_locked = false;
140    }
141
142    /// Check if configuration can be modified
143    pub fn can_modify_config(&self) -> bool {
144        !self.config_locked
145    }
146
147    // Notification management
148
149    /// Add a notification to the queue
150    pub fn add_notification(&mut self, notification: Notification) {
151        self.notifications.push_back(notification);
152    }
153
154    /// Add an info notification
155    pub fn add_info(&mut self, msg: impl Into<String>) {
156        self.add_notification(Notification::info(msg.into()));
157    }
158
159    /// Add a warning notification
160    pub fn add_warning(&mut self, msg: impl Into<String>) {
161        self.add_notification(Notification::warning(msg.into()));
162    }
163
164    /// Add an error notification
165    pub fn add_error(&mut self, msg: impl Into<String>) {
166        self.add_notification(Notification::error(msg.into()));
167    }
168
169    /// Add a success notification
170    pub fn add_success(&mut self, msg: impl Into<String>) {
171        self.add_notification(Notification::success(msg.into()));
172    }
173
174    /// Remove expired notifications
175    pub fn update_notifications(&mut self) {
176        while let Some(front) = self.notifications.front() {
177            if front.is_expired() {
178                self.notifications.pop_front();
179            } else {
180                break;
181            }
182        }
183    }
184
185    // Baud rate management
186
187    /// Select next baud rate
188    pub fn next_baud_rate(&mut self) -> bool {
189        if !self.can_modify_config() {
190            return false;
191        }
192        if let Some(selected) = self.baud_rate_state.selected() {
193            let next = (selected + 1) % self.baud_rate_options.len();
194            self.baud_rate_state.select(Some(next));
195            self.config.baud_rate = self.baud_rate_options[next];
196            true
197        } else {
198            false
199        }
200    }
201
202    /// Select previous baud rate
203    pub fn prev_baud_rate(&mut self) -> bool {
204        if !self.can_modify_config() {
205            return false;
206        }
207        if let Some(selected) = self.baud_rate_state.selected() {
208            let next = if selected == 0 {
209                self.baud_rate_options.len() - 1
210            } else {
211                selected - 1
212            };
213            self.baud_rate_state.select(Some(next));
214            self.config.baud_rate = self.baud_rate_options[next];
215            true
216        } else {
217            false
218        }
219    }
220
221    // Parity management
222
223    /// Toggle parity setting
224    pub fn toggle_parity(&mut self) -> bool {
225        if !self.can_modify_config() {
226            return false;
227        }
228        if let Some(selected) = self.parity_state.selected() {
229            let next = (selected + 1) % self.parity_options.len();
230            self.parity_state.select(Some(next));
231            self.config.parity = self.parity_options[next];
232            true
233        } else {
234            false
235        }
236    }
237
238    // Flow control management
239
240    /// Toggle flow control setting
241    pub fn toggle_flow_control(&mut self) -> bool {
242        if !self.can_modify_config() {
243            return false;
244        }
245        if let Some(selected) = self.flow_control_state.selected() {
246            let next = (selected + 1) % self.flow_control_options.len();
247            self.flow_control_state.select(Some(next));
248            self.config.flow_control = self.flow_control_options[next];
249            true
250        } else {
251            false
252        }
253    }
254
255    // Data bits management
256
257    /// Select next data bits setting
258    pub fn next_data_bits(&mut self) -> bool {
259        if !self.can_modify_config() {
260            return false;
261        }
262        if let Some(selected) = self.data_bits_state.selected() {
263            let next = (selected + 1) % self.data_bits_options.len();
264            self.data_bits_state.select(Some(next));
265            self.config.data_bits = self.data_bits_options[next];
266            true
267        } else {
268            false
269        }
270    }
271
272    // Stop bits management
273
274    /// Select next stop bits setting
275    pub fn next_stop_bits(&mut self) -> bool {
276        if !self.can_modify_config() {
277            return false;
278        }
279        if let Some(selected) = self.stop_bits_state.selected() {
280            let next = (selected + 1) % self.stop_bits_options.len();
281            self.stop_bits_state.select(Some(next));
282            self.config.stop_bits = self.stop_bits_options[next];
283            true
284        } else {
285            false
286        }
287    }
288
289    // Port management
290
291    /// Select port (with validation)
292    pub fn select_port(&mut self, index: usize) -> bool {
293        if !self.can_modify_config() {
294            return false;
295        }
296        if index < self.ports.len() {
297            self.port_list_state.select(Some(index));
298            self.config.port = self.ports[index].clone();
299            true
300        } else {
301            false
302        }
303    }
304
305    // TX mode management
306
307    /// Toggle transmission mode
308    pub fn toggle_tx_mode(&mut self) {
309        self.tx_mode = match self.tx_mode {
310            TxMode::Hex => TxMode::Ascii,
311            TxMode::Ascii => TxMode::Hex,
312        };
313    }
314
315    /// Cycle to next append mode
316    pub fn next_append_mode(&mut self) {
317        if let Some(selected) = self.append_mode_state.selected() {
318            let next = (selected + 1) % self.append_mode_options.len();
319            self.append_mode_state.select(Some(next));
320            self.tx_append_mode = self.append_mode_options[next];
321        }
322    }
323
324    /// Cycle to previous append mode
325    pub fn prev_append_mode(&mut self) {
326        if let Some(selected) = self.append_mode_state.selected() {
327            let next = if selected == 0 {
328                self.append_mode_options.len() - 1
329            } else {
330                selected - 1
331            };
332            self.append_mode_state.select(Some(next));
333            self.tx_append_mode = self.append_mode_options[next];
334        }
335    }
336
337    // Display mode management
338
339    /// Toggle display mode
340    pub fn toggle_display_mode(&mut self) {
341        self.display_mode = match self.display_mode {
342            DisplayMode::Hex => DisplayMode::Text,
343            DisplayMode::Text => DisplayMode::Hex,
344        };
345    }
346
347    // Focus management
348
349    /// Focus next field
350    pub fn focus_next_field(&mut self) {
351        self.focused_field = match self.focused_field {
352            FocusedField::Port => FocusedField::BaudRate,
353            FocusedField::BaudRate => FocusedField::DataBits,
354            FocusedField::DataBits => FocusedField::Parity,
355            FocusedField::Parity => FocusedField::StopBits,
356            FocusedField::StopBits => FocusedField::FlowControl,
357            FocusedField::FlowControl => FocusedField::LogArea,
358            FocusedField::LogArea => FocusedField::TxInput,
359            FocusedField::TxInput => FocusedField::Port,
360        };
361    }
362
363    /// Focus previous field
364    pub fn focus_prev_field(&mut self) {
365        self.focused_field = match self.focused_field {
366            FocusedField::Port => FocusedField::TxInput,
367            FocusedField::BaudRate => FocusedField::Port,
368            FocusedField::DataBits => FocusedField::BaudRate,
369            FocusedField::Parity => FocusedField::DataBits,
370            FocusedField::StopBits => FocusedField::Parity,
371            FocusedField::FlowControl => FocusedField::StopBits,
372            FocusedField::LogArea => FocusedField::FlowControl,
373            FocusedField::TxInput => FocusedField::LogArea,
374        };
375    }
376
377    // Configuration persistence
378
379    /// Save configuration to file
380    pub fn save_config(&self) -> Result<(), String> {
381        let config_dir =
382            dirs::config_dir().ok_or_else(|| "Could not determine config directory".to_string())?;
383        let app_config_dir = config_dir.join("tuiserial");
384        std::fs::create_dir_all(&app_config_dir)
385            .map_err(|e| format!("Failed to create config directory: {}", e))?;
386
387        let config_path = app_config_dir.join("config.json");
388        let json = serde_json::to_string_pretty(&self.config)
389            .map_err(|e| format!("Failed to serialize config: {}", e))?;
390
391        std::fs::write(&config_path, json)
392            .map_err(|e| format!("Failed to write config file: {}", e))?;
393
394        Ok(())
395    }
396
397    /// Load configuration from file, return default if not found or error
398    pub fn load_config(&mut self) {
399        if let Some(config_dir) = dirs::config_dir() {
400            let config_path = config_dir.join("tuiserial").join("config.json");
401            if let Ok(json) = std::fs::read_to_string(&config_path) {
402                if let Ok(config) = serde_json::from_str::<SerialConfig>(&json) {
403                    // Update UI states to match loaded config
404                    if let Some(idx) = self
405                        .baud_rate_options
406                        .iter()
407                        .position(|&b| b == config.baud_rate)
408                    {
409                        self.baud_rate_state.select(Some(idx));
410                    }
411                    if let Some(idx) = self.parity_options.iter().position(|&p| p == config.parity)
412                    {
413                        self.parity_state.select(Some(idx));
414                    }
415                    if let Some(idx) = self
416                        .flow_control_options
417                        .iter()
418                        .position(|&f| f == config.flow_control)
419                    {
420                        self.flow_control_state.select(Some(idx));
421                    }
422                    if let Some(idx) = self
423                        .data_bits_options
424                        .iter()
425                        .position(|&d| d == config.data_bits)
426                    {
427                        self.data_bits_state.select(Some(idx));
428                    }
429                    if let Some(idx) = self
430                        .stop_bits_options
431                        .iter()
432                        .position(|&s| s == config.stop_bits)
433                    {
434                        self.stop_bits_state.select(Some(idx));
435                    }
436                    // Move config assignment to end after all borrows
437                    self.config = config;
438                }
439            }
440        }
441    }
442
443    // Language management
444
445    /// Toggle language
446    pub fn toggle_language(&mut self) {
447        self.language = match self.language {
448            Language::English => Language::Chinese,
449            Language::Chinese => Language::English,
450        };
451    }
452
453    /// Toggle shortcuts help overlay
454    pub fn toggle_shortcuts_help(&mut self) {
455        self.show_shortcuts_help = !self.show_shortcuts_help;
456    }
457
458    /// Show shortcuts help
459    pub fn show_shortcuts_help(&mut self) {
460        self.show_shortcuts_help = true;
461    }
462
463    /// Hide shortcuts help
464    pub fn hide_shortcuts_help(&mut self) {
465        self.show_shortcuts_help = false;
466    }
467}