Rust-Iced: iced — A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm.

Iced

Test Status Documentation Crates.io License project chat

A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm.

Features

Iced is currently experimental software. Take a look at the roadmap, check out the issues, and feel free to contribute!

Installation

Add iced as a dependency in your Cargo.toml:

iced = "0.1"

Iced moves fast and the master branch can contain breaking changes! If you want to learn about a specific release, check out the release list.

Overview

Inspired by The Elm Architecture, Iced expects you to split user interfaces into four different concepts:

  • State — the state of your application
  • Messages — user interactions or meaningful events that you care about
  • View logic — a way to display your state as widgets that may produce messages on user interaction
  • Update logic — a way to react to messages and update your state

We can build something to see how this works! Let's say we want a simple counter that can be incremented and decremented using two buttons.

We start by modelling the state of our application:

use iced::button;

struct Counter {
    // The counter value
    value: i32,

    // The local state of the two buttons
    increment_button: button::State,
    decrement_button: button::State,
}

Next, we need to define the possible user interactions of our counter: the button presses. These interactions are our messages:

#[derive(Debug, Clone, Copy)]
pub enum Message {
    IncrementPressed,
    DecrementPressed,
}

Now, let's show the actual counter by putting it all together in our view logic:

use iced::{Button, Column, Text};

impl Counter {
    pub fn view(&mut self) -> Column<Message> {
        // We use a column: a simple vertical layout
        Column::new()
            .push(
                // The increment button. We tell it to produce an
                // `IncrementPressed` message when pressed
                Button::new(&mut self.increment_button, Text::new("+"))
                    .on_press(Message::IncrementPressed),
            )
            .push(
                // We show the value of the counter here
                Text::new(&self.value.to_string()).size(50),
            )
            .push(
                // The decrement button. We tell it to produce a
                // `DecrementPressed` message when pressed
                Button::new(&mut self.decrement_button, Text::new("-"))
                    .on_press(Message::DecrementPressed),
            )
    }
}

Finally, we need to be able to react to any produced messages and change our state accordingly in our update logic:

impl Counter {
    // ...

    pub fn update(&mut self, message: Message) {
        match message {
            Message::IncrementPressed => {
                self.value += 1;
            }
            Message::DecrementPressed => {
                self.value -= 1;
            }
        }
    }
}

And that's everything! We just wrote a whole user interface. Iced is now able to:

  1. Take the result of our view logic and layout its widgets.
  2. Process events from our system and produce messages for our update logic.
  3. Draw the resulting user interface.

Browse the documentation and the examples to learn more!

Implementation details

Iced was originally born as an attempt at bringing the simplicity of Elm and The Elm Architecture into Coffee, a 2D game engine I am working on.

The core of the library was implemented during May in this pull request. The first alpha version was eventually released as a renderer-agnostic GUI library. The library did not provide a renderer and implemented the current tour example on top of ggez, a game library.

Since then, the focus has shifted towards providing a batteries-included, end-user-oriented GUI library, while keeping the ecosystem modular.

Currently, Iced is a cross-platform GUI library built on top of smaller crates:

Iced ecosystem

Contributing / Feedback

Contributions are greatly appreciated! If you want to contribute, please read our contributing guidelines for more details.

Feedback is also welcome! You can open an issue or, if you want to talk, come chat to our Zulip server. Moreover, you can find me (and a bunch of awesome folks) over the #games-and-graphics and #gui-and-ui channels in the Rust Community Discord. I go by lone_scientist#9554 there.

Sponsors

The development of Iced is sponsored by the Cryptowatch team at Kraken.com

