The #[component]
Macro
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(>k_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 anOption<T>
, only effective when using=
#[name(widget_name)]
would name the widget aswidget_name
, so that it can be referenced later in the UI construction.
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] = >k::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 = >k::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 }
}
// ...