Welcome to the unofficial iced-rs guide

Iced is an Open Source GUI library written in rust to create beautiful and minimal cross platform applications. It leverages the power of Google's Skia (via tiny-skia) and wGPU to render beautiful UI while maintaining clean and maintainable code by using the ELM (or MVU) architecture.

Iced focuses on Simplicity and type-safety so that you can concenterate on your implementation without any framework specific distractions.

This guide tries to explain the basics of the Iced GUI library for the Rust programming language.

Disclaimer

To make it clear at the beginning. This is not an official guide. It is not approved by the iced-rs team! If you search for the official documentation or iced book take a look at the Iced website.

Different versions

Since iced develops quite fast you might encounter big breaking changes in different versions including between the latest version and the master branch (dev version). A lot of people on the discord use the master branch and talk about features in it, so keep that in mind if you do not find them in the latest release.

Documentation Resources

You will find docs of the released iced versions here on docs.rs. If you want to see the docs of the master branch (dev version) you can get them here. There are a lot of small examples available in the example's directory of the iced repo. But make sure you select the proper git tag/branch for the iced version that you use.

Since the last version, there is also a new, great pocket guide that I advise everyone to read.

Some large structural changes are discussed and proposed in rfc's that can be found in the rfcs repository.

Other Learning Resources

There are multiple guides and tutorials that help you to learn iced, including this guide and the official book. Here is a list of other cool resources that you might find helpful:

  • awesome-iced: a list of applications that use iced
  • Github Markdown Tutorial: A very large tutorial covering a lot of stuff, but a bit out of date.
  • Youtube Text Editor Tutorial: This is a tutorial on how to build a text editor with iced 0.10, so pretty out of date. It is still a good video tutorial, but a lot of the stuff won't work in the latest iced version and can be done way easier with newer versions.
  • Example Applications that show how to structure larger apps
    • icebreaker: An application that shows great how a larger application can be made
    • iced_receipts: A small app that shows advanced structural patterns for iced applications

Other Crates

A list of other crates that you might find helpful while creating a gui with iced.

  • iced_aw: Additional widgets from the community
  • rfd: Pop-ups and file dialogs
  • plotters-iced: Plotters backend for iced - for Plotting data
  • dragking: Provides a column and row where the user can drag the items around
  • iced_audio: Helpful widgets for audio applications
  • iced_webview: A webview widget for Iced using the Ultralight/Webkit browser engine (notably the licence of the browser engine can be retrictive depending on your use case)
  • iced_divider: "An Iced widget used to change the size of adjacent containers using the mouse horizontally or vertically."

Contribution

If you want to contribute to this guide, you can open an issue on GitHub and make a pull request. For large changes, it is preferred that you open an issue first to discuss the changes. For any small changes, spelling, grammar and formatting fixes, directly opening a pull request should not be a problem.

© Héctor Ramón (hecrj) for the iced logo.

Frequently Asked Questions

Can I make a mobile app with iced?

Not really. There is a discord thread for making an android app with some success in running an iced app on android. For IOS, there is this repository, but it is marked as archived and the last commit is years old.

Is there an easy way to create pop-ups?

Iced has no built in way for pop-ups such as error, ok/cancel and file dialog popups. Although you could build them by creating multiple windows, this can be a bit complicated at the beginning. A lot of people simply use rfd for that use case, which works great.

How can I run stuff in the background / multithreaded / async?

You can do that using Subscriptions and Tasks. Take a look at the Runtime section to learn more about them.

Can I use iced from another languages?

Although I would not advise you to do so, there is a python binding and a haskell wrapper.

When is the next release, and what features will it bring with it?

The next release will probably happen when the to-dos on the roadmap are finished.

Quickstart

In this section, you will learn in detail about how iced uses the ELM architecture for it's application design. We also, learn how to write a simple counter application which increments or decrements counts with buttons.

Architecture

The architecture of iced is inspired by the elm architecture. This architecture splits your code into 4 main parts:

  • Messages
  • State
  • Update Logic
  • View Logic

 

State

The state contains all the data that your program wants to store throughout its lifespan. This is implemented using a struct. For example, in case of a simple counter app, which increments or decrements the current count value, the state would be like this,

struct Counter {
    count: i32
}

In the above snippet, all we need is a count value for a simple counter application. hence the state.

Message

The message defines any events or interactions that your program will care about. In iced, it will be implemented using the rust enum. For example, let's take a simple counter app, the Messages / Events that might occur are stored in the Message enum, For example,

enum Message {
    IncrementCount,
    DecrementCount
}

Update Logic

The update logic is called every time a message is emitted and can operate based on this message. This logic is the only one that can change the state of your application. A rough example of update logic with respect to the previous counter example is below,

fn update(&mut self, message: Message) -> iced::Task<Message> {
    match message {
        Message::IncrementCount => self.count += 1,
        Message::DecrementCount => self.count -= 1
    }
    iced::Task::none()
}

View Logic

The view logic generates the view, elements/widgets, and layout based on the current state. The view logic is called every time after the update logic is called. So for a simple counter app, all we need is a text view and two buttons. We can declare our UI as follows,

fn view(&self) {
    let ui = column![
        button("+").on_press(Message::IncrementCount),
        text(self.count),
        button("-").on_press(Message::DecrementCount)
    ]
}

Note: The snippets shown above are just for example purposes and will not compile.

Now that we got a basic understanding of the ELM architecture, we can deep dive into Iced and create a simple counter app.

A minimal Application - Counter

Our goal is to create a simple counter where we have a number displayed that we can increase or decrease with two buttons.

Creating a new Project

First of all, make sure Rust is installed in your system. If not head over to Rust Installation Page.

After installing rust, create a new binary crate by executing,

$ cargo new counter-app-iced
$ cd counter-app-iced

Add Iced crate by executing,

$ cargo add iced

Now, build the app using

$ cargo run

On successful build, you can see the text Hello World is printed on console. Now we are ready to create our beautiful GUIs using Iced.

1. Defining the State

For the state, we define a struct. For the counter, we need to store the current value of the counter.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

2. Defining the Message

For our counter, we have two major events that matter to us. Increasing and decreasing the counter.

The message is represented as an enum with two variants, IncrementCounter and DecrementCounter.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

3. Implementing the Counter

To create a window, we implement the update function for our Counter.

Initial state

Next, we need to set out initial value of our state, which is the count value. The new function helps us to do exactly that. The state of the Counter is returned with it's initial count value as 0.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

Update logic

