1use ratatui::layout::Rect;
7use tuiserial_core::{AppState, FocusedField, Language, MenuState};
8
9use crate::areas::{get_clicked_field, get_clicked_menu, is_inside, is_shortcuts_hint_clicked};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum MouseAction {
14 None,
16 FocusField(FocusedField),
18 OpenMenu(usize),
20 SelectMenuItem(usize, usize), SwitchTab(usize),
24 ToggleConnection,
26 ClearLog,
28 RefreshPorts,
30 SendData,
32 ShowShortcutsHelp,
34 CloseShortcutsHelp,
36 CloseMenu,
38}
39
40pub fn handle_mouse_click(
42 app: &AppState,
43 x: u16,
44 y: u16,
45 menu_dropdown_area: Option<Rect>,
46) -> MouseAction {
47 if app.show_shortcuts_help {
49 if is_shortcuts_hint_clicked(x, y) {
51 return MouseAction::CloseShortcutsHelp;
52 }
53 return MouseAction::CloseShortcutsHelp;
55 }
56
57 if let MenuState::Dropdown(menu_idx, _) = app.menu_state {
59 if let Some(dropdown_area) = menu_dropdown_area {
60 if is_inside(dropdown_area, x, y) {
61 let item_idx = calculate_dropdown_item(dropdown_area, y);
63 return MouseAction::SelectMenuItem(menu_idx, item_idx);
64 } else {
65 return MouseAction::CloseMenu;
67 }
68 }
69 }
70
71 if let Some(menu_idx) = get_clicked_menu(x, y) {
73 return MouseAction::OpenMenu(menu_idx);
74 }
75
76 if is_shortcuts_hint_clicked(x, y) {
78 return MouseAction::ShowShortcutsHelp;
79 }
80
81 if let Some(field) = get_clicked_field(x, y) {
87 return MouseAction::FocusField(field);
88 }
89
90 MouseAction::None
95}
96
97pub fn handle_mouse_hover(_app: &AppState, x: u16, y: u16) -> Option<String> {
99 if let Some(menu_idx) = get_clicked_menu(x, y) {
103 let tooltip = match menu_idx {
104 0 => "File operations",
105 1 => "Session management",
106 2 => "View layouts",
107 3 => "Application settings",
108 4 => "Help and information",
109 _ => "",
110 };
111 return Some(tooltip.to_string());
112 }
113
114 if is_shortcuts_hint_clicked(x, y) {
116 return Some("Click to show keyboard shortcuts".to_string());
117 }
118
119 if let Some(field) = get_clicked_field(x, y) {
121 let tooltip = match field {
122 FocusedField::Port => "Select serial port",
123 FocusedField::BaudRate => "Select baud rate",
124 FocusedField::DataBits => "Select data bits",
125 FocusedField::Parity => "Select parity",
126 FocusedField::StopBits => "Select stop bits",
127 FocusedField::FlowControl => "Select flow control",
128 FocusedField::LogArea => "Serial communication log",
129 FocusedField::TxInput => "Enter data to send",
130 };
131 return Some(tooltip.to_string());
132 }
133
134 None
135}
136
137fn calculate_dropdown_item(dropdown_area: Rect, y: u16) -> usize {
139 let _ = dropdown_area;
140 if y < dropdown_area.y || y >= dropdown_area.y + dropdown_area.height {
141 return 0;
142 }
143
144 let relative_y = y.saturating_sub(dropdown_area.y);
145
146 if relative_y == 0 {
148 return 0;
149 }
150
151 relative_y as usize
153}
154
155pub fn calculate_dropdown_area(
157 menu_bar_area: Rect,
158 menu_idx: usize,
159 item_count: usize,
160 lang: Language,
161) -> Rect {
162 let x_offset = tuiserial_core::menu_def::calculate_menu_x_offset(menu_idx, lang);
164
165 let max_width = 25u16; let height = item_count as u16 + 2; Rect {
170 x: menu_bar_area.x + x_offset,
171 y: menu_bar_area.y + 1, width: max_width,
173 height,
174 }
175}
176
177#[allow(dead_code)]
179pub fn is_button_area(area: Rect, x: u16, y: u16, _button_text: &str) -> bool {
180 if !is_inside(area, x, y) {
181 return false;
182 }
183
184 true
187}
188
189pub fn handle_mouse_scroll(
191 _app: &AppState,
192 x: u16,
193 y: u16,
194 direction: ScrollDirection,
195) -> Option<ScrollAction> {
196 use crate::areas::get_ui_areas;
197
198 let areas = get_ui_areas();
199
200 if is_inside(areas.log_area, x, y) {
202 match direction {
203 ScrollDirection::Up => Some(ScrollAction::ScrollUp(3)),
204 ScrollDirection::Down => Some(ScrollAction::ScrollDown(3)),
205 }
206 } else {
207 None
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum ScrollDirection {
214 Up,
215 Down,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum ScrollAction {
221 ScrollUp(u16),
222 ScrollDown(u16),
223}
224
225pub fn get_hover_style(is_hovered: bool) -> ratatui::style::Style {
227 use ratatui::style::{Color, Modifier, Style};
228
229 if is_hovered {
230 Style::default()
231 .fg(Color::Yellow)
232 .add_modifier(Modifier::BOLD)
233 } else {
234 Style::default()
235 }
236}
237
238pub fn is_clickable_area(x: u16, y: u16) -> bool {
240 use crate::areas::get_ui_areas;
241
242 let areas = get_ui_areas();
243
244 is_inside(areas.menu_bar, x, y)
246 || is_inside(areas.port, x, y)
247 || is_inside(areas.baud_rate, x, y)
248 || is_inside(areas.data_bits, x, y)
249 || is_inside(areas.parity, x, y)
250 || is_inside(areas.stop_bits, x, y)
251 || is_inside(areas.flow_control, x, y)
252 || is_inside(areas.tx_area, x, y)
253 || is_inside(areas.shortcuts_hint, x, y)
254 || is_inside(areas.tab_bar, x, y)
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub enum CursorType {
260 Default,
261 Pointer, Text, Help, }
265
266pub fn get_cursor_type(_app: &AppState, x: u16, y: u16) -> CursorType {
268 use crate::areas::get_ui_areas;
269
270 let areas = get_ui_areas();
271
272 if is_inside(areas.tx_area, x, y) {
273 CursorType::Text
274 } else if is_shortcuts_hint_clicked(x, y) {
275 CursorType::Help
276 } else if is_clickable_area(x, y) {
277 CursorType::Pointer
278 } else {
279 CursorType::Default
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_calculate_dropdown_item() {
289 let area = Rect {
290 x: 0,
291 y: 1,
292 width: 20,
293 height: 5,
294 };
295
296 assert_eq!(calculate_dropdown_item(area, 0), 0); assert_eq!(calculate_dropdown_item(area, 1), 0); assert_eq!(calculate_dropdown_item(area, 2), 1); assert_eq!(calculate_dropdown_item(area, 3), 2); }
301
302 #[test]
303 fn test_calculate_dropdown_area() {
304 let menu_bar = Rect {
305 x: 0,
306 y: 0,
307 width: 80,
308 height: 1,
309 };
310
311 let area = calculate_dropdown_area(menu_bar, 0, 4, Language::English);
312 assert_eq!(area.y, 1); assert_eq!(area.height, 6); }
315
316 #[test]
317 fn test_is_clickable_area() {
318 let _result = is_clickable_area(0, 0);
321 }
322}