refactored
This commit is contained in:
135
src/main.rs
135
src/main.rs
@@ -1,72 +1,141 @@
|
|||||||
use std::{io, time::{Duration, Instant}};
|
use std::{
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
io,
|
||||||
use ratatui::{DefaultTerminal, Frame};
|
time::{Duration, Instant},
|
||||||
use ratatui::layout::{Constraint, Layout, Spacing};
|
};
|
||||||
use ratatui::symbols::merge::MergeStrategy;
|
|
||||||
use ratatui::widgets::Block;
|
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)]
|
#[derive(Default)]
|
||||||
struct App {
|
struct App {
|
||||||
should_exit: bool,
|
should_exit: bool,
|
||||||
// navigation state goes here (selected index, velocity, etc.)
|
status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
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<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
|
||||||
|
let tick_rate = Duration::from_millis(16); // ~60fps
|
||||||
let mut last_tick = Instant::now();
|
let mut last_tick = Instant::now();
|
||||||
|
|
||||||
ratatui::run(|terminal: &mut DefaultTerminal| {
|
let mut app = App {
|
||||||
let mut app = App::default();
|
status: "Press q to quit".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
while !app.should_exit {
|
while !app.should_exit {
|
||||||
terminal.draw(|f| render(f))?;
|
terminal.draw(|f| ui(f, &app))?;
|
||||||
|
|
||||||
// Non-blocking input with timeout until next tick
|
|
||||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||||
if event::poll(timeout)? {
|
if event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if key.kind == KeyEventKind::Press {
|
if key.kind == KeyEventKind::Press {
|
||||||
match key.code {
|
handle_key(&mut app, key.code);
|
||||||
KeyCode::Char('q') => app.should_exit = true,
|
|
||||||
|
|
||||||
// KeyCode::Up/Down etc => update nav intent/state
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick for time-based navigation / animations
|
|
||||||
if last_tick.elapsed() >= tick_rate {
|
if last_tick.elapsed() >= tick_rate {
|
||||||
// app.on_tick(last_tick.elapsed());
|
// tick-based updates go here later (smooth nav, download polling, etc.)
|
||||||
last_tick = Instant::now();
|
last_tick = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(frame: &mut Frame) {
|
fn handle_key(app: &mut App, code: KeyCode) {
|
||||||
use Constraint::{Fill};
|
match code {
|
||||||
|
KeyCode::Char('q') => app.should_exit = true,
|
||||||
|
KeyCode::Char('h') => app.status = "hi".to_string(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let [top_area, rest_area] = Layout::vertical([Fill(1), Fill(12)]).areas(frame.area());
|
fn ui(frame: &mut Frame, app: &App) {
|
||||||
|
let area = frame.area();
|
||||||
|
let panes = layout(area);
|
||||||
|
|
||||||
let [main_area, output_area] = Layout::vertical([Fill(9), Fill(3)])
|
// 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))
|
.spacing(Spacing::Overlap(1))
|
||||||
.areas(rest_area);
|
.areas(bottom);
|
||||||
|
|
||||||
let [left_main_area, right_main_area] = Layout::horizontal([Fill(2), Fill(5)])
|
let [left_main, right_main] =
|
||||||
|
Layout::horizontal([Fill(2), Fill(5)])
|
||||||
.spacing(Spacing::Overlap(1))
|
.spacing(Spacing::Overlap(1))
|
||||||
.areas(main_area);
|
.areas(main_area);
|
||||||
|
|
||||||
let [left_output_area, right_output_area] = Layout::horizontal([Fill(5), Fill(1)])
|
let [left_output, right_output] =
|
||||||
|
Layout::horizontal([Fill(5), Fill(1)])
|
||||||
.spacing(Spacing::Overlap(1))
|
.spacing(Spacing::Overlap(1))
|
||||||
.areas(output_area);
|
.areas(output_area);
|
||||||
|
|
||||||
frame.render_widget(Block::bordered(), top_area);
|
Panes {
|
||||||
frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), left_output_area);
|
top,
|
||||||
frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), right_output_area);
|
left_main,
|
||||||
frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), left_main_area);
|
right_main,
|
||||||
frame.render_widget(Block::bordered().merge_borders(MergeStrategy::Exact), right_main_area);
|
left_output,
|
||||||
|
right_output,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user