use blaeck::prelude::*;
use std::time::Instant;
#[derive(Debug)]
pub enum BackgroundEvent {
DataChunk(String),
Progress(u32),
Done,
}
pub struct AppState {
pub text: String,
pub progress: u32,
pub is_loading: bool,
pub input: String,
pub status: String,
pub spinner_start: Instant,
#[allow(dead_code)]
pub should_exit: bool,
}
impl AppState {
pub fn new() -> Self {
Self {
text: String::new(),
progress: 0,
is_loading: false,
input: String::new(),
status: "Type something and press Enter".to_string(),
spinner_start: Instant::now(),
should_exit: false,
}
}
}
pub const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub fn get_spinner_frame(start: Instant) -> &'static str {
let elapsed_ms = start.elapsed().as_millis() as usize;
let frame_idx = (elapsed_ms / 80) % SPINNER_FRAMES.len();
SPINNER_FRAMES[frame_idx]
}
pub fn render(state: &AppState) -> Element {
let mut children: Vec<Element> = vec![
element! {
Text(content: "Async App Demo", bold: true, color: Color::Cyan)
},
element! {
Text(content: state.status.clone(), dim: true)
},
Element::text(""),
];
if !state.text.is_empty() {
children.push(element! {
Text(content: state.text.clone(), color: Color::Green)
});
children.push(Element::text(""));
}
if state.is_loading {
let spinner = get_spinner_frame(state.spinner_start);
let progress_bar = "█".repeat((state.progress / 5) as usize);
let empty_bar = "░".repeat((20 - state.progress / 5) as usize);
children.push(element! {
Box(flex_direction: FlexDirection::Row) {
Text(content: format!("{} ", spinner), color: Color::Yellow)
Text(content: format!("Working... {}% ", state.progress))
Text(content: format!("[{}{}]", progress_bar, empty_bar), dim: true)
}
});
} else {
children.push(element! {
Box(flex_direction: FlexDirection::Row) {
Text(content: "> ", color: Color::Blue)
Text(content: state.input.clone())
Text(content: "_", dim: true)
}
});
}
children.push(Element::text(""));
children.push(element! {
Text(content: "Press Ctrl+C to exit", dim: true)
});
Element::node::<Box>(
BoxProps {
flex_direction: FlexDirection::Column,
..Default::default()
},
children,
)
}
pub fn handle_background_event(state: &mut AppState, event: BackgroundEvent) {
match event {
BackgroundEvent::DataChunk(chunk) => {
state.text.push_str(&chunk);
}
BackgroundEvent::Progress(p) => {
state.progress = p;
}
BackgroundEvent::Done => {
state.is_loading = false;
state.status = "Done! Type something else".to_string();
}
}
}
pub fn build_ui() -> Element {
render(&AppState::new())
}