Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The #[component] Macro

Note

This page is written purely based on observation of the behavior of the #[component] macro, and the documentation of it from the Relm4 book thus may not be complete or accurate.

The #[component] macro provides a convenient way to define UI using builder pattern.

#[relm4::component]
impl SimpleComponent for AppModel {
    type Init = AppInit; // The type of data with which this component will be initialized.
    type Input = AppMsg; // The type of the messages that this component can receive.
    type Output = ();    // The type of the messages that this component can send.

    view! {
        // The UI is defined here with `view!` macro syntax.
    }

    // Initialize the UI.
    fn init(
        init: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let model = AppModel { state: init };

        // Code generated by `view!{}` is inserted below.
        let widgets = view_output!();

        ComponentParts { model, widgets }
    }

    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
        match msg {
            // Handle messages and update state.
        }
    }
}

Modifiers

  • #[component(pub)] makes the component public, allowing it to be used outside of its module.

view!{}

view! {
    gtk::Window {
        set_title: Some("Simple app"),
        set_default_width: 300,
        set_default_height: 100,

        gtk::Box {
            set_orientation: gtk::Orientation::Vertical,
            set_spacing: 5,
            set_margin_all: 5,
        }
    }
}

would get translated into something like this:

use relm4::RelmContainerExt;

let gtk_window = gtk::Window::default();
gtk_window.set_title(Some("Simple app"));
gtk_window.set_default_width(300);
gtk_window.set_default_height(100);

let gtk_box = gtk::Box::default();
gtk_box.set_orientation(gtk::Orientation::Vertical);
gtk_box.set_spacing(5);
gtk_box.set_margin_all(5);

gtk_window.container_add(&gtk_box);

:, = or =>

  • : would dutifully puts everything behind it into argument slot of the function call. If the function call takes multiple arguments, it should be followed by a tuple.
  • = would first tries to build what is behind it using builder pattern, and then puts the result into argument slot of the function call.
  • => have two variants:
    • when followed by a closure, it would be directly put into the argument slot of the function call.
    • when followed by anything else, it would first be turned into a closure like move |_| { sender.input(/* ... */) }, and then put into the argument slot.
view! {
    gtk::Button {
        set_label: "Click me",
        connect_clicked => AppMsg::Clicked
    }
}
view! { /* Widget */ {
    // `&` makes it take reference instead of ownership.
    set_child = &adw::ToolbarView {
        add_top_bar = &adw::HeaderBar {},
    }
}}
view! {
    gtk::Window {
        // set title if it is `Some(T)`, does nothing if otherwise.
        set_title?: optional_title,
    }
}

Attributes

  • #[watch] wathches the variables referenced in the widget, so that the UI will be updated when they change.
  • #[wrap(Some)] would wrap the widget in another type, typically an Option<T>, only effective when using =
  • #[name(widget_name)] would name the widget as widget_name, so that it can be referenced later in the UI construction.

Info

The following items exists, but their usage and purpose are yet to be studied and documented.

  • #[transition = "SlideRight"]
  • #[root]
  • #[local_ref]
  • #[iterate]
  • #[block_signal(handler_name)]

Conditional Widgets

#![allow(unused)]
fn main() {
view! {
    if model.value % 2 == 0 {
        gtk::Label {
            set_label: "The value is even",
        },
        gtk::Label {
            set_label: "The value is odd",
        }
    }
}
view! {
    match model.value {
        0..=9 => {
            gtk::Label {
                set_label: "The value is below 10",
            },
        }
        _ => {
            gtk::Label {
                set_label: "The value is equal or above 10",
            },
        }
    }
}
}

Advanced Syntaxes

Inline Function Initialization

#![allow(unused)]
fn main() {
view! { /* Widget */ {
    set_property_name = new_box() -> gtk::Box { ... }
}}
}

Additional Tailing Arguments

#![allow(unused)]
fn main() {
view! {
    gtk::Grid {
        attach[0, 0, 1, 2] = &gtk::Label { ... }
        // grid.attach(label, 0, 0, 1, 2)
    }
}
}

Naming Temporary Widgets

#![allow(unused)]
fn main() {
view! { /* Widget */ {
    set_child = &adw::ToolbarView {
        // Will name this widget `headerbar`.
        add_top_bar: headerbar = &adw::HeaderBar {},
        set_content: Some(&stack),
    }
}}
}

Accessing Returned Widgets

#![allow(unused)]
fn main() {
view! {
    gtk::Stack {
        add_child = &gtk::Label {
            set_label: "placeholder",
        } -> page: gtk::StackPage { // it can also be named (must specify type)
            // Access the returned widgets (in this case gtk::StackPage)
            set_title: "page title",
        }
    }
}
}

Clone for Signal Handlers

#![allow(unused)]
fn main() {
view! {
    // Clone with same name.
    connect_name[cloned_var1, cloned_var2] => move |arg1, arg2| {
        /* ... */
    },
    // Clone and rename.
    connect_name[sender = components.sender.clone()] => move |/* ... */| {
        /* ... */
    },
}
}

additional_fields!{}

This macro adds additional fields to the generated widget struct.

#[relm4::component]
// ...
    additional_fields! {
        stack: gtk::Stack,
    }

    fn init(
        init: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let counter = CounterModel::builder()
            .launch(init.0)
            .forward(sender.input_sender(), identity);
        let toggler = TogglerModel::builder()
            .launch(init.1)
            .forward(sender.input_sender(), identity);

        view! {
            stack = gtk::Stack {
                add_titled: (counter.widget(), None, "Counter"),
                add_titled: (toggler.widget(), None, "Toggle"),
                set_vhomogeneous: false,
            }
        } // produces a `gtk::Stack` widget named `stack`

        let model = AppModel { counter, toggler };
        let widgets = view_output!(); // picks up the `stack` widget

        let split_view = widgets.split_view.clone();
        widgets.stack.connect_visible_child_notify(move |_| {
            split_view.set_show_content(true);
        }); // can later be accessed

        ComponentParts { model, widgets }
    }
// ...

Info

This thing exists, but its usage and purpose are yet to be studied and documented.

pre_view() and post_view()

Info

This thing exists, but its usage and purpose are yet to be studied and documented.