Welcome

Freya is work in progress cross-platform native GUI library for đŸĻ€ Rust, built on top of đŸ§Ŧ Dioxus and 🎨 Skia as graphics library.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    let mut count = use_state(cx, || 0);

    render!(
        rect {
            height: "20%",
            width: "100%",
            background: "rgb(233, 196, 106)",
            padding: "12",
            color: "rgb(20, 33, 61)",
            label { 
                font_size: "20", 
                "Number is: {count}"
            }
        }
        rect {
            height: "80%",
            width: "100%",
            background: "rgb(168, 218, 220)",
            color: "black",
            padding: "12",
            onclick: move |_| count += 1,
            label { "Click to increase!" }
        }
    )
}
}

Freya

Check out the examples in the Freya repository to learn more.

What is Freya?

Freya is native GUI library built on top of đŸ§Ŧ Dioxus's core, it also uses 🎨 Skia as a graphics library, for đŸĻ€ Rust.

Features

  • ⛏ī¸ Built-in components and hooks library
  • 🔍 Integrated devtools
  • 🧰 Built-in testing runner for components.
  • 🛩ī¸ Cross-platform (Windows, Linux, MacOS)
  • đŸĨ¸ Easy to use
  • đŸ–ŧī¸ Shaders support
  • 🔄ī¸ Hot-reload
  • 📒 Multi line text editing

Why đŸ§Ŧ Dioxus?

Dioxus is a React-like library for Rust. Its component and hooks model make it simple to use and scales to complex apps.

See it's differences with Freya.

Why 🎨 Skia?

Skia is a battle-tested and well-maintained graphics library, and there are even some rusty bindings.

Differences with Dioxus

Freya uses most of the core packages of Dioxus, but not all them.

These are the main differences:

  • Freya has it's own elements, attributes and events, instead of HTML.
  • Freya uses it's own UI layout library called torin, instead of taffy.
  • Freya uses Skia, instead of webview or other graphics libraries used by Dioxus.
  • Freya comes with it's own set of components and hooks, on the other hand Dioxus web/desktop can take advantage of existing CSS libraries and HTML elements.
  • Freya has an integrated devtools panel, dioxus web/desktop already have the browser/webview devtools.
  • Freya comes with a headless testing runner.

Environment Setup

Make sure you have Rust and your OS dependencies installed.

Windows

You will need C++ build tools which you can get through Visual Studio 2022, learn more here.

Linux

Debian-based (Ubuntu, PopOS, etc)

Install these packages:

sudo apt install build-essential libssl-dev pkg-config cmake libgtk-3-dev libclang-dev

Don't hesitate to contribute so other distros can be added here.

MacOS

No setup required. But feel free to add more if we miss something.

Getting started

I encourage you to learn how Dioxus works, when you are done you can continue here. Also make sure you have the followed the environment setup guide.

Now, let's start by creating a hello world project.

Creating the project

mkdir freya-app
cd freya-app
cargo init

Cargo.toml

Make sure to add Freya and Dioxus as dependencies:

[package]
name = "freya-app"
version = "0.1.0"
edition = "2021"

[dependencies]
freya = { git = "https://github.com/marc2332/freya" }
dioxus = { git = "https://github.com/DioxusLabs/dioxus", rev="f69b84bf34114c68749e956f26ed61db38079f7c", features = ["macro", "hooks"]}

src/main.rs

And paste this code in your main.rs file.

#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use freya::prelude::*;

fn main() {
    launch(app);
}

fn app(cx: Scope) -> Element {
    let mut count = use_state(cx, || 0);

    render!(
        rect {
            overflow: "clip",
            height: "100%",
            width: "100%",
            background: "rgb(35, 35, 35)",
            color: "white",
            padding: "12",
            onclick: move |_| count += 1,
            label { "Click to increase -> {count}" }
        }
    )
}

Running

Simply run with cargo:

cargo run

Elements

Freya contains a set of primitive elements:

rect