Now we have to handle the messages that are emitted by the View Logic. The update function does exactly that. The update function get called every time when the View Logic emits a message. We use the rust's powerful match expression to handle messages. Here we use the match expression to increase the count when IncrementCount is emitted and decrease the count when DecrementCount is emitted.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

Tip: Use count.saturating_add(1) or count.saturating_sub(1) for more error proof and optimized code.

View logic

The only thing left is to define our View (a.k.a UI). Define your View Logic in the view function. In iced, all UI components are called widgets. For a counter, we need two button widgets (one for incrementing and another for decrementing) and a text widget. They need to be aligned one after another in a horizontal manner. So, we use row widget macro to align our widgets in a horizontal manner.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

In the above code, we can see that the button's on_press function accepts the message type to be emitted.

Note: By default, the view() function returns the type Element<'_, Message>. So, we use .into() for conversion purpose.

That's pretty much everything for a simple counter app. Now, let's run it.

4. Running the Counter

To run the counter we first create a application with the application function. With that function we define our Window Title and the update and view functions. This illicitly defines out state type and the message type.

Then we run the counter with an initial state and a start task that does nothing.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

Note: The main function should have a return type of Result<(), iced::Error>.

5. Full Code

Now that we completed our simple counter application, the complete code will look like this.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

In the next section, you will see about common layouting techniques that iced offers.

Layout

In this section, you will learn about different layouting widgets and techniques that iced offers us to align and place widgets. In addition, you will learn some of the basic layouting practices. These layout techniques are used to maintain best UI structure and maintain responsiveness.

Element.explain

When styling a GUI, often the result does not look exactly what you wanted it to look like. Therefore, you debug your layout.

In web development, you can use the inspection tool of your browser to show the layout and borders of your elements.

In iced, we don't have an inspection tool. But we have the Element.explain function that we can apply to any Element. This function will draw a line around the element and all of its children. With that, you can debug how spacing and sizing are applied by the renderer.

Here is a short snipped that uses .explain:

iced::Element::new(your_widget).explain(iced::Color::BLACK)

Length

Length is used to fill space in a specific dimension. The Length enum also has capablities of being responsive.

The Length enum has the following variants:

  • Length::Fill
  • Length::FillPortion(u16)
  • Length::Shrink
  • Length::Fixed(f32)

1. Length::Fill enum

Length::Fill is used to set a widget's width or height to fill the viewport. For example, setting a container's width property to Length::Fill will set the container's width to fill the available space.

let ui = container(...)
    .width(Length::Fill)
    .height(50.0);

This will result in the following,

2. Length::FillPortion(u16) enum

Length::FillPortion(u16) is used to set width or height to a collection of widgets in a specified ratio. This enum is mostly used in collection widgets such as row or column.

Let’s say we have two elements: one with FillPortion(3) and one with FillPortion(2). The first will get 2 portions of the available space, while the second one would get 3. So basically, the two elements will get it's portions in the ratio of 3:2.

let col = column![
    container(...)
        .width(Length::FillPortion(2)),
    container(...)
        .width(Length::FillPortion(3)),
].height(50.0)

This will result in the following,

Shrink

Length::Shrink is used to set the minimum length that an element needs to be properly displayed. This length will be independent from anything else. For example, a container will take the length of it's child element.

Fixed

Length::Fixed(f32) is used to set a fixed length. This length will be independent from anything else.

Length::Fixed(15.0)
Length::from(15.0)
Length::from(15)

Columns and Rows

The two most important structs for laying out widgets are Column and Row.

Both lay out their children in one direction. The column organizes the widgets vertically and the row horizontally.

Column and Row Example

By default, they align the items in the top left corner of their space.

A convenient way to create columns and rows is with the column! and row! macros.

We saw one of them in the Minimal Application - Counter.

use iced::widget;

struct Counter {
    // This will be our state of the counter app
    // a.k.a the current count value
    count: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    // Emitted when the increment ("+") button is pressed
    IncrementCount,
    // Emitted when decrement ("-") button is pressed
    DecrementCount,
}

