pixel-widgets 0.4.0

Elm inspired UI library styled using images
Documentation
use futures::{FutureExt, SinkExt};
use pixel_widgets::prelude::*;
use pixel_widgets::Command;
use winit::window::WindowBuilder;

struct Download {
    pub state: ManagedState<String>,
    pub url: String,
    pub progress: usize,
    pub size: usize,
}

#[derive(Clone)]
enum Message {
    UrlChanged(String),
    DownloadPressed,
    DownloadFinished,
    ProgressUpdated(usize, usize),
}

impl Model for Download {
    type Message = Message;

    fn update(&mut self, message: Self::Message) -> Vec<Command<Message>> {
        match message {
            Message::UrlChanged(url) => {
                self.url = url;
                Vec::new()
            }
            Message::DownloadPressed => {
                let (mut tx, rx) = futures::channel::mpsc::unbounded();
                let url = self.url.clone();
                vec![
                    Command::from_stream(rx),
                    Command::from_future_message(
                        tokio::spawn(async move {
                            tx.send(Message::ProgressUpdated(0, 1)).await.unwrap();

                            let mut response = reqwest::get(reqwest::Url::parse(url.as_str()).unwrap()).await.unwrap();
                            let mut progress = 0;
                            let length = response.content_length().unwrap_or(0) as usize;

                            tx.send(Message::ProgressUpdated(0, length)).await.unwrap();
                            while let Ok(Some(bytes)) = response.chunk().await {
                                progress += bytes.len();
                                tx.send(Message::ProgressUpdated(progress, length)).await.unwrap();
                            }

                            Message::DownloadFinished
                        })
                        .map(Result::unwrap),
                    ),
                ]
            }
            Message::DownloadFinished => Vec::new(),
            Message::ProgressUpdated(downloaded, size) => {
                self.progress = downloaded;
                self.size = size;
                Vec::new()
            }
        }
    }

    fn view(&mut self) -> Node<Message> {
        let mut state = self.state.tracker();
        let url = self.url.clone();
        Column::new()
            .push(Input::new(
                state.get_or_default_with("url", || {
                    let mut state = pixel_widgets::widget::input::State::default();
                    state.set_value(url);
                    state
                }),
                "download link",
                Message::UrlChanged,
            ))
            .push(Button::new(state.get("download"), Text::new("Download")).on_clicked(Message::DownloadPressed))
            .push(Text::new(format!(
                "Downloaded: {} / {} bytes",
                self.progress, self.size
            )))
            .push(Progress::new(self.progress as f32 / self.size as f32))
            .into_node()
    }
}

#[tokio::main]
async fn main() {
    let model = Download {
        state: Default::default(),
        progress: 0,
        size: 0,
        url: "http://speedtest.ftp.otenet.gr/files/test10Mb.db".into(),
    };

    let window = WindowBuilder::new()
        .with_title("Downloader")
        .with_inner_size(winit::dpi::LogicalSize::new(320, 240));

    let loader = pixel_widgets::loader::FsLoader::new("./examples".into()).unwrap();

    let mut sandbox = Sandbox::new(model, loader, window).await;
    sandbox.ui.set_stylesheet("download.pwss").await.unwrap();
    sandbox.run().await;
}