Get started

Let's get started with a simple project, which we'll get to know the basics of Floem.

Create a new Rust project

cargo new floem-app

Adding Floem dependency

cd floem-app
cargo add floem

Hello World

In your main.rs, put the following

fn main() {
    floem::launch(|| "Hello, World!");
}

And then, cargo run will give you your first hello world in a Floem window.

That looks simple, isn't it? If you look at the definition of floem::launch, you'll find that it takes a closure which returns a IntoView. If you're familiar with Rust, IntoView is a trait with method into_view where you can convert any type that implements the trait to a Floem View. A Floem View is the core of Floem, where it can be a text label, a button, a text input, or a scroll etc. And Floem implements IntoView for &str type internally, so we can directly return "Hello, World!" as a IntoView.

Now let's dive in to a more complex example, a counter app.

Counter App

We'll create a very simple counter, where it shows the current counter value, a increment button, a decrement button.

In main.rs, put the following.

use floem::{views::button, IntoView};

fn app_view() -> impl IntoView {
    (
        "Value: ",
        button("Increment"),
        button("Decrement"),
    )
}

fn main() {
    floem::launch(app_view);
}

Run this, and you should be able to see "Value: " and two giant buttons for "Increment" and "Decrement".

Let's improve it by giving our example a bit of "style"

use floem::{
    views::{button, Decorators},
    IntoView,
};

fn app_view() -> impl IntoView {
    (
        "Value: ",
        (button("Increment"), button("Decrement")).style(|s| s.flex_row().gap(6)),
    ).style(|s| s.flex_col().gap(6).items_center())
}

fn main() {
    floem::launch(app_view);
}

If you run it again, you should see it should be properly laid out. And in Floem, the views are styled by style method, where it's provided by floem::views::Decorators to any type that's a IntoView type. So remember to import the trait first otherwise Rust compiler will shout at you.

Floem style is a Rust implementation of web Flexbox via the Taffy crate. So you should find lots of similar things if you're already familiar with Flexbox. If you're not, there're lots of guides and tutorials online that talk about Flexbox, and they should be transferrable to Floem styles.

Reactivity

But it doesn't do anything when you click the buttons. That's because there's no value that's been changed or displayed yet. How do you achieve reactivity in Floem?

Signal will be the primitive for reactivity in Floem. If you're familiar with Solidjs or Leptos, that would be similar to the signal in those frameworks. Simply put, if you update the value of a Signal, anything that "listens" to signal will get notified and trigger an action based on the update. In our counter example, we want the counter value gets updated in the UI. So let's create a signal.

use floem::{
    prelude::create_rw_signal,
    views::{button, dyn_view, Decorators},
    IntoView,
};

fn app_view() -> impl IntoView {
    let counter = create_rw_signal(0);
    (
        dyn_view(move || format!("Value: {}", counter)),
        (button("Increment"), button("Decrement")).style(|s| s.flex_row().gap(6)),
    )
        .style(|s| s.flex_col().gap(6).items_center())
}

fn main() {
    floem::launch(app_view);
}

The first thing you'll notice is we created a counter signal. And we wrapped up the value display in a dyn_view with a closure which formats the counter value. You might want to ask why not simply a format!("Value: {}", counter) since String should impl IntoView. Well, that will compile without errors, but we loose the reactivity if we do that way.

Let me explain.

Unlike frameworks like Reactjs, the view creation function is only called once. So format!("Value: {}", counter) will only be called once too, and it will take the initial value of counter which is 0. Even when we update the value of counter signal later in the application, the Value: 0 won't get updated because we won't run that again.

So reactivity in Floem relies on closures, and we will run the closure again and again whenever the signal gets updated. And that provides the characteristic of "fine grained reactivity" in Floem, where the UI doesn't need to do a diff with the previous state, but can only update what needs to be updated, no matter how deep a particular view is at in the view tree. Assume the counter value display in our example is deeply at a nested place, we can still "directly" update the UI, and only that text label part of the UI, without traverse the view tree from the root.

And that gives a basic rule of reactivity in Floem. If you want something to be updated, you'll need to use a closure. So if you ever encounter a bug using Floem where something doesn't get updated, the first thing you'll need to check is "is it a closure"?

Action

But still the buttons don't do anything. That's because we haven't let the button click to update the counter signal yet. And if you do this

use floem::{
    prelude::create_rw_signal,
    views::{button, dyn_view, Decorators},
    IntoView,
};

fn app_view() -> impl IntoView {
    let mut counter = create_rw_signal(0);
    (
        dyn_view(move || format!("Value: {}", counter)),
        (
            button("Increment").action(move || counter += 1),
            button("Decrement").action(move || counter -= 1),
        )
            .style(|s| s.flex_row().gap(6)),
    )
        .style(|s| s.flex_col().gap(6).items_center())
}

fn main() {
    floem::launch(app_view);
}

So we simply let action method on the buttons to increment or decrement the value of counter signal. Now you should have a working counter example.

This will conclude our getting start tutorial. To learn more about Floem, you can check out the examples in our repo, or read the API docs.

Last updated