Crate iced

source ·
Expand description

iced is a cross-platform GUI library focused on simplicity and type-safety. Inspired by Elm.

§Disclaimer

iced is experimental software. If you expect the documentation to hold your hand as you learn the ropes, you are in for a frustrating experience.

The library leverages Rust to its full extent: ownership, borrowing, lifetimes, futures, streams, first-class functions, trait bounds, closures, and more. This documentation is not meant to teach you any of these. Far from it, it will assume you have mastered all of them.

Furthermore—just like Rust—iced is very unforgiving. It will not let you easily cut corners. The type signatures alone can be used to learn how to use most of the library. Everything is connected.

Therefore, iced is easy to learn for advanced Rust programmers; but plenty of patient beginners have learned it and had a good time with it. Since it leverages a lot of what Rust has to offer in a type-safe way, it can be a great way to discover Rust itself.

If you don’t like the sound of that, you expect to be spoonfed, or you feel frustrated and struggle to use the library; then I recommend you to wait patiently until the book is finished.

§The Pocket Guide

Start by calling run:

pub fn main() -> iced::Result {
    iced::run("A cool counter", update, view)
}

Define an update function to change your state:

fn update(counter: &mut u64, message: Message) {
    match message {
        Message::Increment => *counter += 1,
    }
}

Define a view function to display your state:

use iced::widget::{button, text};
use iced::Element;

fn view(counter: &u64) -> Element<Message> {
    button(text(counter)).on_press(Message::Increment).into()
}

And create a Message enum to connect view and update together:

#[derive(Debug, Clone)]
enum Message {
    Increment,
}

§Custom State

You can define your own struct for your state:

#[derive(Default)]
struct Counter {
    value: u64,
}

But you have to change update and view accordingly:

fn update(counter: &mut Counter, message: Message) {
    match message {
        Message::Increment => counter.value += 1,
    }
}

fn view(counter: &Counter) -> Element<Message> {
    button(text(counter.value)).on_press(Message::Increment).into()
}

§Widgets and Elements

The view function must return an Element. An Element is just a generic widget.

The widget module contains a bunch of functions to help you build and use widgets.

Widgets are configured using the builder pattern:

use iced::widget::{button, column, text};
use iced::Element;

fn view(counter: &Counter) -> Element<Message> {
    column![
        text(counter.value).size(20),
        button("Increment").on_press(Message::Increment),
    ]
    .spacing(10)
    .into()
}

A widget can be turned into an Element by calling into.

Widgets and elements are generic over the message type they produce. The Element returned by view must have the same Message type as your update.

§Layout

There is no unified layout system in iced. Instead, each widget implements its own layout strategy.

Building your layout will often consist in using a combination of rows, columns, and containers:

use iced::widget::{column, container, row};
use iced::{Fill, Element};

fn view(state: &State) -> Element<Message> {
    container(
        column![
            "Top",
            row!["Left", "Right"].spacing(10),
            "Bottom"
        ]
        .spacing(10)
    )
    .padding(10)
    .center_x(Fill)
    .center_y(Fill)
    .into()
}

Rows and columns lay out their children horizontally and vertically, respectively. Spacing can be easily added between elements.

Containers position or align a single widget inside their bounds.

§Sizing

The width and height of widgets can generally be defined using a Length.

  • Fill will make the widget take all the available space in a given axis.
  • Shrink will make the widget use its intrinsic size.

Most widgets use a Shrink sizing strategy by default, but will inherit a Fill strategy from their children.

A fixed numeric Length in Pixels can also be used:

use iced::widget::container;
use iced::Element;

fn view(state: &State) -> Element<Message> {
    container("I am 300px tall!").height(300).into()
}

§Theming

The default Theme of an application can be changed by defining a theme function and leveraging the Application builder, instead of directly calling run:

use iced::Theme;

pub fn main() -> iced::Result {
    iced::application("A cool application", update, view)
        .theme(theme)
        .run()
}

fn theme(state: &State) -> Theme {
    Theme::TokyoNight
}

The theme function takes the current state of the application, allowing the returned Theme to be completely dynamic—just like view.

There are a bunch of built-in Theme variants at your disposal, but you can also create your own.

§Styling

As with layout, iced does not have a unified styling system. However, all of the built-in widgets follow the same styling approach.

The appearance of a widget can be changed by calling its style method:

use iced::widget::container;
use iced::Element;

fn view(state: &State) -> Element<Message> {
    container("I am a rounded box!").style(container::rounded_box).into()
}

The style method of a widget takes a closure that, given the current active Theme, returns the widget style:

use iced::widget::button;
use iced::{Element, Theme};

fn view(state: &State) -> Element<Message> {
    button("I am a styled button!").style(|theme: &Theme, status| {
        let palette = theme.extended_palette();

        match status {
            button::Status::Active => {
                button::Style::default()
                   .with_background(palette.success.strong.color)
            }
            _ => button::primary(theme, status),
        }
    })
    .into()
}

