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(>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.
[!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
```rust
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 }
}
// ...
menu!{}
[!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.