use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, widgets::{Block, Borders, List, ListItem, Paragraph, Clear}, Frame, }; use crate::app::{App, InputMode}; pub fn ui(f: &mut Frame, app: &mut App) { let size = f.size(); let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) .split(size); let items: Vec = app .servers .iter() .map(|s| { let content = format!("{} ({}) - {}:{}", s.name, s.user, s.host, s.port); ListItem::new(content).style(Style::default()) }) .collect(); let list = List::new(items) .block(Block::default().borders(Borders::ALL).title(" SSHX - Servers ")) .highlight_style(Style::default().add_modifier(Modifier::BOLD).fg(Color::Yellow)) .highlight_symbol("> "); f.render_stateful_widget(list, chunks[0], &mut app.state); // Help text let help_text = match app.input_mode { InputMode::Normal => "Enter: SSH Connect | m: Mosh Connect | n: New | Shift+n: Copy ID | i: Edit | d: Delete | q: Quit", InputMode::Adding(_) => "Enter: Save | Esc: Cancel | Tab: Next Field", InputMode::Editing(_) => "Enter: Save | Esc: Cancel | Tab: Next Field", }; let help = Paragraph::new(help_text) .style(Style::default().fg(Color::Gray)) .block(Block::default().borders(Borders::ALL).title(" Help ")); f.render_widget(help, chunks[1]); // Popup for Adding Server if let InputMode::Adding(state) = &app.input_mode { let block = Block::default().borders(Borders::ALL).title(" Add New Connection "); let area = centered_rect(60, 40, size); f.render_widget(Clear, area); // Clear the background f.render_widget(block, area); let input_layout = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints( [ Constraint::Length(3), // Name Constraint::Length(3), // User Constraint::Length(3), // Host Constraint::Length(3), // Port Constraint::Min(1), ] .as_ref(), ) .split(area); let fields = [ ("Name", &state.name), ("User (default: root)", &state.user), ("Host/IP", &state.host), ("Port (default: 22)", &state.port), ]; for (i, (label, value)) in fields.iter().enumerate() { let mut style = Style::default(); if state.field_idx == i { style = style.fg(Color::Yellow); } let input = Paragraph::new(value.as_str()) .style(style) .block(Block::default().borders(Borders::ALL).title(*label)); f.render_widget(input, input_layout[i]); // Show cursor in the active input field if state.field_idx == i { f.set_cursor( input_layout[i].x + value.len() as u16 + 1, // Position after the text input_layout[i].y + 1, // Middle of the input area ); } } } // Popup for Editing Server if let InputMode::Editing(state) = &app.input_mode { let block = Block::default().borders(Borders::ALL).title(" Edit Connection "); let area = centered_rect(60, 40, size); f.render_widget(Clear, area); // Clear the background f.render_widget(block, area); let input_layout = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints( [ Constraint::Length(3), // Name Constraint::Length(3), // User Constraint::Length(3), // Host Constraint::Length(3), // Port Constraint::Min(1), ] .as_ref(), ) .split(area); let fields = [ ("Name", &state.name), ("User (default: root)", &state.user), ("Host/IP", &state.host), ("Port (default: 22)", &state.port), ]; for (i, (label, value)) in fields.iter().enumerate() { let mut style = Style::default(); if state.field_idx == i { style = style.fg(Color::Yellow); } let input = Paragraph::new(value.as_str()) .style(style) .block(Block::default().borders(Borders::ALL).title(*label)); f.render_widget(input, input_layout[i]); // Show cursor in the active input field if state.field_idx == i { f.set_cursor( input_layout[i].x + value.len() as u16 + 1, // Position after the text input_layout[i].y + 1, // Middle of the input area ); } } } } pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { let popup_layout = Layout::default() .direction(Direction::Vertical) .constraints( [ Constraint::Percentage((100 - percent_y) / 2), Constraint::Percentage(percent_y), Constraint::Percentage((100 - percent_y) / 2), ] .as_ref(), ) .split(r); Layout::default() .direction(Direction::Horizontal) .constraints( [ Constraint::Percentage((100 - percent_x) / 2), Constraint::Percentage(percent_x), Constraint::Percentage((100 - percent_x) / 2), ] .as_ref(), ) .split(popup_layout[1])[1] }