use revue::prelude::*;
use revue::worker::{WorkerChannel, WorkerCommand, WorkerMessage, WorkerSender};
use std::thread;
use std::time::Duration;
struct ProgressDemo {
receiver: Option<revue::worker::WorkerReceiver<String>>,
progress: f32,
status: String,
partial_results: Vec<String>,
final_result: Option<String>,
is_running: bool,
}
impl ProgressDemo {
fn new() -> Self {
Self {
receiver: None,
progress: 0.0,
status: "Ready".to_string(),
partial_results: Vec::new(),
final_result: None,
is_running: false,
}
}
fn start_processing(&mut self) {
if self.is_running {
self.status = "Already running!".to_string();
return;
}
let channel = WorkerChannel::new();
let (sender, receiver) = channel.split();
self.receiver = Some(receiver);
self.is_running = true;
self.progress = 0.0;
self.status = "Starting...".to_string();
self.partial_results.clear();
self.final_result = None;
thread::spawn(move || {
process_items(sender);
});
}
fn cancel_task(&mut self) {
if let Some(receiver) = &self.receiver {
receiver.cancel();
self.status = "Cancellation requested...".to_string();
}
}
fn pause_task(&mut self) {
if let Some(receiver) = &self.receiver {
receiver.pause();
self.status = "Pause requested...".to_string();
}
}
fn resume_task(&mut self) {
if let Some(receiver) = &self.receiver {
receiver.resume();
self.status = "Resume requested...".to_string();
}
}
fn handle_key(&mut self, key: &Key) -> bool {
match key {
Key::Char('s') => {
self.start_processing();
true
}
Key::Char('c') => {
self.cancel_task();
true
}
Key::Char('p') => {
self.pause_task();
true
}
Key::Char('r') => {
self.resume_task();
true
}
Key::Char('x') => {
self.receiver = None;
self.is_running = false;
self.progress = 0.0;
self.status = "Cleared".to_string();
self.partial_results.clear();
self.final_result = None;
true
}
_ => false,
}
}
fn tick(&mut self) -> bool {
let mut updated = false;
if let Some(receiver) = &self.receiver {
while let Some(msg) = receiver.recv() {
match msg {
WorkerMessage::Progress(p) => {
self.progress = p;
updated = true;
}
WorkerMessage::Status(s) => {
self.status = s;
updated = true;
}
WorkerMessage::Partial(result) => {
self.partial_results.push(result);
if self.partial_results.len() > 5 {
self.partial_results.remove(0);
}
updated = true;
}
WorkerMessage::Complete(result) => {
self.final_result = Some(result);
self.is_running = false;
self.status = "Completed!".to_string();
updated = true;
}
WorkerMessage::Error(e) => {
self.status = format!("Error: {}", e);
self.is_running = false;
updated = true;
}
WorkerMessage::Custom(msg) => {
self.status = format!("Custom: {}", msg);
updated = true;
}
}
}
}
updated
}
}
impl View for ProgressDemo {
fn render(&self, ctx: &mut RenderContext) {
let progress_percent = (self.progress * 100.0) as i32;
let progress_bar_width = 40;
let filled_width = ((progress_bar_width as f32) * self.progress) as usize;
let progress_bar = format!(
"[{}{}] {}%",
"=".repeat(filled_width),
" ".repeat(progress_bar_width - filled_width),
progress_percent
);
let view = vstack()
.gap(1)
.child(
Border::panel().title("📊 Worker Progress Demo").child(
vstack()
.gap(1)
.child(
hstack()
.gap(2)
.child(Text::new("Status:").bold())
.child(Text::new(&self.status).fg(Color::CYAN)),
)
.child(hstack().gap(2).child(Text::new("Running:")).child(
if self.is_running {
Text::new("Yes").fg(Color::YELLOW)
} else {
Text::new("No").fg(Color::rgb(100, 100, 100))
},
)),
),
)
.child(
Border::single().title("Progress").child(
vstack()
.child(Text::new(progress_bar).fg(Color::GREEN))
.child(Text::muted(format!(
"{:.1}% complete",
self.progress * 100.0
))),
),
)
.child(
Border::single()
.title("Partial Results (last 5)".to_string())
.child({
let mut stack = vstack();
if self.partial_results.is_empty() {
stack = stack.child(Text::muted("No partial results yet"));
} else {
for result in &self.partial_results {
stack = stack.child(Text::new(format!("• {}", result)));
}
}
stack
}),
)
.child(Border::single().title("Final Result").child(
if let Some(result) = &self.final_result {
vstack()
.child(Text::success("✓ Task completed"))
.child(Text::new(result).fg(Color::WHITE))
} else {
vstack().child(Text::muted("Not completed yet"))
},
))
.child(
Border::success_box()
.title("✨ Features Demonstrated")
.child(
vstack()
.child(Text::success(
"✓ WorkerChannel: Bidirectional communication",
))
.child(Text::success("✓ Progress updates (0.0 to 1.0)"))
.child(Text::success("✓ Status messages"))
.child(Text::success("✓ Partial results"))
.child(Text::success("✓ Commands: Cancel, Pause, Resume")),
),
)
.child(
Border::rounded().title("Controls").child(
vstack()
.child(
hstack()
.gap(2)
.child(Text::muted("[s]"))
.child(Text::new("Start processing")),
)
.child(
hstack()
.gap(2)
.child(Text::muted("[c]"))
.child(Text::new("Cancel task")),
)
.child(
hstack()
.gap(2)
.child(Text::muted("[p]"))
.child(Text::new("Pause task")),
)
.child(
hstack()
.gap(2)
.child(Text::muted("[r]"))
.child(Text::new("Resume task")),
)
.child(
hstack()
.gap(2)
.child(Text::muted("[x]"))
.child(Text::new("Clear all")),
)
.child(
hstack()
.gap(2)
.child(Text::muted("[q]"))
.child(Text::new("Quit")),
),
),
);
view.render(ctx);
}
fn meta(&self) -> WidgetMeta {
WidgetMeta::new("ProgressDemo")
}
}
fn process_items(sender: WorkerSender<String>) {
sender.status("Initializing...".to_string());
thread::sleep(Duration::from_millis(500));
let total_items = 20;
for i in 0..total_items {
if let Some(cmd) = sender.check_command() {
match cmd {
WorkerCommand::Cancel => {
sender.status("Cancelled by user".to_string());
sender.error("Task was cancelled");
return;
}
WorkerCommand::Pause => {
sender.status("Paused".to_string());
loop {
thread::sleep(Duration::from_millis(100));
if let Some(WorkerCommand::Resume) = sender.check_command() {
sender.status("Resumed".to_string());
break;
}
if sender.is_cancelled() {
sender.error("Cancelled while paused");
return;
}
}
}
WorkerCommand::Resume => {
sender.status("Already running".to_string());
}
WorkerCommand::Custom(msg) => {
sender.status(format!("Custom command: {}", msg));
}
}
}
let progress = (i + 1) as f32 / total_items as f32;
sender.progress(progress);
sender.status(format!("Processing item {}/{}", i + 1, total_items));
thread::sleep(Duration::from_millis(200));
if (i + 1) % 5 == 0 {
sender.partial(format!("Completed batch {}", (i + 1) / 5));
}
}
sender.complete(format!("Successfully processed {} items", total_items));
}
fn main() -> Result<()> {
println!("📊 Worker Progress Example");
println!("Demonstrates WorkerChannel with progress updates.\n");
let mut app = App::builder().build();
let demo = ProgressDemo::new();
app.run(demo, |event, demo, _app| match event {
Event::Key(key_event) => demo.handle_key(&key_event.key),
Event::Tick => demo.tick(),
_ => false,
})
}