1use 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
17pub struct AppState {
19 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 pub ports: Vec<String>,
28
29 pub scroll_offset: u16,
31 pub auto_scroll: bool,
32
33 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 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 pub focused_field: FocusedField,
56
57 pub notifications: VecDeque<Notification>,
59
60 pub debug_mode: bool,
62 pub last_mouse_event: String,
63
64 pub menu_state: MenuState,
66 pub language: Language,
67
68 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)), parity_state: ListState::default().with_selected(Some(0)), flow_control_state: ListState::default().with_selected(Some(0)), data_bits_state: ListState::default().with_selected(Some(3)), stop_bits_state: ListState::default().with_selected(Some(0)), 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 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn lock_config(&mut self) {
134 self.config_locked = true;
135 }
136
137 pub fn unlock_config(&mut self) {
139 self.config_locked = false;
140 }
141
142 pub fn can_modify_config(&self) -> bool {
144 !self.config_locked
145 }
146
147 pub fn add_notification(&mut self, notification: Notification) {
151 self.notifications.push_back(notification);
152 }
153
154 pub fn add_info(&mut self, msg: impl Into<String>) {
156 self.add_notification(Notification::info(msg.into()));
157 }
158
159 pub fn add_warning(&mut self, msg: impl Into<String>) {
161 self.add_notification(Notification::warning(msg.into()));
162 }
163
164 pub fn add_error(&mut self, msg: impl Into<String>) {
166 self.add_notification(Notification::error(msg.into()));
167 }
168
169 pub fn add_success(&mut self, msg: impl Into<String>) {
171 self.add_notification(Notification::success(msg.into()));
172 }
173
174 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 self.config = config;
438 }
439 }
440 }
441 }
442
443 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 pub fn toggle_shortcuts_help(&mut self) {
455 self.show_shortcuts_help = !self.show_shortcuts_help;
456 }
457
458 pub fn show_shortcuts_help(&mut self) {
460 self.show_shortcuts_help = true;
461 }
462
463 pub fn hide_shortcuts_help(&mut self) {
465 self.show_shortcuts_help = false;
466 }
467}