#![forbid(unsafe_code)]
#![allow(clippy::unused_self)]
#![allow(clippy::missing_const_for_fn)]
#![allow(clippy::needless_pass_by_value)]
#[cfg(not(feature = "async"))]
fn main() {
eprintln!("This example requires the 'async' feature.");
eprintln!("Run with: cargo run -p charmed-bubbletea --example async_fetch --features async");
}
#[cfg(feature = "async")]
mod async_app {
use bubbletea::{Cmd, KeyMsg, KeyType, Message, quit, tick};
use lipgloss::Style;
use std::time::{Duration, Instant};
struct TimerTick(#[allow(dead_code)] Instant);
struct FetchComplete(String);
#[derive(bubbletea::Model)]
pub struct App {
status: Status,
elapsed_secs: u32,
}
#[derive(Clone)]
enum Status {
Idle,
Fetching,
Done(String),
}
impl App {
pub const fn new() -> Self {
Self {
status: Status::Idle,
elapsed_secs: 0,
}
}
fn fetch_data() -> Cmd {
Cmd::new(|| {
std::thread::sleep(Duration::from_secs(2));
Message::new(FetchComplete("Data loaded successfully!".to_string()))
})
}
fn tick() -> Cmd {
tick(Duration::from_secs(1), |t| Message::new(TimerTick(t)))
}
fn init(&self) -> Option<Cmd> {
None
}
fn update(&mut self, msg: Message) -> Option<Cmd> {
if let Some(key) = msg.downcast_ref::<KeyMsg>() {
match key.key_type {
KeyType::Runes => {
if let Some(&ch) = key.runes.first() {
match ch {
'f' | 'F' => {
if matches!(self.status, Status::Idle) {
self.status = Status::Fetching;
self.elapsed_secs = 0;
return Some(
bubbletea::batch(vec![
Some(Self::fetch_data()),
Some(Self::tick()),
])
.unwrap(),
);
}
}
'r' | 'R' => {
self.status = Status::Idle;
self.elapsed_secs = 0;
}
'q' | 'Q' => return Some(quit()),
_ => {}
}
}
}
KeyType::CtrlC | KeyType::Esc => return Some(quit()),
_ => {}
}
}
if msg.is::<TimerTick>() && matches!(self.status, Status::Fetching) {
self.elapsed_secs += 1;
return Some(Self::tick());
}
if let Some(FetchComplete(data)) = msg.downcast::<FetchComplete>() {
self.status = Status::Done(data);
}
None
}
fn view(&self) -> String {
let title_style = Style::new().bold().foreground("#7D56F4");
let status_style = Style::new().foreground("#FF69B4");
let help_style = Style::new().faint();
let title = title_style.render("Async Runtime Example");
let status = match &self.status {
Status::Idle => status_style.render("Ready. Press 'f' to fetch data."),
Status::Fetching => {
let dots = ".".repeat((self.elapsed_secs as usize % 4) + 1);
status_style.render(&format!("Fetching{dots} ({}s elapsed)", self.elapsed_secs))
}
Status::Done(data) => status_style.render(&format!("✓ {data}")),
};
let help = help_style.render("f: fetch | r: reset | q: quit");
format!("{title}\n\n{status}\n\n{help}")
}
}
}
#[cfg(feature = "async")]
#[tokio::main]
async fn main() -> Result<(), bubbletea::Error> {
use async_app::App;
use bubbletea::Program;
let model = App::new();
Program::new(model).run_async().await?;
Ok(())
}