Widgets that can be in multiple different states will also provide the closure with some Status, allowing you to use a different style for each state.

You can extract the Palette colors of a Theme with the palette or extended_palette methods.

Most widgets provide styling functions for your convenience in their respective modules; like container::rounded_box, button::primary, or text::danger.

§Concurrent Tasks

The update function can optionally return a Task.

A Task can be leveraged to perform asynchronous work, like running a future or a stream:

use iced::Task;

struct State {
    weather: Option<Weather>,
}

enum Message {
   FetchWeather,
   WeatherFetched(Weather),
}

fn update(state: &mut State, message: Message) -> Task<Message> {
    match message {
        Message::FetchWeather => Task::perform(
            fetch_weather(),
            Message::WeatherFetched,
        ),
        Message::WeatherFetched(weather) => {
            state.weather = Some(weather);

            Task::none()
       }
    }
}

async fn fetch_weather() -> Weather {
    // ...
}

Tasks can also be used to interact with the iced runtime. Some modules expose functions that create tasks for different purposes—like changing window settings, focusing a widget, or querying its visible bounds.

Like futures and streams, tasks expose a monadic interface—but they can also be mapped, chained, batched, canceled, and more.

§Passive Subscriptions

Applications can subscribe to passive sources of data—like time ticks or runtime events.

You will need to define a subscription function and use the Application builder:

use iced::window;
use iced::{Size, Subscription};

#[derive(Debug)]
enum Message {
    WindowResized(Size),
}

pub fn main() -> iced::Result {
    iced::application("A cool application", update, view)
        .subscription(subscription)
        .run()
}

fn subscription(state: &State) -> Subscription<Message> {
    window::resize_events().map(|(_id, size)| Message::WindowResized(size))
}

A Subscription is a declarative builder of streams that are not allowed to end on their own. Only the subscription function dictates the active subscriptions—just like view fully dictates the visible widgets of your user interface, at every moment.

As with tasks, some modules expose convenient functions that build a Subscription for you—like time::every which can be used to listen to time, or keyboard::on_key_press which will notify you of any key presses. But you can also create your own with Subscription::run and run_with_id.

§Scaling Applications

The update, view, and Message triplet composes very nicely.

A common pattern is to leverage this composability to split an application into different screens:

use contacts::Contacts;
use conversation::Conversation;

use iced::{Element, Task};

struct State {
    screen: Screen,
}

enum Screen {
    Contacts(Contacts),
    Conversation(Conversation),
}

enum Message {
   Contacts(contacts::Message),
   Conversation(conversation::Message)
}

fn update(state: &mut State, message: Message) -> Task<Message> {
    match message {
        Message::Contacts(message) => {
            if let Screen::Contacts(contacts) = &mut state.screen {
                contacts.update(message).map(Message::Contacts)
            } else {
                Task::none()    
            }
        }
        Message::Conversation(message) => {
            if let Screen::Conversation(conversation) = &mut state.screen {
                conversation.update(message).map(Message::Conversation)
            } else {
                Task::none()    
            }
        }
    }
}

fn view(state: &State) -> Element<Message> {
    match &state.screen {
        Screen::Contacts(contacts) => contacts.view().map(Message::Contacts),
        Screen::Conversation(conversation) => conversation.view().map(Message::Conversation),
    }
}

Functor methods like Task::map, Element::map, and Subscription::map make this approach seamless.

Re-exports§

Modules§

  • advancedadvanced
    Leverage advanced concepts like custom widgets.
  • Align and position widgets.
  • Create and run iced applications step by step.
  • Draw lines around containers.
  • Access the clipboard.
  • Create and run daemons that run in the background.
  • Handle events of a user interface.
  • Choose your preferred executor to power your application.
  • Load and use fonts.
  • Colors that transition progressively.
  • Listen and react to keyboard events.
  • Listen and react to mouse events.
  • Display interactive elements on top of other widgets.
  • Space stuff around the perimeter.
  • Configure your application.
  • Create asynchronous streams of data.
  • systemsystem
    Retrieve system information.
  • Create runtime tasks.
  • Use the built-in theme and styles.
  • Listen and react to time.
  • Listen and react to touch events.
  • Use the built-in widgets or create your own.
  • Configure the window of your application in native platforms.

Macros§

  • Creates a Color with shorter and cleaner syntax.

Structs§

Enums§

  • Alignment on the axis of a container.
  • The background of some element.
  • The strategy used to fit the contents of a widget to its bounding box.
  • An error that occurred while running an application.
  • A user interface event.
  • A fill which transitions colors progressively along a direction, either linearly, radially (TBD), or conically (TBD).
  • The strategy used to fill space in a specific dimension.
  • The strategy used to rotate the content.
  • A built-in theme.

Traits§

Functions§

  • Creates an iced Application given its title, update, and view logic.
  • Creates an iced Daemon given its title, update, and view logic.
  • Creates a Task that exits the iced runtime.
  • Runs a basic iced application with default Settings given its title, update, and view logic.

Type Aliases§