// Implement our Counter
impl Counter {
    fn new() -> Self {
        // initialize the counter struct
        // with count value as 0.
        Self { count: 0 }
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        // handle emitted messages
        match message {
            Message::IncrementCount => self.count += 1,
            Message::DecrementCount => self.count -= 1,
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<'_, Message> {
        // create the View Logic (UI)
        let row = widget::row![
            widget::button("-").on_press(Message::DecrementCount),
            widget::text(self.count),
            widget::button("+").on_press(Message::IncrementCount)
        ];
        widget::container(row)
            .center_x(iced::Length::Fill)
            .center_y(iced::Length::Fill)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .into()
    }
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}

There, we created a Column with three children inside. One text and two buttons. The syntax for rows is the same.

You can put any Element inside a Column or Row.

Alignment

Of course, we can change the horizontal alignment for columns and the vertical alignment for rows.

Column and Row Example

This is how they would align in the center.

In code, if you want to set the Alignment you can call the align_x method on your column/row. It will return itself with the new alignment.

let some_column = iced::widget::column![
    iced::widget::text("Hello World!"),
    iced::widget::text("Another Hello World!")
].align_x(iced::Alignment::Center)

Spacing

Because you cannot set margins in Iced and often want to add space between elements.

Columns and rows provide a spacing method to control the gap/spacing.

Below is an example of how to use spacing on a column:

let some_column = iced::widget::column![
    iced::widget::text("Hello World!"),
    iced::widget::text("Another Hello World!")
].spacing(20)

Spacing Image

Wrapping

Rows offer a feature that columns don't, they can wrap their children elements onto new lines. You enable this by calling the .wrap() method on a row.

Once wrapping is activated, the layout of the row’s children changes. If the available horizontal space fills up, any extra children automatically move to a new row below. Additionally, children with a width set to Fill or FillPortion expand to take up any remaining horizontal space, which can trigger a row break. In contrast, children with fixed or shrink widths continue to be placed side by side until there isn’t enough space, at which point they break onto a new row.

Container

The Container is useful when aligning items. A Container has one child element (could be a button, text, column, row, etc.).

use iced::{widget, Length};
use iced::alignment::{Horizontal, Vertical};

let stuff_centered = widget::Container::new(widget::text("Some Text"))
    .align_x(Horizontal::Center)
    .align_y(Vertical::Center)
    .width(Length::Fill)
    .height(Length::Fill);

Note:

We use width and height to maximize the size of the container, creating extra space for centering.

However, this is not strictly necessary if the container is already large enough; without additional space, there will be no noticeable difference between applying alignment and not applying any alignment.

Both align_x and align_y methods are available for alignment purposes on Container.

Subscriptions and Tasks

Sometimes you have a task that takes some time to complete and should run in the background. If you run it in your update function, the GUI becomes locked and unresponsive until the task is finished. This could be a web request or an operation that listens for external events.

Iced offers two solutions to this issue: Task and Subscription.

A task runs until it completes, whereas a subscription continues running as long as the application requires it.

In this chapter, we will examine both solutions and explore how to use them.

Tasks

Note: in the past Tasks where named Commands

A task is "A set of asynchronous actions to be performed by some runtime".

Basically, a task is just a Stream that returns messages.

You can create custom tasks, but often you get them by some function and just want to execute it. For example, minimizing and maximizing a window requires executing a given task.

A task will run until it has finished and can return multiple messages during its execution.

Executing a Task

In your App, you can execute a task by returning it from the update function of your application.

Batch multiple tasks

Sometimes you want to return more than one task. For that, you can use the Task::batch function to batch a few of them together like this:

return Task::batch(vec![task1, task2, task3]);

Custom Task using perform

In this example, we will create an app that fetches your current IP address by making an API call with our own custom task.

Dependencies

As you see, we have two dependencies in our project.

One of them is reqwest. We use reqwest to make the API call.

The other one is iced. Since this is a guide for iced, that should not wonder you. But as you see, we added the tokio feature. This lets iced use tokio as part of the runtime as needed for reqwest.

[dependencies]
iced = {version="0.13.1", features = ["tokio"]}
reqwest = "0.11.24"

Making the api request

At first, we define what our task will do.

For that, we are creating an async function that makes an async get request to an API that provides the public IP.

use iced::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

Tip: If you have something that is not async but synchronous and will block your application like a heavy computation, you can use tokio::spawn_blocking in a task or subscription to run a closure on a thread where blocking is acceptable.

Starting/Creating the task

In the update function we return Task::none() or our custom task depending on the message.

If the Message is Message::CurrentIp we change our state, if it is Message::Refetch we return our task.

use iced::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

To create our custom task, we use the Task::perform function. It takes a future, in this case our fetch_ip function, and a closure that converts the returned value of the future into a message.

use iced::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

Note: fetch_ip() produces the future

Note: Message::CurrentIp is a shorthand for |x| Message::CurrentIp(x)

Full Code

use iced::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

Task From Stream

Imagine you want a Task that produces more than one Message. One solution is to use Task::run and pass a Stream to it.

A Stream is basically an async iterator.

To create a Stream we can use iced::stream::channel. With that function, we can convert a Future to a Stream. In the Future we can emit messages via a given Sender.

Subscriptions

A subscription is similar to a task. It runs in the background. Subscriptions are often used to listen to external events. It can produce one or more values. One key difference is that we control how long a subscription runs. That leads to the "issue" that the subscription itself can never end by itself, even after finishing its work.

Warning: I am not that familiar with the iced internals so the following might be incorrect. It is only how I understood it.

A Subscription runs as long as we return it from the closure provided by the subscription function. The runtime calls that method after each update and checks if a new or old subscription is provided.

Every Subscription has an ID. If you use Subscription::run_with_id you specify the ID. If you use Subscription::run the function pointer is used, which could cause bugs, from what I have heard.

If a new subscription is provided, the runtime will start it. If an old one that already runs is provided, nothing happens. If a subscription runs that is not provided by the subscription function, the running subscription is terminated.

Create a Stream

To create a Stream we can use iced::stream::channel. With that function, we can convert a Future to a Stream. In the future, we can emit messages via a given Sender.

Listen to events

Often you want to do something when the user presses some key, if a file is dropped on your window or general mouse events. For that, you can use the iced::event::listen subscription. It runs in the background and emits a message with/on every Event.

Note: If you just want to get mouse events in a specific widget area you should use the MouseArea widget.

Here is a practical example how to listen to an arbitrary event in form of a keyboard event.

In the example, the subscription always runs as defined in the .subscription method of application here:

use iced::widget;

#[derive(Debug, Clone)]
enum Message {
    Event(iced::event::Event),
}

fn update(_state: &mut u8, message: Message) -> iced::Task<Message> {
    // handle emitted messages
    match message {
        Message::Event(event) => match event {
            iced::event::Event::Keyboard(keyboard_event) => match keyboard_event {
                iced::keyboard::Event::KeyReleased { key, .. } => {
                    println!("Key {:?} was pressed", key);
                }
                _ => {}
            },
            _ => {}
        },
    }
    iced::Task::none()
}

fn view(_state: &u8) -> iced::Element<'_, Message> {
    widget::text("Event Example").into()
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Event example", update, view)
        .subscription(|_state| iced::event::listen().map(Message::Event))
        .run()
}

It emits a message containing the event:

use iced::widget;

#[derive(Debug, Clone)]
enum Message {
    Event(iced::event::Event),
}

fn update(_state: &mut u8, message: Message) -> iced::Task<Message> {
    // handle emitted messages
    match message {
        Message::Event(event) => match event {
            iced::event::Event::Keyboard(keyboard_event) => match keyboard_event {
                iced::keyboard::Event::KeyReleased { key, .. } => {
                    println!("Key {:?} was pressed", key);
                }
                _ => {}
            },
            _ => {}
        },
    }
    iced::Task::none()
}

fn view(_state: &u8) -> iced::Element<'_, Message> {
    widget::text("Event Example").into()
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Event example", update, view)
        .subscription(|_state| iced::event::listen().map(Message::Event))
        .run()
}

In the update method we can use that event and react to it:

use iced::widget;

#[derive(Debug, Clone)]
enum Message {
    Event(iced::event::Event),
}

fn update(_state: &mut u8, message: Message) -> iced::Task<Message> {
    // handle emitted messages
    match message {
        Message::Event(event) => match event {
            iced::event::Event::Keyboard(keyboard_event) => match keyboard_event {
                iced::keyboard::Event::KeyReleased { key, .. } => {
                    println!("Key {:?} was pressed", key);
                }
                _ => {}
            },
            _ => {}
        },
    }
    iced::Task::none()
}

fn view(_state: &u8) -> iced::Element<'_, Message> {
    widget::text("Event Example").into()
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Event example", update, view)
        .subscription(|_state| iced::event::listen().map(Message::Event))
        .run()
}

Important Note: The example uses iced::event::listen that reacts to all kind of events. There are specific subscriptions for special event kinds, such as window, key_press and key_release, as well.

Full Code

use iced::widget;

#[derive(Debug, Clone)]
enum Message {
    Event(iced::event::Event),
}