The rect element (aka rectangle) is a box where you can place as many elements inside you want. You can specify things like width, paddings or even in what direction the inner elements are stacked.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            direction: "vertical",
            label { "Hi!" }
            label { "Hi again!"}
        }
    )
}
}

label

The label element simply shows some text.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            "Hello World"
        }
    )
}
}

svg

The svg element let's you draw a SVG. You will need to use the bytes_to_data to transform the bytes into data the element can recognize.

Example:

#![allow(unused)]

fn main() {
static FERRIS: &[u8] = include_bytes!("./ferris.svg");

fn app(cx: Scope) -> Element {
    let ferris = bytes_to_data(cx, FERRIS);
    render!(
        svg {
            svg_data: ferris,
        }
    )
}
}

image

The image element, just like svg element, require you to pass the image bytes yourself.

#![allow(unused)]
fn main() {
static RUST_LOGO: &[u8] = include_bytes!("./rust_logo.png");

fn app(cx: Scope) -> Element {
    let image_data = bytes_to_data(cx, RUST_LOGO);
    render!(
        image {
            image_data: image_data,
            width: "{size}",
            height: "{size}",
        }
    )
}
}

paragraph and text

Both paragraph and text elements are used together. They will let you build texts with different styles.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        paragraph {
            text {
                font_size: "15",
                "Hello, "
            }
            text {
                font_size: "30",
                "World!"
            }
        }
    )
}
}

Layout

Learn how the layout attributes work.

⚠ī¸ Freya's layout is still somewhat limited.

width & height

All elements support both width and height attributes.

See syntax for Size Units.

Usage
#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            background: "red",
            width: "15",
            height: "50",
        }
    )
}
}

min_width & min_height

rect supports specifying a minimum width and height, this can be useful if you use it alongside a percentage for the target size.

See syntax for Size Units.

Usage
#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            background: "red",
            min_width: "100",
            min_height: "100",
            width: "50%",
            height: "50%",
        }
    )
}
}

max_width & max_height

rect supports specifying a maximum width and height.

See syntax for Size Units.

Usage
#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            background: "red",
            max_width: "50%",
            max_height: "50%",
            width: "500",
            height: "500",
        }
    )
}
}

Size Units

Logical pixels

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            width: "50",
            height: "33"
        }
    )
}
}

Percentages

Relative percentage to the parent equivalent value.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            width: "50%", // Half the parent
            height: "75%" // 3/4 the parent
        }
    )
}
}

calc()

For more complex logic you can use the calc() function.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            width: "calc(33% - 60 + 15%)", // (1/3 of the parent minus 60) plus 15% of parent
            height: "calc(100% - 10)" // 100% of the parent minus 10
        }
    )
}
}

direction

Control how the inner elements will be stacked, possible values are horizontal, vertical (default) or both (default for text elements, e.g label, paragraph, text, etc).

Usage
#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            width: "100%",
            height: "100%",
            direction: "vertical",
            rect {
                width: "100%",
                height: "50%",
                background: "red"
            },
            rect {
                width: "100%",
                height: "50%",
                background: "green"
            }
        }
    )
}
}

padding

Specify the inner paddings of an element. You can do so by three different ways, just like in CSS.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            padding: "25" // 25 in all sides
            padding: "100 50" // 100 in top and bottom, and 50 in left and right
            padding: "5 7 3 9" // 5 in top, 7 in right, 3 in bottom and 9 in left
        }
    )
}

}

display

Control how the inner elements are displayed, possible values are normal (default) or center.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            width: "100%",
            height: "100%",
            direction: "both",
            display: "center",
            rect {
                width: "50%",
                height: "50%",
                background: "red"
            },
        }
    )
}
}

margin

Specify the margin of an element. You can do so by three different ways, just like in CSS.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            margin: "25" // 25 in all sides
            margin: "100 50" // 100 in top and bottom, and 50 in left and right
            margin: "5 7 3 9" // 5 in top, 7 in right, 3 in bottom and 9 in left
        }
    )
}
}

Style

