diff options
author | 2020-03-23 21:36:26 +0100 | |
---|---|---|
committer | 2020-03-23 21:36:26 +0100 | |
commit | 784b8ee74c15f682b5bab74ac7d475fbed20e937 (patch) | |
tree | 3802215b31cdc71749ae7ae6eda948fffa1b2fba /examples | |
parent | 092e9fb4cc3edcf2083b4464d24d50c82a1163d2 (diff) | |
parent | 8e0dcd212d71ff334aa590ee3b565da7b8d24713 (diff) | |
download | iced-784b8ee74c15f682b5bab74ac7d475fbed20e937.tar.gz iced-784b8ee74c15f682b5bab74ac7d475fbed20e937.tar.bz2 iced-784b8ee74c15f682b5bab74ac7d475fbed20e937.zip |
Merge pull request #232 from Songtronix/songtronix/add-download-example
Add example for download with progress tracking
Diffstat (limited to 'examples')
-rw-r--r-- | examples/README.md | 1 | ||||
-rw-r--r-- | examples/download_progress/Cargo.toml | 12 | ||||
-rw-r--r-- | examples/download_progress/README.md | 17 | ||||
-rw-r--r-- | examples/download_progress/src/download.rs | 110 | ||||
-rw-r--r-- | examples/download_progress/src/main.rs | 143 |
5 files changed, 283 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md index a7673705..5aea51eb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -73,6 +73,7 @@ A bunch of simpler examples exist: - [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time. - [`counter`](counter), the classic counter example explained in the [`README`](../README.md). - [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. +- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml new file mode 100644 index 00000000..34e6a132 --- /dev/null +++ b/examples/download_progress/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "download_progress" +version = "0.1.0" +authors = ["Songtronix <contact@songtronix.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["tokio"] } +iced_native = { path = "../../native" } +iced_futures = { path = "../../futures" } +reqwest = "0.10" diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md new file mode 100644 index 00000000..c606c5f9 --- /dev/null +++ b/examples/download_progress/README.md @@ -0,0 +1,17 @@ +## Download progress + +A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. + +The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress. + +<div align="center"> + <a href="https://gfycat.com/wildearlyafricanwilddog"> + <img src="https://thumbs.gfycat.com/WildEarlyAfricanwilddog-small.gif"> + </a> +</div> + +You can run it with `cargo run`: + +``` +cargo run --package download_progress +``` diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs new file mode 100644 index 00000000..96e1dc28 --- /dev/null +++ b/examples/download_progress/src/download.rs @@ -0,0 +1,110 @@ +use iced_futures::futures; + +// Just a little utility function +pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> { + iced::Subscription::from_recipe(Download { + url: url.to_string(), + }) +} + +pub struct Download { + url: String, +} + +// Make sure iced can use our download stream +impl<H, I> iced_native::subscription::Recipe<H, I> for Download +where + H: std::hash::Hasher, +{ + type Output = Progress; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + std::any::TypeId::of::<Self>().hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + Box::pin(futures::stream::unfold( + State::Ready(self.url), + |state| async move { + match state { + State::Ready(url) => { + let response = reqwest::get(&url).await; + + match response { + Ok(response) => { + if let Some(total) = response.content_length() { + Some(( + Progress::Started, + State::Downloading { + response, + total, + downloaded: 0, + }, + )) + } else { + Some((Progress::Errored, State::Finished)) + } + } + Err(_) => { + Some((Progress::Errored, State::Finished)) + } + } + } + State::Downloading { + mut response, + total, + downloaded, + } => match response.chunk().await { + Ok(Some(chunk)) => { + let downloaded = downloaded + chunk.len() as u64; + + let percentage = + (downloaded as f32 / total as f32) * 100.0; + + Some(( + Progress::Advanced(percentage), + State::Downloading { + response, + total, + downloaded, + }, + )) + } + Ok(None) => Some((Progress::Finished, State::Finished)), + Err(_) => Some((Progress::Errored, State::Finished)), + }, + State::Finished => { + // We do not let the stream die, as it would start a + // new download repeatedly if the user is not careful + // in case of errors. + let _: () = iced::futures::future::pending().await; + + None + } + } + }, + )) + } +} + +#[derive(Debug, Clone)] +pub enum Progress { + Started, + Advanced(f32), + Finished, + Errored, +} + +pub enum State { + Ready(String), + Downloading { + response: reqwest::Response, + total: u64, + downloaded: u64, + }, + Finished, +} diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs new file mode 100644 index 00000000..6c3094f7 --- /dev/null +++ b/examples/download_progress/src/main.rs @@ -0,0 +1,143 @@ +use iced::{ + button, executor, Align, Application, Button, Column, Command, Container, + Element, Length, ProgressBar, Settings, Subscription, Text, +}; + +mod download; + +pub fn main() { + Example::run(Settings::default()) +} + +#[derive(Debug)] +enum Example { + Idle { button: button::State }, + Downloading { progress: f32 }, + Finished { button: button::State }, + Errored { button: button::State }, +} + +#[derive(Debug, Clone)] +pub enum Message { + Download, + DownloadProgressed(download::Progress), +} + +impl Application for Example { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Example, Command<Message>) { + ( + Example::Idle { + button: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Download progress - Iced") + } + + fn update(&mut self, message: Message) -> Command<Message> { + match message { + Message::Download => match self { + Example::Idle { .. } + | Example::Finished { .. } + | Example::Errored { .. } => { + *self = Example::Downloading { progress: 0.0 }; + } + _ => {} + }, + Message::DownloadProgressed(message) => match self { + Example::Downloading { progress } => match message { + download::Progress::Started => { + *progress = 0.0; + } + download::Progress::Advanced(percentage) => { + *progress = percentage; + } + download::Progress::Finished => { + *self = Example::Finished { + button: button::State::new(), + } + } + download::Progress::Errored => { + *self = Example::Errored { + button: button::State::new(), + }; + } + }, + _ => {} + }, + }; + + Command::none() + } + + fn subscription(&self) -> Subscription<Message> { + match self { + Example::Downloading { .. } => { + download::file("https://speed.hetzner.de/100MB.bin") + .map(Message::DownloadProgressed) + } + _ => Subscription::none(), + } + } + + fn view(&mut self) -> Element<Message> { + let current_progress = match self { + Example::Idle { .. } => 0.0, + Example::Downloading { progress } => *progress, + Example::Finished { .. } => 100.0, + Example::Errored { .. } => 0.0, + }; + + let progress_bar = ProgressBar::new(0.0..=100.0, current_progress); + + let control: Element<_> = match self { + Example::Idle { button } => { + Button::new(button, Text::new("Start the download!")) + .on_press(Message::Download) + .into() + } + Example::Finished { button } => Column::new() + .spacing(10) + .align_items(Align::Center) + .push(Text::new("Download finished!")) + .push( + Button::new(button, Text::new("Start again")) + .on_press(Message::Download), + ) + .into(), + Example::Downloading { .. } => { + Text::new(format!("Downloading... {:.2}%", current_progress)) + .into() + } + Example::Errored { button } => Column::new() + .spacing(10) + .align_items(Align::Center) + .push(Text::new("Something went wrong :(")) + .push( + Button::new(button, Text::new("Try again")) + .on_press(Message::Download), + ) + .into(), + }; + + let content = Column::new() + .spacing(10) + .padding(10) + .align_items(Align::Center) + .push(progress_bar) + .push(control); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} |