#![allow(non_snake_case)] mod centeredRect; mod entryTable; mod exitPrompt; mod textHints; use ratatui::{ Frame, layout::{Constraint, Direction, Layout}, style::{Color, Style}, text::{Line, Span, Text}, widgets::{Block, Borders, Clear, Padding, Paragraph}, }; use crate::app::status::{AppStatus, CurrentScreen, EntryValError}; use crate::app::{AppState, status::EditingField}; use crate::ui::centeredRect::centered_rect; use crate::ui::textHints::hints; pub fn ui(frame: &mut Frame, app: &mut AppState) { // Split entire body into left title+body and right screen+keybinds chunks let screenChunks = Layout::default() .direction(ratatui::layout::Direction::Horizontal) .constraints([Constraint::Fill(5), Constraint::Fill(2)]) .split(frame.area()); // Split left chunk into title and body let titleBodyChunks = Layout::default() .direction(ratatui::layout::Direction::Vertical) .constraints([Constraint::Length(3), Constraint::Min(3)]) .split(screenChunks[0]); // Split right chunk into header and keybind area let headerKeybindChunks = Layout::default() .direction(ratatui::layout::Direction::Vertical) .constraints([Constraint::Length(3), Constraint::Min(3)]) .split(screenChunks[1]); // ------------------------ // Create and add title block let titleBlock = Block::default() .borders(Borders::all()) .style(Style::default()); let title = Paragraph::new(Text::styled( "SteckBrett, a network ports plugboard", Style::default().fg(Color::Green), )) .block(titleBlock); frame.render_widget(title, titleBodyChunks[0]); // table drawing let table = entryTable::getTableElement( &app.entries, &app.screen, &app.appStatus, app.isHashDifferent(), ); frame.render_stateful_widget(table, titleBodyChunks[1], &mut app.tableState); // Renter exit prompt if let CurrentScreen::Exit = app.screen { let area = centered_rect(60, 25, titleBodyChunks[1]); frame.render_widget(Clear, area); frame.render_widget(exitPrompt::getExitPara(), area); } // Editing screen popup if let Some(edit) = &app.currentlyEditing { let popup = Block::default() .title("Add an entry") .borders(Borders::all()) .style(Style::default().bg(Color::Rgb(42, 61, 69)).fg(Color::Green)); let area = centered_rect(75, 30, titleBodyChunks[1]); frame.render_widget(Clear, area); frame.render_widget(popup, area); let fields = Layout::default() .direction(Direction::Horizontal) .margin(1) .constraints([ Constraint::Percentage(30), Constraint::Percentage(20), Constraint::Percentage(30), Constraint::Percentage(25), ]) .split(area); let mut fromIPBlock = Block::default() .title("From IP") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let mut toIPBlock = Block::default() .title("To IP") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let mut fromPortBlock = Block::default() .title("From Port") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let mut toPortBlock = Block::default() .title("To Port") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let activeStyle = Style::default().bg(Color::LightYellow).fg(Color::Black); match edit { EditingField::FromIP => fromIPBlock = fromIPBlock.style(activeStyle), EditingField::ToIP => toIPBlock = toIPBlock.style(activeStyle), EditingField::FromPort => fromPortBlock = fromPortBlock.style(activeStyle), EditingField::ToPort => toPortBlock = toPortBlock.style(activeStyle), } let fromIPPara = Paragraph::new(app.fromIP.clone()).block(fromIPBlock); let toIPPara = Paragraph::new(app.toIP.clone()).block(toIPBlock); let fromPortPara = Paragraph::new(app.fromPort.clone()).block(fromPortBlock); let toPortPara = Paragraph::new(app.toPort.clone()).block(toPortBlock); frame.render_widget(fromIPPara, fields[0]); frame.render_widget(fromPortPara, fields[1]); frame.render_widget(toIPPara, fields[2]); frame.render_widget(toPortPara, fields[3]); }; // -------------------------------- // Current page title and status let mut borderColor = Color::White; let _ = borderColor; // to suppress warning, remove later let screenHeader = vec![ match app.screen { CurrentScreen::Main => { borderColor = Color::LightBlue; Span::styled("Main Screen", Style::default().fg(Color::LightBlue)) } CurrentScreen::Add => { borderColor = Color::LightGreen; Span::styled("Add Window", Style::default().fg(Color::Green)) } CurrentScreen::Exit => { borderColor = Color::LightRed; Span::styled("Exit Screen", Style::default().fg(Color::Red)) } CurrentScreen::Settings => { borderColor = Color::LightBlue; Span::styled("Settings", Style::default().fg(Color::Blue)) } CurrentScreen::Delete => { borderColor = Color::Magenta; Span::styled("Delete", Style::default().fg(Color::Magenta)) } } .to_owned(), Span::styled(" | ", Style::default().fg(Color::White)), { match &app.appStatus { AppStatus::Welcome => Span::styled("Welcome", Style::default().fg(Color::White)), AppStatus::Added => Span::styled("Added", Style::default().fg(Color::Green)), AppStatus::Deleted => Span::styled("Deleted", Style::default().fg(Color::Magenta)), AppStatus::Editing => { if let Some(editing) = &app.currentlyEditing { let curEdit = match editing { EditingField::FromIP => "From IP", EditingField::ToIP => "To IP", EditingField::FromPort => "From Port", EditingField::ToPort => "To Port", }; Span::styled( format!("Editing: {curEdit}"), Style::default().fg(Color::Green), ) } else { Span::styled("Not Editing", Style::default().fg(Color::White)) } } AppStatus::Error(e) => { let errString = match e { EntryValError::ToPortValError => "To Port Invalid", EntryValError::FromPortValError => "From Port Invalid", EntryValError::ToIPValError => "To IP Invalid", EntryValError::FromIPValError => "From IP Invalid", EntryValError::None => "", }; Span::styled(errString, Style::default().fg(Color::Red)) } AppStatus::Saved => Span::styled("Saved", Style::default().fg(Color::Green)), } }, ]; let screenHeaderPara = Paragraph::new(Line::from(screenHeader)).block( Block::default() .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT) .style(Style::default().fg(borderColor)), ); // Keybinds Entry let keybinds = { match app.screen { CurrentScreen::Main => hints::mainHints(), CurrentScreen::Add => hints::addHints(), CurrentScreen::Settings => hints::settingsHints(), CurrentScreen::Delete => hints::delHints(), CurrentScreen::Exit => hints::exitHints(), } }; let keyBindFooter = Paragraph::new(keybinds).block( Block::default() .borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT) .style(Style::default().fg(borderColor)), ); frame.render_widget(screenHeaderPara, headerKeybindChunks[0]); frame.render_widget(keyBindFooter, headerKeybindChunks[1]); }