Learn how the style attributes work.

background

The background attribute will let you specify a color as the background of the element.

You can learn about the syntax of this attribute here.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            background: "red"
        }
    )
}
}

Compatible elements: rect

shadow

The shadow attribute let's you draw a shadow outside of the element.

Syntax: <x> <y> <intensity> <size> <color>

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            shadow: "0 0 25 2 rgb(0, 0, 0, 120)"
        }
    )
}
}

Compatible elements: rect

corner_radius & corner_smoothing

The corner_radius attribute let's you smooth the corners of the element, with corner_smoothing you can archieve a "squircle" effect.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            corner_radius: "10",
            corner_smoothing: "75%"
        }
    )
}
}

Compatible elements: rect

border

You can add a border to an element using the border and border_align attributes.

  • border syntax: [width] <solid | none> [color].
  • border_align syntax: <inner | outer | center>.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            border: "2 solid black",
            border_align: "inner"
        }
    )
}
}

Compatible elements: rect

overflow

Specify how overflow should be handled.

Accepted values: clip | none.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            overflow: "clip"
            width: "100",
            height: "100%",
            rect {
                width: "500",
                height: "100%",
                background: "red",
            }
        }
    )
}
}

Compatible elements: rect

Color syntax

The attributes that have colors as values can use the following syntax:

Static colors

  • rect
  • blue
  • green
  • yellow
  • black (default for color attribute)
  • gray
  • white (default for background attribute)
  • orange
  • transparent

rgb() / hsl()

  • With RGB: rgb(150, 60, 20)
  • With RGB and alpha: rgb(150, 60, 20, 70)
  • With HSL: hsl(28deg, 80%, 50%)
  • With HSL and alpha: hsl(28deg, 80%, 50%, 25%)

Inheritance

These are some attribute that are inherited from the element parents:

  • color
  • font_family
  • font_size
  • font_style
  • font_weight
  • font_width
  • line_height
  • align
  • max_lines
  • letter_spacing
  • word_spacing
  • decoration
  • decoration_style
  • decoration_color
  • text_shadow

Font Style

Learn how the font style attributes work.

color

The color attribute let's you specify the color of the text.

You can learn about the syntax of this attribute in Color Syntax.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            color: "green",
            "Hello, World!"
        }
    )
}
}

Another example showing inheritance:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        rect {
            color: "blue",
            label {
                "Hello, World!"
            }
        }
    )
}

}

Compatible elements: label, paragraph, text

font_family

With the font_family you can specify what font do you want to use for the inner text.

Limitation: Only fonts installed in the system are supported for now.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            font_family: "Inter",
            "Hello, World!"
        }
    )
}
}

Compatible elements: label, paragraph,

font_size

You can specify the size of the text using font_size.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            font_size: "50",
            "Hellooooo!"
        }
    )
}
}

Compatible elements: label, paragraph, text

align

You can change the alignment of the text using the align attribute.

Accepted values: center, end, justify, left, right, start

Example

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            align: "right",
            "Hello, World!"
        }
    )
}
}

Compatible elements: label, paragraph,

font_style

You can choose a style for a text using the font_style attribute.

Accepted values: upright (default), italic and oblique.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            font_style: "italic",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, label.

font_weight

You can choose a weight for a text using the font_weight attribute.

Accepted values:

  • invisible
  • thin
  • extra-light
  • light
  • normal (default)
  • medium
  • semi-bold
  • bold
  • extra-bold
  • black
  • extra-black
  • 50
  • 100
  • 200
  • 300
  • 400
  • 500
  • 600
  • 700
  • 800
  • 900
  • 950

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            font_weight: "bold",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, label.

font_width

You can choose a width for a text using the font_width attribute.

Accepted values:

  • ultra-condensed
  • extra-condensed
  • condensed
  • normal (default)
  • semi-expanded
  • expanded
  • extra-expanded
  • ultra-expanded

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            font_weight: "bold",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, label.

line_height

