use std::io;
use std::path::PathBuf;
use std::time::Duration;
use clap::Parser;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture, Event, poll, read};
use crossterm::execute;
use ratatui::DefaultTerminal;
mod app;
mod note;
mod persistence;
mod ui;
#[derive(Parser)]
#[command(
name = "stickynote",
version,
about = "A terminal-based sticky notes board"
)]
struct Cli {
#[arg(short, long)]
board: Option<PathBuf>,
#[arg(short, long, value_parser = ["dark", "light", "mono"])]
theme: Option<String>,
}
fn main() -> io::Result<()> {
let cli = Cli::parse();
let save_data = if let Some(ref path) = cli.board {
persistence::load_board_from(path)
} else {
persistence::load_board()
};
let mut app = app::App::new(save_data);
app.board_path = cli.board;
if let Some(ref theme_name) = cli.theme
&& let Some(idx) = note::THEMES
.iter()
.position(|t| t.name == theme_name.as_str())
{
app.theme_idx = idx;
}
let mut terminal = ratatui::init();
execute!(io::stdout(), EnableMouseCapture)?;
let result = run_app(&mut terminal, &mut app);
execute!(io::stdout(), DisableMouseCapture)?;
ratatui::restore();
app.flush_save();
result
}
fn run_app(terminal: &mut DefaultTerminal, app: &mut app::App) -> io::Result<()> {
let save_threshold = Duration::from_millis(200);
while !app.should_quit {
terminal.draw(|frame| ui::render(app, frame))?;
if poll(Duration::from_millis(50))? {
match read()? {
Event::Key(key) => app.handle_key(key),
Event::Mouse(mouse) => app.handle_mouse(mouse),
Event::Resize(w, h) => {
app.width = w;
app.height = h;
}
_ => {}
}
}
if app.dirty && app.last_save.elapsed() >= save_threshold {
let data = app.to_save_data();
match persistence::save_board(&data, app.board_path.as_ref()) {
Ok(()) => {
app.dirty = false;
app.last_save = std::time::Instant::now();
app.save_error.clear();
}
Err(e) => {
app.save_error = e;
}
}
}
}
Ok(())
}