Iced crate - Progress Bar Help

My question feels simple, and maybe the solution is simple also but I am unsure. Basically I have a function that takes a long time to run, lets say around 10 seconds. For the example I will just do a long for loop to demonstrate this. I have a simple iced application window running with a button that when pressed, will run this long function. What I would like to have is also a progress_bar.

pub fn main() -> iced::Result {
    iced::run("Test", update, view)
}
fn update(progress: &mut f32, message: Message) {
    match message {
        Message::Run => {
              for i in 0..=1_000_000_000 {} 
// no state updates here, in 
// the real program this writes a file and then exits
        },
        Message::UpdateProgress(value) => progress = value,
    }
}
fn view(progress: &f32) -> Element<Message> {
    column![
    progress_bar(0.0..=100.0, progress)
    button("Run").on_press(Message::Run)
    ].into()
}

The setup is similar to above, the question is how can I run Message::UpdateProgress every 10% for example. I have tried

Task::Perform(std::future::ready(()), move || { Message::UpdateProgress(/*the value*/) })

And have really gotten nowhere. I am not sure if I need to be using futures, if I need to spawn another thread (which I have also tried) and sending messages. Really not sure and the documentation on iced isn't very descriptive for someone who is newer (not completely new) to rust.

Thanks in advance

1 Like

I don't know iced, but have you looked at the download_progress example?

3 Likes

Oh wow no I completely missed that one. Thanks I will be looking at that tomorrow. Seems like it is probably the right path to go down.

2 Likes

For anyone wondering, I came up with a basic solution.

We need a function with this signature fn() -> impl Stream<Item = Result<f32, ()>> that will stream us data (in this case 0 to 100) back and we can handle it with a closure.

fn run_loop() ->  impl Stream<Item = Result<f32, ()>>  {
// try_channel: Creates a new Stream that produces the items sent from a Future
// that can fail to the mpsc::Sender provided to the closure.
    try_channel(1, move |mut o| async move {

        let t: Vec<i32> = vec![0; 1000];
        let mut q: std::slice::Iter<'_, i32> = t.iter();
        let mut iter: i32 = 0;

        while let Some(_) = q.next() {
            //  were looping 1000 times waiting 100ms between each to simulate load
            iter += 1;
            let _ = o.send((iter as f32 / 1000.0 ) * 100.0 as f32).await;
            thread::sleep(Duration::from_millis(100));
        }

        let _ = o.send(100.0).await;

        Ok(())
    })
}

try_channel signature:

pub fn try_channel<T, E, F>(size: usize, f: impl FnOnce(mpsc::Sender<T>) -> F) -> impl Stream<Item = Result<T, E>>
where
    F: Future<Output = Result<(), E>>

Then in our update function

fn update(s: &mut State, message: Message) -> Task<Message> {
    match message {
      
        Message::TestThreadSpawn => {
            s.progress = 0.0;
            Task::run(run_loop(), |r: Result<f32, ()>| match r {
                Ok(v) => Message::SetWorking(v),
                Err(_) => Message::SetWorking(100.0) // we could handle errors here if we want
            })
        },

        Message::SetWorking(b) => { s.progress = b; Task::none() }, 
...
   }
}

then our view

fn main_screen(s: &State) -> Element<Message> {
    container(
        column![
            progress_bar(0.0..=100.0, s.progress),
            button("Test thread").on_press(Message::TestThreadSpawn)
        ]
     ).into()
}

In the real version obviously this will be adapted to a real function that takes around 10s to run but I think this can help anyone trying to wrap their head around iced futures / streams

Thanks this actually led me to solving the problem (included below).

1 Like

It is always jarring to see std::thread::sleep (assuming the reference is from std) used in an async block or fn. This will block the native async executor (tokio or whatever you're using). This should be replaced with the async equivalent, like:

tokio::time::sleep(Duration::from_millis(100)).await;

Blocking for 100ms may not be noticeable in this case, especially if the download/progress is modal, preventing other user interactions. But it will get much worse when more sleeps are stacked up from multiple downloads or other background tasks.

Other than that, it looks pretty solid!

2 Likes

Thanks for the answer, luckily the sleep is only there for examples sake. I will .await it from now on as this makes more sense when you are already inside a async block. Thanks again!