fn update(_state: &mut u8, message: Message) -> iced::Task<Message> {
    // handle emitted messages
    match message {
        Message::Event(event) => match event {
            iced::event::Event::Keyboard(keyboard_event) => match keyboard_event {
                iced::keyboard::Event::KeyReleased { key, .. } => {
                    println!("Key {:?} was pressed", key);
                }
                _ => {}
            },
            _ => {}
        },
    }
    iced::Task::none()
}

fn view(_state: &u8) -> iced::Element<'_, Message> {
    widget::text("Event Example").into()
}

fn main() -> Result<(), iced::Error> {
    // run the app from main function
    iced::application("Event example", update, view)
        .subscription(|_state| iced::event::listen().map(Message::Event))
        .run()
}

Blocking Code

To run non async code / blocking in a Task or a Subscription we can use tokio::task::spawn_blocking

Note: This might only work on native and not on wasm

Example

Here is a small example that shows how to use tokio::task::spawn_blocking.

Cargo.toml

Because we want to use spawn_blocking from tokio we need to add the tokio feature to iced. This will lead to iced using tokio.

[dependencies]
iced = { version = "0.13.1", features = ["tokio"] }
tokio = { version = "1.38.0", features = ["rt"] }

Actual Example

In the example, there will be a button and a text. A press onto the button will trigger a large computation to be started (in the example, we will just sleep a few seconds and return a number). If the computation finishes, the result will be shown in the text widget.

Our computation runs in a task, because we do not want to block our whole UI until it has finished.

Inside the task we call spawn_blocking with a closure of our computation. To get the returned value of the closure, we need to await the JoinHandle returned by spawn_blocking. That will give us the result of the heavy computation without blocking the UI.

#[derive(Debug, Clone)]
enum Message {
    CalculatedInformation(i32),
    StartCalculatingInformation,
}

#[derive(Default)]
struct App {
    hard_to_process_information: Option<i32>,
    calculation_in_progress: bool,
}

impl App {
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
            Message::CalculatedInformation(information) => {
                // Set the information
                self.hard_to_process_information = Some(information);
            }
            Message::StartCalculatingInformation => {
                // Change the state to indicate that the calculation is in progress
                self.calculation_in_progress = true;
                // Return a task that will calculate the information
                return iced::Task::future(async {
                    let information = tokio::task::spawn_blocking(|| {
                        println!("Calculating information...");
                        // Simulate a long computation
                        std::thread::sleep(std::time::Duration::from_secs(2));

                        // return some value
                        42
                    })
                    .await
                    .unwrap();

                    // Send the information back to the update function
                    Message::CalculatedInformation(information)
                });
            }
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            // Display the information if it is available
            iced::widget::Text::new(self.hard_to_process_information.map_or(
                "Information will be ready in a second...".to_string(),
                |x| format!("Information: {}", x),
            )),
            // Display a button to start the calculation
            iced::widget::button("Start Calculation").on_press_maybe(
                if self.calculation_in_progress {
                    None
                } else {
                    Some(Message::StartCalculatingInformation)
                }
            )
        ]
        .into()
    }
}

fn main() {
    iced::run("Task Example", App::update, App::view).unwrap();
}

Full Code

#[derive(Debug, Clone)]
enum Message {
    CalculatedInformation(i32),
    StartCalculatingInformation,
}

#[derive(Default)]
struct App {
    hard_to_process_information: Option<i32>,
    calculation_in_progress: bool,
}

impl App {
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
            Message::CalculatedInformation(information) => {
                // Set the information
                self.hard_to_process_information = Some(information);
            }
            Message::StartCalculatingInformation => {
                // Change the state to indicate that the calculation is in progress
                self.calculation_in_progress = true;
                // Return a task that will calculate the information
                return iced::Task::future(async {
                    let information = tokio::task::spawn_blocking(|| {
                        println!("Calculating information...");
                        // Simulate a long computation
                        std::thread::sleep(std::time::Duration::from_secs(2));

                        // return some value
                        42
                    })
                    .await
                    .unwrap();

                    // Send the information back to the update function
                    Message::CalculatedInformation(information)
                });
            }
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            // Display the information if it is available
            iced::widget::Text::new(self.hard_to_process_information.map_or(
                "Information will be ready in a second...".to_string(),
                |x| format!("Information: {}", x),
            )),
            // Display a button to start the calculation
            iced::widget::button("Start Calculation").on_press_maybe(
                if self.calculation_in_progress {
                    None
                } else {
                    Some(Message::StartCalculatingInformation)
                }
            )
        ]
        .into()
    }
}

fn main() {
    iced::run("Task Example", App::update, App::view).unwrap();
}

Structuring Apps

When you create larger iced apps, you might want to have reusable components and views. For that, there is a common approach that is also used in this showcase from the founder of iced.

In this architecture a view/component is independent of the application and is split into three parts, message, the state and the action.

The state will have a normal view function that returns a iced::Element<Message>.

The update function will differ a bit. Instead of a Task like our main application, it will return an action enum. Common variants for this enum can be None, Task(iced::Task<Message>) and Submit(String). You can think of this action like a message that is sent from the view to the parent application that should handle it.

This kind of architecture will enhance composability.

Initial State

The view should also have a function that creates a new instance of that view. If some task needs to be done for the initial state of the view (i.e. fetching data), something like (Self, iced::Task<Message>) should be returned by that function. The task would be executed, and the data is sent via a message to the view.

To differ between a loaded and not loaded state you can use something like this pattern for your state:

enum State {
    NotLoaded,
    Loaded {
        field1: String,
        field2: i32
    }
}

If you do not need to lazy load something from an API or do similar, you can just have a normal struct as state.

Mapping

If you want to compose your UI of subparts that have their own functionality, it makes sense to give them their own messages.

But to use a iced::Element<ViewMessage> in your app that requires a iced::Element<AppMessage> you have to map them.

For that, you can simply use iced::Element<ViewMessage>.map(AppMessage::ViewMessage).

Note: this is a shortcut for iced::Element<ViewMessage>.map(|view_message| AppMessage::ViewMessage(view_message))

The .map function takes a closure that takes the message and converts it into another message. This often requires having a dedicated message variant on the application level that contains the view message.

Map functions like this are available on Task::map, Subscription::map, Element::map.

Example

The example shows an unfinished joke listing app with a view for creating jokes. When the user tries to create a new joke, a default joke, fetched from an API, is provided. The user can get a random joke from the API on a button click as well. All Jokes are listed in the main/default view.

This is how the view for new jokes looks like using this design:

fn main() {
    iced::run("Project Structure Example", App::update, App::view).unwrap();
}

#[derive(Debug, Clone)]
enum Message {
    // This message is used to handle the new views message
    NewMessage(new::Message),
    New,
}