Specify the height of the lines of the text.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            lines_height: "3",
            "Hello, World! \n Hello, again!"
        }
    )
}
}

max_lines

Determines the amount of lines that the text can have. It has unlimited lines by default.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            "Hello, World! \n Hello, World! \n Hello, world!" // Will show all three lines
        }
        label {
            lines_height: "2",
            "Hello, World! \n Hello, World! \n Hello, world!" // Will only show two lines
        }
    )
}
}

Compatible elements: text, paragraph.

letter_spacing

Specify the spacing between characters of the text.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            letter_spacing: "10",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, paragraph, label.

word_spacing

Specify the spacing between words of the text.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            word_spacing: "10",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, paragraph, label.

decoration

Specify the decoration in a text.

Accpted values:

  • underline
  • line-through
  • overline

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            decoration: "line-through",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, paragraph, label.

decoration_style

Specify the decoration's style in a text.

Accpted values:

  • solid (default)
  • double
  • dotted
  • dashed
  • wavy

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            decoration: "line-through",
            decoration_style: "dotted",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, paragraph, label.

decoration_color

Specify the decoration's color in a text.

You can learn about the syntax of this attribute in Color Syntax.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            decoration: "line-through",
            decoration_color: "orange",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, paragraph, label.

text_shadow

Specify the shadow of a text.

Syntax: <x> <y> <size> <color>

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            text_shadow: "0 18 12 rgb(0, 0, 0)",
            "Hello, World!"
        }
    )
}
}

Compatible elements: text, label.

text_overflow

Determines how text is treated when it exceeds its max_lines count. By default uses the clip mode, which will cut off any overflowing text, with ellipsis mode it will show ... at the end.

Accepted values:

  • clip (default)
  • ellipsis

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            max_lines: "3",
            text_overflow: "ellipsis",
            "Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong text"
        }
    )
}
}

Compatible elements: label, paragraph.

Effects

Learn how the effects attributes work.

rotate

The rotate attribute let's you rotate an element.

Example:

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        label {
            rotate: "180deg",
            "Hello, World!"
        }
    )
}
}

Compatible elements: all except text.

Components

Freya comes with a set of ready to use components:

Theming

Freya has built-in support for Theming.

⚠ī¸ Currently, extending the base theme is not supported.

Accessing the current theme

You can access the whole current theme via the use_get_theme hook.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        ThemeProvider {
            Component { }
        }
    )
}

#[allow(non_snake_case)]
fn Component(cx: Scope) -> Element {
    let theme = use_get_theme(cx);

    let button_theme = &theme.button;

    render!(
        rect {
            background: "{button_theme.background}",
        }
    )
}
}

Custom default theme

By default, the selected theme is DARK_THEME. You use the alternative, LIGHT_THEME or any you want.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        ThemeProvider {
            theme: LIGHT_THEME,
            Component { }
        }
    )
}

#[allow(non_snake_case)]
fn Component(cx: Scope) -> Element {
    let theme = use_get_theme(cx);

    let button_theme = &theme.button;

    render!(
        rect {
            background: "{button_theme.background}",
        }
    )
}
}

Change theme

Changing the selected theme at runtime is possible by using the use_theme hook.

#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    render!(
        ThemeProvider {
            Component { }
        }
    )
}

#[allow(non_snake_case)]
fn Component(cx: Scope) -> Element {
    let theme = use_theme(cx);

    let onclick = |_| {
        *theme.write() = LIGHT_THEME;
    };

    render!(
        Button {
            onclick: onclick,
            label {
                "Use Light theme"
            }
        }
    )
}
}

Custom theme

Themes can be built from scratch or extended from others, like here with LIGHT_THEME:

#![allow(unused)]

fn main() {
const CUSTOM_THEME: Theme = Theme {
    button: ButtonTheme {
        background: "rgb(230, 0, 0)",
        hover_background: "rgb(150, 0, 0)",
        font_theme: FontTheme { color:  "white" }
    },
    ..LIGHT_THEME
};

fn app(cx: Scope) -> Element {
    render!(
        ThemeProvider {
            theme: CUSTOM_THEME,
            rect {
                width: "100%",
                height: "100%",
                Button {
                    label {
                        "Report"
                    }
                }
            }
        }
    )
}
}

