use std::time::Duration;
use envision::app::tick;
use envision::prelude::*;
use ratatui::layout::{Alignment, Constraint, Layout};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
struct AsyncCounterApp;
#[derive(Default, Clone)]
struct State {
count: i32,
auto_increment: bool,
ticks: u64,
async_result: Option<String>,
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
enum Msg {
Increment,
Decrement,
ToggleAuto,
Tick,
AsyncResult(String),
FetchData,
Quit,
}
impl App for AsyncCounterApp {
type State = State;
type Message = Msg;
fn init() -> (State, Command<Msg>) {
(State::default(), Command::none())
}
fn update(state: &mut State, msg: Msg) -> Command<Msg> {
match msg {
Msg::Increment => {
state.count += 1;
}
Msg::Decrement => {
state.count -= 1;
}
Msg::ToggleAuto => {
state.auto_increment = !state.auto_increment;
}
Msg::Tick => {
state.ticks += 1;
if state.auto_increment {
state.count += 1;
}
}
Msg::FetchData => {
state.async_result = Some("Loading...".to_string());
return Command::perform_async(async {
tokio::time::sleep(Duration::from_millis(500)).await;
Some(Msg::AsyncResult("Data loaded successfully!".to_string()))
});
}
Msg::AsyncResult(result) => {
state.async_result = Some(result);
}
Msg::Quit => {
return Command::quit();
}
}
Command::none()
}
fn view(state: &State, frame: &mut Frame) {
let area = frame.area();
let chunks = Layout::vertical([
Constraint::Length(3), Constraint::Length(5), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Min(0), ])
.split(area);
let title = Paragraph::new("Async Counter - Subscription Demo")
.style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL));
frame.render_widget(title, chunks[0]);
let counter_style = if state.count >= 0 {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::Red)
};
let counter = Paragraph::new(format!("Count: {}", state.count))
.style(counter_style.add_modifier(Modifier::BOLD))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).title("Counter"));
frame.render_widget(counter, chunks[1]);
let ticks = Paragraph::new(format!("Ticks: {}", state.ticks))
.style(Style::default().fg(Color::Blue))
.alignment(Alignment::Center)
.block(
Block::default()
.borders(Borders::ALL)
.title("Subscription Ticks"),
);
frame.render_widget(ticks, chunks[2]);
let auto_status = if state.auto_increment {
Span::styled("ON", Style::default().fg(Color::Green))
} else {
Span::styled("OFF", Style::default().fg(Color::Red))
};
let auto_para =
Paragraph::new(Line::from(vec![Span::raw("Auto-Increment: "), auto_status]))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL));
frame.render_widget(auto_para, chunks[3]);
let async_text = state
.async_result
.as_deref()
.unwrap_or("Press [F] to fetch data");
let async_para = Paragraph::new(async_text)
.style(Style::default().fg(Color::Yellow))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).title("Async Result"));
frame.render_widget(async_para, chunks[4]);
let controls = Paragraph::new(Line::from(vec![
Span::styled("[+/-] ", Style::default().fg(Color::Green)),
Span::raw("Count "),
Span::styled("[A] ", Style::default().fg(Color::Cyan)),
Span::raw("Toggle Auto "),
Span::styled("[F] ", Style::default().fg(Color::Yellow)),
Span::raw("Fetch "),
Span::styled("[Q] ", Style::default().fg(Color::Magenta)),
Span::raw("Quit"),
]))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).title("Controls"));
frame.render_widget(controls, chunks[5]);
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut runtime = Runtime::<AsyncCounterApp, _>::virtual_builder(60, 20).build()?;
runtime.subscribe(tick(Duration::from_millis(500)).with_message(|| Msg::Tick));
println!("=== Async Counter Demo ===\n");
runtime.render()?;
println!("Initial state:");
println!("{}\n", runtime.backend());
runtime.dispatch(Msg::Increment);
runtime.dispatch(Msg::Increment);
runtime.render()?;
println!("After 2 increments:");
println!("{}\n", runtime.backend());
runtime.dispatch(Msg::ToggleAuto);
runtime.render()?;
println!("Auto-increment enabled:");
println!("{}\n", runtime.backend());
println!("Running 3 ticks with subscriptions...\n");
for i in 0..3 {
runtime.run_ticks(1)?;
tokio::time::sleep(Duration::from_millis(600)).await;
runtime.process_pending();
runtime.render()?;
println!("After tick {}:", i + 1);
println!(
"Count: {}, Ticks: {}\n",
runtime.state().count,
runtime.state().ticks
);
}
println!("Triggering async fetch...");
runtime.dispatch(Msg::FetchData);
runtime.render()?;
println!("Before async completes:");
println!("{}\n", runtime.backend());
tokio::time::sleep(Duration::from_millis(600)).await;
runtime.process_pending();
runtime.render()?;
println!("After async completes:");
println!("{}\n", runtime.backend());
println!("Final state:");
println!(" Count: {}", runtime.state().count);
println!(" Ticks: {}", runtime.state().ticks);
println!(" Auto: {}", runtime.state().auto_increment);
println!(" Async Result: {:?}", runtime.state().async_result);
Ok(())
}