#[derive(Debug, Default)]
enum View {
    #[default]
    Default,
    Edit(new::NewView),
}

#[derive(Debug, Default)]
struct App {
    view: View,
    items: Vec<String>,
}

impl App {
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
            Message::NewMessage(view_message) => {
                if let View::Edit(edit) = &mut self.view {
                    // Call the update method of the edit view
                    // and handle the returned action
                    match edit.update(view_message) {
                        new::Action::None => {}
                        // If the action is a task, map the
                        // task to a message, the higher level message
                        new::Action::Task(task) => return task.map(Message::NewMessage),
                        new::Action::Submitted(content) => {
                            self.view = View::Default;
                            self.items.push(content);
                        }
                    }
                }
            }
            Message::New => {
                // Create a new view
                let (view, task) = new::NewView::new();
                self.view = View::Edit(view);
                // Run the task and map it to the higher level message
                return task.map(Message::NewMessage);
            }
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<Message> {
        match &self.view {
            View::Default => {
                let items = self
                    .items
                    .iter()
                    .map(|item| iced::widget::text(item).into())
                    .collect();
                iced::widget::column![
                    iced::widget::button("Edit").on_press(Message::New),
                    iced::widget::Column::from_vec(items)
                ]
                .into()
            }
            // If the view is an edit view, call the view method of the edit view
            // and map the returned message to the higher level message
            View::Edit(edit) => edit.view().map(Message::NewMessage),
        }
    }
}

mod new {
    pub enum Action {
        None,
        Task(iced::Task<Message>),
        Submitted(String),
    }

    #[derive(Debug, Clone)]
    pub enum Message {
        Submit,
        ChangeContent(String),
        RandomJoke,
    }

    #[derive(Debug, Default)]
    pub struct NewView {
        content: String,
    }

    impl NewView {
        pub fn new() -> (Self, iced::Task<Message>) {
            (Self::default(), Self::random_joke_task())
        }

        pub fn update(&mut self, message: Message) -> Action {
            match message {
                Message::Submit => Action::Submitted(self.content.clone()),
                Message::ChangeContent(content) => {
                    self.content = content;
                    Action::None
                }
                Message::RandomJoke => Action::Task(Self::random_joke_task()),
            }
        }

        pub fn view(&self) -> iced::Element<Message> {
            iced::widget::column![
                iced::widget::text_input("Content", &self.content).on_input(Message::ChangeContent),
                iced::widget::button("Random Joke").on_press(Message::RandomJoke),
                iced::widget::button("Submit").on_press(Message::Submit)
            ]
            .into()
        }

        fn random_joke_task() -> iced::Task<Message> {
            iced::Task::future(async {
                // Fetch a joke from the internet
                let client = reqwest::Client::new();
                let response: serde_json::Value = client
                    .get("https://icanhazdadjoke.com")
                    .header("Accept", "application/json")
                    .send()
                    .await
                    .unwrap()
                    .json()
                    .await
                    .unwrap();

                // Parse the response
                let joke = response["joke"].as_str().unwrap();

                // Return the joke as a message
                Message::ChangeContent(joke.to_owned())
            })
        }
    }
}

As you see, the update function produces this action, that the parent of the view can handle:

fn main() {
    iced::run("Project Structure Example", App::update, App::view).unwrap();
}

#[derive(Debug, Clone)]
enum Message {
    // This message is used to handle the new views message
    NewMessage(new::Message),
    New,
}

#[derive(Debug, Default)]
enum View {
    #[default]
    Default,
    Edit(new::NewView),
}

#[derive(Debug, Default)]
struct App {
    view: View,
    items: Vec<String>,
}

impl App {
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
            Message::NewMessage(view_message) => {
                if let View::Edit(edit) = &mut self.view {
                    // Call the update method of the edit view
                    // and handle the returned action
                    match edit.update(view_message) {
                        new::Action::None => {}
                        // If the action is a task, map the
                        // task to a message, the higher level message
                        new::Action::Task(task) => return task.map(Message::NewMessage),
                        new::Action::Submitted(content) => {
                            self.view = View::Default;
                            self.items.push(content);
                        }
                    }
                }
            }
            Message::New => {
                // Create a new view
                let (view, task) = new::NewView::new();
                self.view = View::Edit(view);
                // Run the task and map it to the higher level message
                return task.map(Message::NewMessage);
            }
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<Message> {
        match &self.view {
            View::Default => {
                let items = self
                    .items
                    .iter()
                    .map(|item| iced::widget::text(item).into())
                    .collect();
                iced::widget::column![
                    iced::widget::button("Edit").on_press(Message::New),
                    iced::widget::Column::from_vec(items)
                ]
                .into()
            }
            // If the view is an edit view, call the view method of the edit view
            // and map the returned message to the higher level message
            View::Edit(edit) => edit.view().map(Message::NewMessage),
        }
    }
}

mod new {
    pub enum Action {
        None,
        Task(iced::Task<Message>),
        Submitted(String),
    }

    #[derive(Debug, Clone)]
    pub enum Message {
        Submit,
        ChangeContent(String),
        RandomJoke,
    }

    #[derive(Debug, Default)]
    pub struct NewView {
        content: String,
    }

    impl NewView {
        pub fn new() -> (Self, iced::Task<Message>) {
            (Self::default(), Self::random_joke_task())
        }

        pub fn update(&mut self, message: Message) -> Action {
            match message {
                Message::Submit => Action::Submitted(self.content.clone()),
                Message::ChangeContent(content) => {
                    self.content = content;
                    Action::None
                }
                Message::RandomJoke => Action::Task(Self::random_joke_task()),
            }
        }

        pub fn view(&self) -> iced::Element<Message> {
            iced::widget::column![
                iced::widget::text_input("Content", &self.content).on_input(Message::ChangeContent),
                iced::widget::button("Random Joke").on_press(Message::RandomJoke),
                iced::widget::button("Submit").on_press(Message::Submit)
            ]
            .into()
        }

        fn random_joke_task() -> iced::Task<Message> {
            iced::Task::future(async {
                // Fetch a joke from the internet
                let client = reqwest::Client::new();
                let response: serde_json::Value = client
                    .get("https://icanhazdadjoke.com")
                    .header("Accept", "application/json")
                    .send()
                    .await
                    .unwrap()
                    .json()
                    .await
                    .unwrap();

                // Parse the response
                let joke = response["joke"].as_str().unwrap();

                // Return the joke as a message
                Message::ChangeContent(joke.to_owned())
            })
        }
    }
}

The application that would host the view could look like this:

