use ratatui::layout::Rect;
use tuiserial_core::{AppState, FocusedField, Language, MenuState};
use crate::areas::{get_clicked_field, get_clicked_menu, is_inside, is_shortcuts_hint_clicked};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MouseAction {
None,
FocusField(FocusedField),
OpenMenu(usize),
SelectMenuItem(usize, usize), SwitchTab(usize),
ToggleConnection,
ClearLog,
RefreshPorts,
SendData,
ShowShortcutsHelp,
CloseShortcutsHelp,
CloseMenu,
}
pub fn handle_mouse_click(
app: &AppState,
x: u16,
y: u16,
menu_dropdown_area: Option<Rect>,
) -> MouseAction {
if app.show_shortcuts_help {
if is_shortcuts_hint_clicked(x, y) {
return MouseAction::CloseShortcutsHelp;
}
return MouseAction::CloseShortcutsHelp;
}
if let MenuState::Dropdown(menu_idx, _) = app.menu_state {
if let Some(dropdown_area) = menu_dropdown_area {
if is_inside(dropdown_area, x, y) {
let item_idx = calculate_dropdown_item(dropdown_area, y);
return MouseAction::SelectMenuItem(menu_idx, item_idx);
} else {
return MouseAction::CloseMenu;
}
}
}
if let Some(menu_idx) = get_clicked_menu(x, y) {
return MouseAction::OpenMenu(menu_idx);
}
if is_shortcuts_hint_clicked(x, y) {
return MouseAction::ShowShortcutsHelp;
}
if let Some(field) = get_clicked_field(x, y) {
return MouseAction::FocusField(field);
}
MouseAction::None
}
pub fn handle_mouse_hover(_app: &AppState, x: u16, y: u16) -> Option<String> {
if let Some(menu_idx) = get_clicked_menu(x, y) {
let tooltip = match menu_idx {
0 => "File operations",
1 => "Session management",
2 => "View layouts",
3 => "Application settings",
4 => "Help and information",
_ => "",
};
return Some(tooltip.to_string());
}
if is_shortcuts_hint_clicked(x, y) {
return Some("Click to show keyboard shortcuts".to_string());
}
if let Some(field) = get_clicked_field(x, y) {
let tooltip = match field {
FocusedField::Port => "Select serial port",
FocusedField::BaudRate => "Select baud rate",
FocusedField::DataBits => "Select data bits",
FocusedField::Parity => "Select parity",
FocusedField::StopBits => "Select stop bits",
FocusedField::FlowControl => "Select flow control",
FocusedField::LogArea => "Serial communication log",
FocusedField::TxInput => "Enter data to send",
};
return Some(tooltip.to_string());
}
None
}
fn calculate_dropdown_item(dropdown_area: Rect, y: u16) -> usize {
let _ = dropdown_area;
if y < dropdown_area.y || y >= dropdown_area.y + dropdown_area.height {
return 0;
}
let relative_y = y.saturating_sub(dropdown_area.y);
if relative_y == 0 {
return 0;
}
relative_y as usize
}
pub fn calculate_dropdown_area(
menu_bar_area: Rect,
menu_idx: usize,
item_count: usize,
lang: Language,
) -> Rect {
let x_offset = tuiserial_core::menu_def::calculate_menu_x_offset(menu_idx, lang);
let max_width = 25u16; let height = item_count as u16 + 2;
Rect {
x: menu_bar_area.x + x_offset,
y: menu_bar_area.y + 1, width: max_width,
height,
}
}
#[allow(dead_code)]
pub fn is_button_area(area: Rect, x: u16, y: u16, _button_text: &str) -> bool {
if !is_inside(area, x, y) {
return false;
}
true
}
pub fn handle_mouse_scroll(
_app: &AppState,
x: u16,
y: u16,
direction: ScrollDirection,
) -> Option<ScrollAction> {
use crate::areas::get_ui_areas;
let areas = get_ui_areas();
if is_inside(areas.log_area, x, y) {
match direction {
ScrollDirection::Up => Some(ScrollAction::ScrollUp(3)),
ScrollDirection::Down => Some(ScrollAction::ScrollDown(3)),
}
} else {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollDirection {
Up,
Down,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollAction {
ScrollUp(u16),
ScrollDown(u16),
}
pub fn get_hover_style(is_hovered: bool) -> ratatui::style::Style {
use ratatui::style::{Color, Modifier, Style};
if is_hovered {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
}
}
pub fn is_clickable_area(x: u16, y: u16) -> bool {
use crate::areas::get_ui_areas;
let areas = get_ui_areas();
is_inside(areas.menu_bar, x, y)
|| is_inside(areas.port, x, y)
|| is_inside(areas.baud_rate, x, y)
|| is_inside(areas.data_bits, x, y)
|| is_inside(areas.parity, x, y)
|| is_inside(areas.stop_bits, x, y)
|| is_inside(areas.flow_control, x, y)
|| is_inside(areas.tx_area, x, y)
|| is_inside(areas.shortcuts_hint, x, y)
|| is_inside(areas.tab_bar, x, y)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorType {
Default,
Pointer, Text, Help, }
pub fn get_cursor_type(_app: &AppState, x: u16, y: u16) -> CursorType {
use crate::areas::get_ui_areas;
let areas = get_ui_areas();
if is_inside(areas.tx_area, x, y) {
CursorType::Text
} else if is_shortcuts_hint_clicked(x, y) {
CursorType::Help
} else if is_clickable_area(x, y) {
CursorType::Pointer
} else {
CursorType::Default
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_dropdown_item() {
let area = Rect {
x: 0,
y: 1,
width: 20,
height: 5,
};
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); }
#[test]
fn test_calculate_dropdown_area() {
let menu_bar = Rect {
x: 0,
y: 0,
width: 80,
height: 1,
};
let area = calculate_dropdown_area(menu_bar, 0, 4, Language::English);
assert_eq!(area.y, 1); assert_eq!(area.height, 6); }
#[test]
fn test_is_clickable_area() {
let _result = is_clickable_area(0, 0);
}
}