Hot reload

Freya supports Dioxus hot reload, this means you can update the layout and styling of your app on the fly, without having to compile any rust code.

Setup

Just before launching your app, you need to initialize the hot-reload context:

fn main() {

    dioxus_hot_reload::hot_reload_init!(Config::<FreyaCtx>::default());

    launch(app);
}

That's it!

Testing

Freya comes with a special testing renderer (freya-testing) that let's you run your components in a headless environment. This will let you easily write unit tests for your components.

Getting started

You can use the launch_test function to run the tests of your component, it will return you a set of utilities for you to interact with the component.

For example, this will launch a state-less component and assert that it renders a label with the text "Hello World!".

#![allow(unused)]
fn main() {
#[tokio::test]
async fn test() {
    fn our_component(cx: Scope) -> Element {
        render!(
            label {
                "Hello World!"
            }
        )
    }

    let mut utils = launch_test(our_component);

    let root = utils.root();
    let label = root.get(0);
    let label_text = label.get(0);

    assert_eq!(label_text.text(), Some("Hello World!"));
}
}

The root() function will give you the Root node of your app, then, with the get function you can retrieve a Node from it's parent given it's index position.

Dynamic components

If the component has logic that might execute asynchronously, you will need to wait for the component to update using the wait_for_update function before asserting the result.

Here, the component has a state that is false by default, but, once mounted it will update the state to true.

#![allow(unused)]
fn main() {
#[tokio::test]
async fn dynamic_test() {
    fn dynamic_component(cx: Scope) -> Element {
        let state = use_state(cx, || false);
        let state_setter = state.setter();

        use_effect(cx, (), move |_| async move {
            state_setter(true);
        });

        render!(
            label {
                "Is enabled? {state}"
            }
        )
    }

    let mut utils = launch_test(dynamic_component);

    let root = utils.root();
    let label = root.get(0);

    assert_eq!(label.get(0).text(), Some("Is enabled? false"));

    // This will run the `use_effect` and update the state.
    utils.wait_for_update().await;

    assert_eq!(label.get(0).text(), Some("Is enabled? true"));
}
}

Events

We can also simulate events on the component, for example, we can simulate a click event on a rect and assert that the state has been updated.

#![allow(unused)]
fn main() {
#[tokio::test]
async fn event_test() {
    fn event_component(cx: Scope) -> Element {
        let enabled = use_state(cx, || false);
        render!(
            rect {
                width: "100%",
                height: "100%",
                background: "red",
                direction: "both",
                onclick: |_| {
                    enabled.set(true);
                },
                label {
                    "Is enabled? {enabled}"
                }
            }
        )
    }

    let mut utils = launch_test(event_component);

    let rect = utils.root().get(0);
    let label = rect.get(0);

    utils.wait_for_update().await;

    let text = label.get(0);
    assert_eq!(text.text(), Some("Is enabled? false"));

    // Push a click event to the events queue
    utils.push_event(FreyaEvent::Mouse {
        name: "click",
        cursor: (5.0, 5.0).into(),
        button: Some(MouseButton::Left),
    });

    // Run the queued events and update the state
    utils.wait_for_update().await;

    // Because the click event was executed and the state updated, now the text has changed too!
    let text = label.get(0);
    assert_eq!(text.text(), Some("Is enabled? true"));
}
}

Testing configuration

The launch_test comes with a default configuration, but you can also pass your own with the launch_test_with_config function.

Here is an example of how to can set our custom window size:

#![allow(unused)]
fn main() {
#[tokio::test]
async fn test() {
    fn our_component(cx: Scope) -> Element {
        render!(
            label {
                "Hello World!"
            }
        )
    }

    let mut utils = launch_test_with_config(
        our_component,
        TestingConfig::default().with_size((500.0, 800.0).into()),
    );

    let root = utils.root();
    let label = root.get(0);
    let label_text = label.get(0);

    assert_eq!(label_text.text(), Some("Hello World!"));
}
}