fn main() {
    iced::run("Project Structure Example", App::update, App::view).unwrap();
}

#[derive(Debug, Clone)]
enum Message {
    // This message is used to handle the new views message
    NewMessage(new::Message),
    New,
}

#[derive(Debug, Default)]
enum View {
    #[default]
    Default,
    Edit(new::NewView),
}

#[derive(Debug, Default)]
struct App {
    view: View,
    items: Vec<String>,
}

impl App {
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
            Message::NewMessage(view_message) => {
                if let View::Edit(edit) = &mut self.view {
                    // Call the update method of the edit view
                    // and handle the returned action
                    match edit.update(view_message) {
                        new::Action::None => {}
                        // If the action is a task, map the
                        // task to a message, the higher level message
                        new::Action::Task(task) => return task.map(Message::NewMessage),
                        new::Action::Submitted(content) => {
                            self.view = View::Default;
                            self.items.push(content);
                        }
                    }
                }
            }
            Message::New => {
                // Create a new view
                let (view, task) = new::NewView::new();
                self.view = View::Edit(view);
                // Run the task and map it to the higher level message
                return task.map(Message::NewMessage);
            }
        }
        iced::Task::none()
    }

    fn view(&self) -> iced::Element<Message> {
        match &self.view {
            View::Default => {
                let items = self
                    .items
                    .iter()
                    .map(|item| iced::widget::text(item).into())
                    .collect();
                iced::widget::column![
                    iced::widget::button("Edit").on_press(Message::New),
                    iced::widget::Column::from_vec(items)
                ]
                .into()
            }
            // If the view is an edit view, call the view method of the edit view
            // and map the returned message to the higher level message
            View::Edit(edit) => edit.view().map(Message::NewMessage),
        }
    }
}

mod new {
    pub enum Action {
        None,
        Task(iced::Task<Message>),
        Submitted(String),
    }

    #[derive(Debug, Clone)]
    pub enum Message {
        Submit,
        ChangeContent(String),
        RandomJoke,
    }

    #[derive(Debug, Default)]
    pub struct NewView {
        content: String,
    }

    impl NewView {
        pub fn new() -> (Self, iced::Task<Message>) {
            (Self::default(), Self::random_joke_task())
        }

        pub fn update(&mut self, message: Message) -> Action {
            match message {
                Message::Submit => Action::Submitted(self.content.clone()),
                Message::ChangeContent(content) => {
                    self.content = content;
                    Action::None
                }
                Message::RandomJoke => Action::Task(Self::random_joke_task()),
            }
        }

        pub fn view(&self) -> iced::Element<Message> {
            iced::widget::column![
                iced::widget::text_input("Content", &self.content).on_input(Message::ChangeContent),
                iced::widget::button("Random Joke").on_press(Message::RandomJoke),
                iced::widget::button("Submit").on_press(Message::Submit)
            ]
            .into()
        }

        fn random_joke_task() -> iced::Task<Message> {
            iced::Task::future(async {
                // Fetch a joke from the internet
                let client = reqwest::Client::new();
                let response: serde_json::Value = client
                    .get("https://icanhazdadjoke.com")
                    .header("Accept", "application/json")
                    .send()
                    .await
                    .unwrap()
                    .json()
                    .await
                    .unwrap();

                // Parse the response
                let joke = response["joke"].as_str().unwrap();

                // Return the joke as a message
                Message::ChangeContent(joke.to_owned())
            })
        }
    }
}

Component

Important Notice: The Component Trait is deprecated, and I strongly advise against its use. If you are interested in creating reusable components, take a look at the App Structure section.

Sometimes you want to create your own reusable custom components that you can reuse through your applications. That is where the Component trait comes in place. You can turn anything that implements this trait easily into an Element with the component function.

Components should only be used as reusable widgets and not for organizing code or splitting your applications into different parts!

Note: Component is only available on crate feature lazy.

The 3 parts of a Component

Each component is build out of three parts: the component itself, the state of the component and the internal message of the component.

These are similar to the 3 parts of your application with one difference. The internal state that can change based on events is represented as an extra a struct, not as the component struct itself.

Component itself

At first, we create the component struct itself:

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

As you see, it has one field link. Here, we store the link that will be displayed and opened when the hyperlink is clicked.

Message / Event

Now we need to create the message that will be used inside our component:

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Here we have three events for our component. The Clicked event is called every time the user clicks onto the component. The MouseEnter and MouseExit events are called when the mouse enters over the component and leaves (in other words, hovering over the component).

State

In the state of our component, we store if the mouse hovers over the component.

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Implementing the Component trait

Now we can implement the Component trait for the Hyperlink struct.

Full Implementation

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Types

We define the types for our state and message/event:

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

View and Update logic

Every time an event is called, the update and view function gets called.

In the update function, we set the hovered field of the state or print "open link". Instead of printing something you could use crates like opener to open files and website, but that is beyond this example.

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

As you see, we return None in the update function. Instead of None we could return a Some(Message) that is propagated to the parent application.

We define in the view function how our component looks on the screen. In this case, we have a mouse area with a text inside.

The text color changes when the mouse is hovered over the component. If the mouse hovers above the component is determined by the state.hovered field that is hold up to date by our update function.

use iced::widget;
use iced::widget::Component;
pub struct Hyperlink {
    link: String,
}

impl Hyperlink {
    pub fn new(link: String) -> Self {
        Self { link }
    }
}

#[derive(Debug, Copy, Clone)]
pub enum HyperlinkEvent {
    Clicked,
    MouseEnter,
    MouseExit,
}

#[derive(Default)]
pub struct HyperlinkState {
    hovered: bool,
}

impl<Message> Component<Message> for Hyperlink {
    type State = HyperlinkState;
    type Event = HyperlinkEvent;

    fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
        match event {
            HyperlinkEvent::Clicked => println!("open link"),
            HyperlinkEvent::MouseEnter => state.hovered = true,
            HyperlinkEvent::MouseExit => state.hovered = false,
        }
        None
    }

    fn view(
        &self,
        state: &Self::State,
    ) -> iced::Element<'_, Self::Event, iced::Theme, iced::Renderer> {
        widget::container(
            widget::mouse_area(widget::text(&self.link).style(iced::theme::Text::Color(
                if state.hovered {
                    iced::Color::from_rgb(0.5, 0.5, 0.5)
                } else {
                    iced::Color::from_rgb(0.0, 0.0, 0.0)
                },
            )))
            .on_enter(HyperlinkEvent::MouseEnter)
            .on_exit(HyperlinkEvent::MouseExit)
            .on_press(HyperlinkEvent::Clicked),
        )
        .into()
    }
}

