1pub mod app;
4pub mod banner;
5pub mod body;
6pub mod cli;
7pub mod due;
8pub mod duepicker;
9pub mod form;
10pub mod fuzzy;
11pub mod image;
12pub mod input;
13pub mod model;
14pub mod open;
15pub mod settings;
16pub mod slash;
17pub mod store;
18pub mod text_input;
19pub mod theme;
20pub mod ui;
21pub mod undo;
22pub mod update;
23
24use std::io;
25use std::time::Duration;
26
27use ratatui::DefaultTerminal;
28use ratatui::crossterm::event::{
29 self, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
30 KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
31};
32use ratatui::crossterm::execute;
33
34use crate::app::App;
35
36pub const VERSION: &str = env!("CARGO_PKG_VERSION");
37
38pub fn run() {
40 cli::run();
41}
42
43pub fn run_tui() -> io::Result<()> {
44 let images = image::ImageStore::detect();
47
48 let mut terminal = ratatui::init();
49 let mut out = io::stdout();
50 let _ = execute!(out, EnableMouseCapture, EnableBracketedPaste);
51 let enhanced = execute!(
54 out,
55 PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)
56 )
57 .is_ok();
58
59 let mut app = App::new(VERSION);
60 app.images = images;
61 let result = event_loop(&mut terminal, &mut app);
62
63 if enhanced {
64 let _ = execute!(out, PopKeyboardEnhancementFlags);
65 }
66 let _ = execute!(out, DisableBracketedPaste, DisableMouseCapture);
67 ratatui::restore();
68 result
69}
70
71fn event_loop(terminal: &mut DefaultTerminal, app: &mut App) -> io::Result<()> {
72 let mut last_clock = String::new();
73 loop {
74 while event::poll(Duration::ZERO)? {
76 input::handle_event(app, event::read()?);
77 app.mark_dirty();
78 if app.should_quit {
79 app.flush_saves_now();
80 return Ok(());
81 }
82 }
83 let _ = app.expire_message();
84 if app.poll_update_check() {
85 app.mark_dirty();
86 }
87 if app.flush_saves() {
88 app.mark_dirty();
89 }
90 if app.images.recheck_cell_size() {
92 app.mark_dirty();
93 }
94 if app.images.poll_pending() {
95 app.mark_dirty();
96 }
97
98 let gif_advanced = app.form.as_mut().is_some_and(|f| f.tick_gif());
99 if gif_advanced {
100 app.mark_dirty();
101 }
102 let need_fast = app.form.as_ref().is_some_and(|f| f.gif_playing());
103
104 let clock = crate::due::now_string(&app.settings.date_format);
105 if clock != last_clock {
106 last_clock = clock;
107 app.mark_dirty();
108 }
109
110 if app.dirty {
111 terminal.draw(|frame| ui::draw(frame, app))?;
112 app.dirty = false;
113 }
114
115 let wait = if need_fast {
116 Duration::from_millis(30)
117 } else if app.images.has_pending() {
118 Duration::from_millis(16)
119 } else if app.tasks_dirty_pending() {
120 Duration::from_millis(50)
121 } else {
122 Duration::from_millis(500)
123 };
124 let _ = event::poll(wait)?;
125 }
126}