From 13039965ba12e875239cf2e46ebc33d92ecef275 Mon Sep 17 00:00:00 2001 From: cwj3rcb Date: Fri, 13 Feb 2026 20:12:26 -0600 Subject: [PATCH] refactored --- src/main.rs | 177 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 123 insertions(+), 54 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1dd9b09..a85d447 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,72 +1,141 @@ -use std::{io, time::{Duration, Instant}}; -use crossterm::event::{self, Event, KeyCode, KeyEventKind}; -use ratatui::{DefaultTerminal, Frame}; -use ratatui::layout::{Constraint, Layout, Spacing}; -use ratatui::symbols::merge::MergeStrategy; -use ratatui::widgets::Block; +use std::{ + io, + time::{Duration, Instant}, +}; + +use crossterm::{ + event::{self, Event, KeyCode, KeyEventKind}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{ + layout::{Constraint, Layout, Rect, Spacing}, + symbols::merge::MergeStrategy, + widgets::{Block, Paragraph}, + Frame, Terminal, +}; +use ratatui::backend::CrosstermBackend; #[derive(Default)] struct App { should_exit: bool, - // navigation state goes here (selected index, velocity, etc.) + status: String, } fn main() -> io::Result<()> { - let tick_rate = Duration::from_millis(16); // ~60Hz + // Terminal setup + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + terminal.clear()?; + + let res = run_app(&mut terminal); + + // Terminal restore (always) + disable_raw_mode()?; + execute!(io::stdout(), LeaveAlternateScreen)?; + terminal.show_cursor()?; + + res +} + +fn run_app(terminal: &mut Terminal>) -> io::Result<()> { + let tick_rate = Duration::from_millis(16); // ~60fps let mut last_tick = Instant::now(); - ratatui::run(|terminal: &mut DefaultTerminal| { - let mut app = App::default(); + let mut app = App { + status: "Press q to quit".to_string(), + ..Default::default() + }; - while !app.should_exit { - terminal.draw(|f| render(f))?; + while !app.should_exit { + terminal.draw(|f| ui(f, &app))?; - // Non-blocking input with timeout until next tick - let timeout = tick_rate.saturating_sub(last_tick.elapsed()); - if event::poll(timeout)? { - if let Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press { - match key.code { - KeyCode::Char('q') => app.should_exit = true, - - // KeyCode::Up/Down etc => update nav intent/state - _ => {} - } - } + let timeout = tick_rate.saturating_sub(last_tick.elapsed()); + if event::poll(timeout)? { + if let Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press { + handle_key(&mut app, key.code); } } - - // Tick for time-based navigation / animations - if last_tick.elapsed() >= tick_rate { - // app.on_tick(last_tick.elapsed()); - last_tick = Instant::now(); - } } - Ok(()) - }) + if last_tick.elapsed() >= tick_rate { + // tick-based updates go here later (smooth nav, download polling, etc.) + last_tick = Instant::now(); + } + } + + Ok(()) } -fn render(frame: &mut Frame) { - use Constraint::{Fill}; - - let [top_area, rest_area] = Layout::vertical([Fill(1), Fill(12)]).areas(frame.area()); - - let [main_area, output_area] = Layout::vertical([Fill(9), Fill(3)]) - .spacing(Spacing::Overlap(1)) - .areas(rest_area); - - let [left_main_area, right_main_area] = Layout::horizontal([Fill(2), Fill(5)]) - .spacing(Spacing::Overlap(1)) - .areas(main_area); - - let [left_output_area, right_output_area] = Layout::horizontal([Fill(5), Fill(1)]) - .spacing(Spacing::Overlap(1)) - .areas(output_area); - - frame.render_widget(Block::bordered(), top_area); - frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), left_output_area); - frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), right_output_area); - frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), left_main_area); - frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), right_main_area); +fn handle_key(app: &mut App, code: KeyCode) { + match code { + KeyCode::Char('q') => app.should_exit = true, + KeyCode::Char('h') => app.status = "hi".to_string(), + _ => {} + } } + +fn ui(frame: &mut Frame, app: &App) { + let area = frame.area(); + let panes = layout(area); + + // Top + frame.render_widget(Block::bordered().title("cj-jftui"), panes.top); + + // Main + frame.render_widget(Block::bordered().title("List").merge_borders(MergeStrategy::Exact), panes.left_main); + frame.render_widget(Block::bordered().title("Details").merge_borders(MergeStrategy::Exact), panes.right_main); + + // Bottom + frame.render_widget( + Paragraph::new(app.status.as_str()) + .block(Block::bordered().title("Status").merge_borders(MergeStrategy::Exact)), + panes.left_output, + ); + frame.render_widget( + Block::bordered().title("Info").merge_borders(MergeStrategy::Exact), + panes.right_output, + ); +} + +struct Panes { + top: Rect, + left_main: Rect, + right_main: Rect, + left_output: Rect, + right_output: Rect, +} + +fn layout(area: Rect) -> Panes { + use Constraint::Fill; + + let [top, bottom] = + Layout::vertical([Fill(1), Fill(12)]).areas(area); + + let [main_area, output_area] = + Layout::vertical([Fill(9), Fill(3)]) + .spacing(Spacing::Overlap(1)) + .areas(bottom); + + let [left_main, right_main] = + Layout::horizontal([Fill(2), Fill(5)]) + .spacing(Spacing::Overlap(1)) + .areas(main_area); + + let [left_output, right_output] = + Layout::horizontal([Fill(5), Fill(1)]) + .spacing(Spacing::Overlap(1)) + .areas(output_area); + + Panes { + top, + left_main, + right_main, + left_output, + right_output, + } +} \ No newline at end of file