Render Backend

Forcing Software Rendering:

By default, Iced tries to use wgpu as the backend, and if that is not possible, it uses tiny-skia as a fallback software renderer.

If you want to specifically use tiny-skia as render backend, you can do that with an environment variable ICED_BACKEND = tiny-skia :

[env]
ICED_BACKEND = "tiny-skia"

Selecting a Graphics Backend for WGPU:

For Hardware Accelerated Rendering(i.e. Using a GPU to render) Iced uses WGPU.

WGPU by itself can use various graphics API's like OpenGL, Vulkan, DirectX, Metal, etc.

To force a specific graphics backend for WGPU you can set an environemnt variable WGPU_BACKEND = vulkan:

[env]
WGPU_BACKEND = "vulkan" # It can have these values: dx12 / vulkan / metal / gl

NOTE:

To set these Environment Variables in Cargo, you need to create a .cargo directory in the root of your repository. Then create a config.toml in this directory and then add the code mentioned above.

Example of config.toml file contents:

[env]
WGPU_BACKEND = "vulkan"

Wasm / Running on Web

Iced has the ability to compile to web assembly (WASM). With that, you can run your iced app in the normal web browser.

When run on the web, iced uses a canvas to render the application to it. Previously, there was the iced_web repository that used another approach, but since it was hard to maintain, support was dropped. As of today, iced_web is out of date and not maintained.

Preparation

Before we start, we need to get the tools for compiling for the web.

Add Wasm Target

First, we need to add the wasm32-unknown-unknown target to our compiler so that we are able to compile to wasm.

rustup target add wasm32-unknown-unknown

If you want, you can compile to that target using

cargo build --target wasm32-unknown-unknown

Install Trunk

We will use trunk for building and serving the web page. For that, we need to install trunk via cargo:

cargo install --locked trunk

Create index.html

For trunk we need to create a index.html file that trunk will use as the base file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NAME OF THE WEBSITE</title>
    <base data-trunk-public-url />
</head>
<body style="margin: 0; padding: 0; width:100%; height:100svh">
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="directoryname" />
</body>
</html>

Replace the directoryname with the name of your project.

Iced Features that are important for wasm

  • web-colors on the web, the colors of your app might not be correct or as intended, this feature fixes that.
  • webgl makes your app run in browsers that do not support wgpu (sadly, wgpu is not supported on all browsers and all platforms).

Running the App using Trunk

trunk serve

This will compile your project to wasm, build a web page, watch for changes and serve the app for you. You can access the web app at the URL given in the output. The generated site that is served is located in the dist directory.

Deploying the Web app

For now, we only used trunk serve to run our app. This works fine for development, but is not very good for production. We don't need to watch for changes in our files and rebuild/autoload them in production. Without that functionality, the served html can be way smaller.

Thankfully, we can use trunk build to build our app into a minimal result. If you look at the dist directory, you can see your build app (index.html as the starting point).

Disclaimer: If you try to open the index.html file without a web server that serves it, you will run into CORS issues.

You can serve these files on any web server. As a minimal example, you can use the built-in python web server and execute python3 -m http.server in the dist directory.

Debugging Wasm (ready)

If you encounter panics and errors in your browser console from your wasm builds, you might notice that these errors are nearly impossible to interpret.

To get a better error with a notice where exactly in your code the panic occurred, you have to change the panic hook to one that provides better exceptions in your browser.

For that, we can use the crate console_error_panic_hook.

To use that one you need to set the panic hook at the start of your program like here:

fn main() {
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));

    // ...
}

With that in place, the errors in your web console should look a lot better.

Debug Mode

If you want to optimize your app, measure performance, or debug issues, you might want to use debug mode.

It provides access to information such as:

  • Startup time
  • Update timings
  • View timings
  • Layout timings

To enable it, add the Cargo feature debug to the iced dependency and run your application.
To activate debug mode, simply press F12.

In the top-right corner, you should see the debug information displayed like this:

Preview

Widget API

Disclaimer: This section is based on version 0.13.1! If you are using a newer version of Iced the API will be different!

In rare cases, you might want to write a custom widget. A custom widget gives you full control over the layout and drawing process. A custom widget is defined as something that implements the Widget trait.

You might want to use other existing widgets in your custom widget. It is recommended that you store them as Elements in your widget struct. When calling methods on the children that take the state Tree, you need to make sure to pass the correct child tree from Tree.children to the child widget.
If you want them to produce messages that are local to your widget, just like a component, you can create a new Shell and give it to them in the on_event function.

To get detailed examples of the Widget trait you can always take a look at the core widgets like Button.

Generics of the Widget trait

The Widget trait has three different generics.

Message

The Message generic is the message type that your widget can emit. It should be left generic to fit into any application that wants to use the widget. That is why it is common not to create the message hardcoded, but to take a Message as a parameter or a function that generates the appropriate message instead.

Good examples of this are Button where the message can be set with the on_press and is saved in the Button struct:

pub fn on_press(mut self, on_press: Message) -> Self {
    self.on_press = Some(OnPress::Direct(on_press));
    self
}

or the TextInput that takes a message in its on_input method to generate the appropriate message:

pub fn on_input(
    mut self,
    on_input: impl Fn(String) -> Message + 'a,
) -> Self {
    self.on_input = Some(Box::new(on_input));
    self
}

Theme

The Theme generic specifies the type of the theme. To keep it simple, you could stay with the iced provided ones and use iced::Theme.

In the long run, it is better to use a generic theme and Catalog instead.

Renderer

The Renderer generic specifies the renderer that is used to draw your widget. For most widgets, you can use a generic that implements iced::advanced::Renderer, since this trait has the renderer interface that is most used (as far as I can tell). There are also traits like the one for SVGs that you might need for specific use cases, such as SVGs.

Displaying Text

Displaying text in your widget is quite difficult. The easiest way I found is to just use a normal text widget instead of using the text renderer directly.

Common Structs

Limits

The limits contain the maximum space that the widget can take in the layout.

They contain the minimum limit that describes the space that the widget will get either way. So even if the given node is smaller, the space will be only used for that widget.

If the widget with shrink as width is placed in a row with an item that has set its size to fill, the minimum limit will be 0. If both widths are set to Fill, it will get the minimum limit of half the space that the row takes.

If the Size of the returned node is larger than the maximum specified by the limits, as far as I tested in rows and columns, other widgets that come after the widget will have just less space available (in their layout methods). But it is not recommended to take more space than there is specified as the maximum in the limits.

Node's

A Node is a rectangle with position and size that can have a list of children.

