use color_eyre::eyre::Result;
use ratatui::{prelude::*, widgets::*};
pub fn initialize_panic_handler() {
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
original_hook(panic_info);
fn startup() -> Result<()> {
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(std::io::stderr(), crossterm::terminal::EnterAlternateScreen)?;
fn shutdown() -> Result<()> {
crossterm::execute!(std::io::stderr(), crossterm::terminal::LeaveAlternateScreen)?;
crossterm::terminal::disable_raw_mode()?;
action_tx: mpsc::UnboundedSender<Action>,
fn ui(f: &mut Frame, app: &mut App) {
"Press j or k to increment or decrement.\n\nCounter: {}\n\nTicker: {}",
.title("ratatui async counter app")
.title_alignment(Alignment::Center)
.border_type(BorderType::Rounded),
.style(Style::default().fg(Color::Cyan))
.alignment(Alignment::Center),
fn update(app: &mut App, msg: Action) -> Action {
Action::ScheduleIncrement => {
let tx = app.action_tx.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
tx.send(Action::Increment).unwrap();
Action::ScheduleDecrement => {
let tx = app.action_tx.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
tx.send(Action::Decrement).unwrap();
Action::Quit => app.should_quit = true, // You can handle cleanup and exit here
fn handle_event(app: &App, tx: mpsc::UnboundedSender<Action>) -> tokio::task::JoinHandle<()> {
let tick_rate = std::time::Duration::from_millis(250);
tokio::spawn(async move {
let action = if crossterm::event::poll(tick_rate).unwrap() {
if let crossterm::event::Event::Key(key) = crossterm::event::read().unwrap() {
if key.kind == crossterm::event::KeyEventKind::Press {
crossterm::event::KeyCode::Char('j') => Action::ScheduleIncrement,
crossterm::event::KeyCode::Char('k') => Action::ScheduleDecrement,
crossterm::event::KeyCode::Char('q') => Action::Quit,
if let Err(_) = tx.send(action) {
async fn run() -> Result<()> {
let mut t = Terminal::new(CrosstermBackend::new(std::io::stderr()))?;
let (action_tx, mut action_rx) = mpsc::unbounded_channel();
let mut app = App { counter: 0, should_quit: false, action_tx, ticker: 0 };
let task = handle_event(&app, app.action_tx.clone());
if let Some(action) = action_rx.recv().await {
update(&mut app, action);
async fn main() -> Result<()> {
initialize_panic_handler();