Animating

Freya provides you with two hooks to help you animate your components.

use_animation

This a very simple hook that will let you animate a certain value from an inital value to a final value, in a given duration of time. There are a few animations that you can choose from:

  • Linear
  • EaseIn
  • EaseInOut
  • BounceIns

Here is an example that will animate a value from 0.0 to 100.0 in 50 milliseconds, using the linear animation.

fn main() {
    launch(app);
}

 fn app(cx: Scope) -> Element {
    let animation = use_animation(cx, 0.0);

    let progress = animation.value();

    use_effect(cx, (), move |_| {
        animation.start(Animation::new_linear(0.0..=100.0, 50));
        async move {}
    });

    render!(rect {
        width: "{progress}",
    })
}

use_animation_transition

This hook let's you group a set of animations together with a certain type of animation and a given duration. You can also specifiy a set of dependencies that will make animations callback re run.

Just like use_animation you have these animations:

  • Linear
  • EaseIn
  • EaseInOut
  • BounceIns

Here is an example that will animate a size and a color in 200 milliseconds, using the new_sine_in_out animation.

fn main() {
    launch(app);
}

const TARGET: f64 = 500.0;

fn app(cx: Scope) -> Element {
    let animation = use_animation_transition(cx, TransitionAnimation::new_sine_in_out(200), (), || {
        vec![
            Animate::new_size(0.0, TARGET),
            Animate::new_color("rgb(33, 158, 188)", "white"),
        ]
    });

    let size = animation.get(0).unwrap().as_size();
    let background = animation.get(1).unwrap().as_color();

    let onclick = move |_: MouseEvent| {
        if size == 0.0 {
            animation.start();
        } else if size == TARGET {
            animation.reverse();
        }
    };

    render!(
        rect {
            overflow: "clip",
            background: "black",
            width: "100%",
            height: "100%",
            offset_x: "{size}",
            rect {
                height: "100%",
                width: "200",
                background: "{background}",
                onclick: onclick,
            }
        }
    )
}

Virtualizing

Virtualizing helps you render a lot of data efficiently. It will only mount the elements you see in the screen, no matter how big the data is.

Target usages

  • Text editor
  • Tables
  • Etc

Usage

Freya comes with a VirtualScrollView component which can help you archive the virtualization of some data.

The virtualization logic of VirtualScrollView is implemented at component-level, so, you could implement your own version if you wanted.

Here is an example:

fn main() {
    launch(app);
}

fn app(cx: Scope) -> Element {
    let values = use_state(cx, || vec!["Hello World"].repeat(400));

    render!(
        VirtualScrollView {
            width: "100%",
            height: "100%",
            show_scrollbar: true,
            direction: "vertical",
            length: values.get().len(),
            item_size: 25.0,
            builder_values: values.get(),
            builder: Box::new(move |(key, index, _cx, values)| {
                let values = values.unwrap();
                let value = values[index];
                rsx! {
                    label {
                        key: "{key}",
                        height: "25",
                        "{index} {value}"
                    }
                }
            })
        }
    )
}

alt text

Parameters

show_scrollbar

By default, it does not display a scrollbar. However, you can enable it by setting the show_scrollbar parameter to true.

direction

It supports both vertical and horizontal directions. If direction is set to vertical, the items will be displayed in a single column, with the scrollbar appearing on the right-hand side. If direction is set to horizontal, the items will be displayed in a single row, with the scrollbar appearing at the bottom.

length

How many elements can be rendered. Usually the lenth of your data.

item_size

Used to calculate how many elements can be fit in the viewport.

builder_values

Any data that you might need in the builder function

builder

This is a function that dinamically creates an element for the given index in the list. It receives 4 arguments, a key for the element, the index of the element, the Scope (cx) and the builder_values you previously passed.