You can move or translate its position with the .move_to and .translate position. The node also provides a lot of other helper methods that are worth looking at (i.e., padding).

Tree's

A tree with the current state of the widget and all the states of its children.

Methods of the Widget trait

State (child states), Tag and Diff

A widget can have a state or be stateless. A widget state can contain data that should live longer than the widget itself. A widget itself lives quite short. After each view call, it gets recreated. The scrollable, for example, saves the scroll position in its state. That way, the scroll position stays after each view call.

If your widget has a state, you need to implement the tag and state methods; otherwise, you can just use the default implementation. If you reuse other widgets that have a state, you are required to implement the children method.

State method

At best, the state of your widget is created once and will be reused as much as possible. The state method returns the initial state of your widget if a new one needs to be created. It could look like this:

fn state(&self) -> iced::advanced::widget::tree::State {
	iced::advanced::widget::tree::State::new(YourCustomState::default())
}

Tag method

To identify the different states, the Tag is used. The tag method returns a Tag based on your state type. Internally, it uses the TypeId of your state to identify it.

Note: You are right if you think: "If if uses the type ID, can two of my widget states be accidentally swapped?", you are right. They can. For those cases the diff method should fix it.

For most use cases, the tag method will look like this:

fn tag(&self) -> iced::advanced::widget::tree::Tag {
	iced::advanced::widget::tree::Tag::of::<YourCustomState>()
}

If you have a widget that is stateless, you can just use the default implementation from the Widget trait.

diff

This function compares/reconciles the old state Tree, with what you would expect it to be. If there should be something different, you change it in the given tree.
There are two cases where this is needed. The first one is when two states with the same tag are accidentally swapped. In the second one, the state should change based on data passed to the widget in a view call.

If your widget has child widgets, it should contain something like this to diff the children:

fn diff(&self, tree: &mut Tree) {
	tree.diff_children(&self.children);
}

children

If your widget uses other children, you need to return their state Trees from the children method. The order of the child Trees returned determines the order of Tree.children in all other methods.

This method could look like this:

fn children(&self) -> Vec<Tree> {
	vec![Tree::new(&self.your_widget_as_element)]
}

size

The size method returns the size of the widget. That size can be used by other widgets to find a good layout. A good example of this is iced::advanced::layout::flex.

The size method could look like this:

fn size(&self) -> iced::Size<iced::Length> {
    iced::Size::new(iced::Length::Shrink, iced::Length::Fill)
}

layout

The layout method defines the layout of the widget and all of its child widgets. It returns a Node that represents the layout of the widget and all of its children.

To do that, you are given the Limits of the widget, meaning the minimum and maximum size that the widget can get, and the current state Tree.

If you have child widgets, you need to call their layout methods with their state from Tree.children, the Renderer (you can simply use the one that is given as a parameter) and limits you want to assign to the child. The returned Node shall be included in the returned Node.children of your widget.

draw

The draw function uses the given Renderer to draw the widget onto the screen. The renderer can implement different Renderer traits.

With the given viewport, you can know what region of your window is currently visible.

In general, your widget should use the theme provided colors to fit into the application style. The text color is provided by the style parameter.

You can access the position, area and layout of your children via the given Layout parameter. The order of child layouts provided in layout.children() is equal to the order of Node.children returned by the layout method.
To draw child widgets, you can simply call their draw methods with the appropriate state (from Tree.children) and layout (from Layout.children()).

Since you might want to draw some special effect or graphic depending on the mouse position, you can access it if available with the Cursor parameter.

by default, if the cursor position is available if it is over the window

operate

This function applies some given operation. The Operation can have different effects.

You should apply all the appropriate functions of the Operation trait. To do so, you pass your state to the function and an Id that identifies your state. Of course, you should only apply functions that are used. If your widget can not be focused, you should not call the focusable function.

If your widget has a child widget that does some operations, your operate method could look like this:

fn operate(
	&self,
	tree: &mut Tree,
	layout: Layout<'_>,
	renderer: &Renderer,
	operation: &mut dyn Operation,
) {
	operation.container(None, layout.bounds(), &mut |operation| {
		self.your_child_element.as_widget().operate(
			&mut tree.children[0],
			layout.children().next().unwrap(),
			renderer,
			operation,
		);
	});
}

If you are interested in implementing operations for your widget, see the operation section.

on_event

The on_event method processes an Event.

The Cursor parameter provides access to the current mouse position. The Clipboard parameter gives access to the clipboard of the user's system.

This is the only method of your widget that can emit messages to the application. For that, a Shell is provided as a parameter. But the Shell can do other things as well. You can check if the current layout is valid or invalidate it, request redraws, check if the widget tree is valid and invalidate the widget tree, etc.

The method returns Status::Ignored if neither the widget nor its children have handled the event or Status::Captured else. For easier merging of the Statuses with child widgets, you can use the merge function on the Status and merge to Status into one.

If you want to do some animations, you can trigger/request redraws with the shell until the animation is over.

mouse interaction

This method returns the current mouse Interaction.
A mouse interaction is mostly how the cursor is displayed. You often see your cursor changes when you are resizing or moving some element on the screen. If your widget, for example, has some area that can be grabbed and moved, you can return the Interaction::Move while the cursor is over that area to change the look of the cursor.

overlay

This method returns the overlay of the widget if there is any. A good example of this can be found in the form of the tooltip widget.

If you have child widgets it could look something like this:

fn overlay<'a>(
    &'a mut self,
    tree: &'a mut Tree,
    layout: Layout<'_>,
    renderer: &Renderer,
    translation: Vector,
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
    overlay::from_children(&mut self.children, tree, layout, renderer, translation)
}

Operations

Operations can query and update a widget state.

An operation provides specific functions defined in the Operation trait. These functions take the state of the widget and an identifier to locate the relevant state/widget. For common operations like focusable and text editing widgets there are fixed functions in the Operation trait. These functions require the states to implement unified traits such as Focusable and TextInput. If you want some custom operation that is unique to your widget, you can do that with the custom function of the Operation trait.

These functions are provided to the widgets, and it is their job to apply them in their operate function.

To launch/start/emit and Operation you will need to send a Task to the runtime. You can create the Task using the helper function operate.

Custom Operation

If you want to create a custom Operation it is convention to create a trait for it. All the widget states that the operation can be applied to should implement this trait.

In your implementation of the Operation trait, you will need to implement the custom method. This function takes a dynamic type Any that will be the state and an Id that identifies the widget state. You can downcast the state to a trait object that matches the previous defined trait that your state implements. After downcasting the state you can use the methods provided by your trait to modify the state.