Comments

  • Example of composition of UI elements
    Example of composition of UI elements

    Jun 10, 2020

    An example that demonstrates how to compose separate UI elements, especially ones that don't necessarily emit any messages, would be extremely useful.

    question 
    Reply
  • Image is not updating
    Image is not updating

    Jun 12, 2020

    I have a form with an image and a button. When the button is pressed, an image handle is reloaded from the same file (which was changed in the meantime). I expect the image to update it's contents, but, for some reason, it stays untouched. What am I doing wrong?

    struct Ui {
        next: button::State,
        image: Handle,
    }
    
    impl Sandbox for Ui {
        type Message = Message;
        
        fn new() -> Self {
            Ui {
                next: button::State::new(),
                image: Handle::from_path("test.png"),
            }
        }
        
        fn update(&mut self, message: Self::Message) {
            match message {
                Message::Next => {
                    // the image on disk was changed by a different program, so I need to re-load it
                    self.image = Handle::from_path("test.png");
                }
            }
        }
    
        // ...
    
        fn view(&mut self) -> iced::Element<'_, Self::Message> {
            Row::new()
                .push(
                    Button::new(&mut self.next, Text::new("Next"))
                        .on_press(Message::Next)
                )
                .push(Image::new(self.image.clone()))
                .into()
        }
    }
    
    improvement 
    Reply
  • How to inject custom events into application?
    How to inject custom events into application?

    Jun 13, 2020

    Hello,

    I have an app that needs to process MIDI events. They come from midir crate via a callback. The thing is, I don't quite understand, how to inject them into an application logic. Ideally I'd like them to be treated something like Message::MidiEvent(event) along with usual button presses and mouse events.

    What's the best way to achieve that? If I'm not mistaken, it should be done by creating custom Recipe and subscribing to it. But maybe there is an easier way? In contrast to the download progress example, in my case it's just a plain event with no associated state. I just set up an input port and then receive callbacks on every MIDI event that I'd like to wrap into an application specific message.

    Thank you.

    question 
    Reply
  • How to integrate input events with existing engine, and use iced as ui library?
    How to integrate input events with existing engine, and use iced as ui library?

    Jun 14, 2020

    I looked at the integration example, and from what i gather all winit events are simply queued to iced, and later processed in a batch. What i would like to do however, is handle event with iced if it can (mouse over ui, text input, etc), and if user input is not handled by iced ui elements, send it to controls of my engine (in the same frame). How can this be done?

    Nice work on the library, web/native combination is especially impressive.

    Reply
  • window is rotated
    window is rotated

    Jun 16, 2020

    nvidia graphics card run todos demo ,the window is rotate .

    my computer operating system is windows 10,

    graphics card is NVIDIA GeForce GTX 1050,driver version is 2020/05/27, game ready driver.

    image

    bug question 
    Reply
  • `iced_wgpu` version mismatch with crates.io
    `iced_wgpu` version mismatch with crates.io

    Jun 17, 2020

    The iced_wgpu crate on crates.io doesn't seem to be updating correctly, as it's still dependent on wgpu 0.4. Looking at iced/wgpu/Cargo.toml reveals that it still has its version set to 0.2.2, whereas crates.io has a 0.2.3 version.

    question 
    Reply
  • External Command or Message to update the UI?
    External Command or Message to update the UI?

    Mar 21, 2020

    I'm a newbie to Iced and don't familiar with The Elm Architecture neither. I found a scenario that is hard even infeasible for the current architecture to achieve. (Maybe I'm wrong or something I missed?)

    Here is the scenario:

    We have a Download button, the on-press event is to trigger an async download task. In the meanwhile, when the download task started, the Download button should display the percent progress ( 0% -> 1% ->... -> 100%).

    Below is the download function, which takes a progress_callback to notify the current progress.

    use bytes::BufMut;
    use futures_util::StreamExt;
    use reqwest;
    
    pub struct Downloader;
    
    impl Downloader {
        pub async fn download<F>(
            url: &str,
            progress_callback: F,
        ) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>>
        where
            F: Fn(usize),
        {
            let mut bytes: Vec<u8> = vec![];
            let response = reqwest::get(url).await?;
            let total_size = response.content_length().unwrap() as f64;
            let mut stream = response.bytes_stream();
            while let Some(ret) = stream.next().await {
                let b = ret?;
                bytes.put(b);
                progress_callback(((100 * bytes.len()) as f64 / total_size) as usize);
            }
            Ok(bytes)
        }
    }
    
    #[cfg(test)]
    mod test {
        use std::error::Error;
    
        use tokio;
    
        use super::*;
    
        #[tokio::test]
        async fn test_download() {
            match Downloader::download(
                "https://a-large-file-url",
                |progress| {
                    println!("{}", progress);
                },
            )
            .await
            {
                Ok(bytes) => assert!(bytes.len() > 0),
                Err(e) => panic!("Download failed... {}", e.to_string()),
            }
        }
    }
    

    Use Subscription or Custom Widget? Well, I really have no idea how to achieve this. Does it mean we need a kind of external Command or Message to support this?

    question 
    Reply
  • All Examples: thread 'main' panicked at 'assertion failed: `(left == right)`
    All Examples: thread 'main' panicked at 'assertion failed: `(left == right)`

    Mar 29, 2020

    Hi! Total Rust noob here. Using Arch and an AMD GPU.

    First, I could not start any example because of

    thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-native-0.4.3/src/instance.rs:474:5
    

    I could fix that by installing Vulkan.

    But now, I'm facing another Issue: Example apps start, but as soon as I move my mouse cursor over the example’s window, the app breaks with

    >>>cargo run --package styling
        Finished dev [unoptimized + debuginfo] target(s) in 0.08s
         Running `target/debug/styling`
    thread 'main' panicked at 'assertion failed: `(left == right)`
      left: `0`,
     right: `1`', /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-native-0.4.3/src/hub.rs:120:9
    stack backtrace:
       0: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
       1: core::fmt::write
       2: std::io::Write::write_fmt
       3: std::panicking::default_hook::{{closure}}
       4: std::panicking::default_hook
       5: std::panicking::rust_panic_with_hook
       6: rust_begin_unwind
       7: std::panicking::begin_panic_fmt
       8: <wgpu_native::hub::Storage<T,I> as core::ops::index::Index<I>>::index
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-native-0.4.3/src/hub.rs:120
       9: wgpu_native::command::command_encoder_begin_render_pass::{{closure}}
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-native-0.4.3/src/command/mod.rs:266
      10: core::option::Option<T>::map
                 at /build/rust/src/rustc-1.42.0-src/src/libcore/option.rs:450
      11: wgpu_native::command::command_encoder_begin_render_pass
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-native-0.4.3/src/command/mod.rs:264
      12: wgpu_command_encoder_begin_render_pass
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-native-0.4.3/src/command/mod.rs:739
      13: wgpu::CommandEncoder::begin_render_pass
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.4.0/src/lib.rs:1059
      14: <iced_wgpu::window::backend::Backend as iced_native::window::backend::Backend>::draw
                 at ./wgpu/src/window/backend.rs:81
      15: iced_winit::application::Application::run::{{closure}}
                 at ./winit/src/application.rs:317
      16: winit::platform_impl::platform::sticky_exit_callback
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.22.0/src/platform_impl/linux/mod.rs:698
      17: winit::platform_impl::platform::x11::EventLoop<T>::run_return
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.22.0/src/platform_impl/linux/x11/mod.rs:310
      18: winit::platform_impl::platform::x11::EventLoop<T>::run
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.22.0/src/platform_impl/linux/x11/mod.rs:406
      19: winit::platform_impl::platform::EventLoop<T>::run
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.22.0/src/platform_impl/linux/mod.rs:645
      20: winit::event_loop::EventLoop<T>::run
                 at /home/daniel/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.22.0/src/event_loop.rs:148
      21: iced_winit::application::Application::run
                 at ./winit/src/application.rs:200
      22: iced::application::Application::run
                 at ./src/application.rs:177
      23: iced::sandbox::Sandbox::run
                 at ./src/sandbox.rs:128
      24: styling::main
                 at examples/styling/src/main.rs:8
      25: std::rt::lang_start::{{closure}}
                 at /build/rust/src/rustc-1.42.0-src/src/libstd/rt.rs:67
      26: std::panicking::try::do_call
      27: __rust_maybe_catch_panic
      28: std::rt::lang_start_internal
      29: std::rt::lang_start
                 at /build/rust/src/rustc-1.42.0-src/src/libstd/rt.rs:67
      30: main
      31: __libc_start_main
      32: _start
    note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
    

    Any clue?

    help wanted question 
    Reply
  • Canvas examples on MacOS don't work
    Canvas examples on MacOS don't work

    May 12, 2020

    0.1.1 seems to have broken the Canvas widget on Mac OS. When trying to run the clock, solar_system, bezier_tool examples on MacOS 10.14.6 get a blank, white, screen where I believe the Canvas is supposed to be. Running game_of_life presents a gray screen, and all the surrounding menu components but does not show any of the white "cell" pieces.

    Manually switching to the 0.1 branch and testing the ones that exist there seems to work fine.

    bug 
    Reply
  • Panic when running any of the examples under Linux Intel GPU
    Panic when running any of the examples under Linux Intel GPU

    Nov 25, 2019

    Environment:

    openSUSE Tumbleweed, kernel 5.3.11. KDE Plasma 5.17.3 System font: Noto Sans Mesa DRI Intel(R) HD Graphics 620 (Kaby Lake GT2) Mesa 19.2.4 Vulkan Instance Version: 1.1.127

    Running cargo run --example tour causes panic with the following stack trace (snipped):

      15: core::result::Result<T,E>::unwrap
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/result.rs:933
      16: wgpu_glyph::builder::GlyphBrushBuilder<()>::using_fonts_bytes::{{closure}}
                 at /home/dmitry/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu_glyph-0.6.0/src/builder.rs:44
      17: core::iter::adapters::map_fold::{{closure}}
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/adapters/mod.rs:694
      18: core::iter::traits::iterator::Iterator::fold::ok::{{closure}}
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/traits/iterator.rs:1813
      19: core::iter::traits::iterator::Iterator::try_fold
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/traits/iterator.rs:1694
      20: core::iter::traits::iterator::Iterator::fold
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/traits/iterator.rs:1816
      21: <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/adapters/mod.rs:727
      22: core::iter::traits::iterator::Iterator::for_each
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/traits/iterator.rs:616
      23: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::spec_extend
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/liballoc/vec.rs:1966
      24: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::from_iter
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/liballoc/vec.rs:1949
      25: <alloc::vec::Vec<T> as core::iter::traits::collect::FromIterator<T>>::from_iter
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/liballoc/vec.rs:1836
      26: core::iter::traits::iterator::Iterator::collect
                 at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/iter/traits/iterator.rs:1478
      27: wgpu_glyph::builder::GlyphBrushBuilder<()>::using_fonts_bytes
                 at /home/dmitry/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu_glyph-0.6.0/src/builder.rs:41
      28: iced_wgpu::text::Pipeline::new
                 at wgpu/src/text.rs:36
      29: iced_wgpu::renderer::Renderer::new
                 at wgpu/src/renderer.rs:64
      30: <iced_wgpu::renderer::Renderer as iced_native::renderer::windowed::Windowed>::new
                 at wgpu/src/renderer.rs:417
      31: iced_winit::application::Application::run
                 at ./winit/src/application.rs:115
      32: iced::application::Application::run
                 at ./src/application.rs:140
      33: iced::sandbox::Sandbox::run
                 at ./src/sandbox.rs:128
      34: tour::main
                 at examples/tour.rs:10
    
    bug 
    Reply
  • Text Selection for text_input widget
    Text Selection for text_input widget

    Feb 22, 2020

    TL;DR Based on Finnerales fork, still WIP but looking for tips and/or wishes.

    First of all, this is based on work done by @Finnerale (#184). It's the biggest and imo most difficult part of what has been done in this pull request (as of now). My problem was, that I wasn't patient enough to wait any longer and i wanted this functionality for my side project. So although I'm an absolute beginner regarding Rust/GUI/Collab Work, I thought I'd give it a go.

    What's implemented:

    • Selection via Mouse dragging (by Finnerale)
    • Ctrl + A or 3 clicks to select everything
    • double click to select a word

    What's left afaik:

    • Shift + Left/Right = Expand selection by one character
    • Ctrl + Shift + Left/Right = Expand selection by one "word"
    • Shift + Home/End = Expand selection to Start/End (* I really need to work on Comments/Documention huh -> should be added tomorrow)

    So most importantly I'd like to know if i did something wrong or inappropriate (regarding the code(style)) and if there is something i forgot to add. Oh and also tell me if this is a waste of time for you (or me).

    I'm gonna post this as a draft pull request, because it's obviously still WIP :)

    P.S. Yea, i noticed the failing builds :P

    feature 
    Reply
  • Another run example error: `cargo run --example tour`
    Another run example error: `cargo run --example tour`

    Dec 1, 2019

    I'm trying to run the example on my Windows 10, but I come across this:

    iced> $Env:RUST_BACKTRACE="full"
    iced> cargo run --example tour  
        Finished dev [unoptimized + debuginfo] target(s) in 0.63s
         Running `target\debug\examples\tour.exe`
    [2019-12-01T05:21:46Z INFO  winit::platform_impl::platform::window] Guessed window DPI factor: 1
    error: process didn't exit successfully: `target\debug\examples\tour.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
    

    I tried to backtrace the log, But it seems to only print a line of log about DPI factor. How could I log more to locate this issue?

    bug 
    Reply