The component
macro reference
There are quite a few of examples where the component
macro is used in this book. Still, we haven't covered everything in the previous chapters, and it's also nice to have all the information in one place. This chapter serves as an exhaustive reference for the component macro syntax.
The component
attribute macro expects a trait implementation of SimpleComponent
, Component
, or FactoryComponent
on a user-provided struct
that holds the component's state.
Public widgets
If you want to make the Widgets
struct generated by the macro public, you can provide pub
(or any other visibility) as an argument to the attribute macro.
#[relm4::component(pub)]
Nested view!
macro
The nested view!
macro allows us to easily define widgets and mutate their properties.
Constructing widgets
All components must have a root widget in their view!
macro, such as gtk::Window
or gtk::Box
. Widgets are constructed by providing the type of the widget followed by curly braces. This will construct the widget using its Default
implementation.
view! {
gtk::Window {
...
}
}
Sometimes, there's no Default
implementation for a widget, or it may be more convenient to construct it manually. If the function is associated with a type, you can use this syntax:
gtk::Label::new(Some("Label from constructor")),
// ...
gtk::Label::builder()
.label("Label from builder")
.selectable(true)
.build(),
Otherwise, you can provide the type:
set_property_name = new_box() -> gtk::Box { ... }
Child widgets
Child widgets are added by nesting the declarations. This will automatically pass the child widget to set_child
on the parent:
view! {
gtk::Window {
gtk::Box {
}
}
}
If another method is needed to assign a child, you can call it instead like this:
gtk::Box {
append = >k::Label { ... }
}
Use &
in front of the widget type to assign a reference.
A common mistake is to accidentally use
:
instead of=
for assigning widgets.
If the widget needs to be wrapped in another type (commonly Option
), use the wrap
attribute:
#[wrap(Some)]
set_property_name = gtk::Box { ... }
Sometimes you need to pass additional arguments along with the widget, for example when calling gtk::Grid::attach
. You can do this by providing the additional arguments in square brackets after the method:
set_property_name[value1, value2, ...] = gtk::Box { ... }
This will expand to parent.set_property_name(box_widget, value1, value2, ...)
.
Naming widgets
Widgets may be given a name with the name
attribute. These names are accessible as fields on the Widgets
struct generated by the macro.
#[name = "important_label"]
gtk::Label { ... }
// ...
let widgets = view_output!();
let label = &widgets.important_label;
Names can also be assigned with this syntax:
set_child: important_label = gtk::Label { ... }
Returned widgets
Sometimes, methods used for assigning widgets return another widget.
For example, gtk::Stack::add_child()
allows you to add a widget to the stack, but also returns a gtk::StackPage
widget.
To get access to this widget, you can use a special syntax of the view macro:
gtk::Stack {
add_child = >k::Label {
set_label: "placeholder",
} -> {
// Set the title of the stack page
set_title: "page title",
}
}
Properties
Properties are initialized and mutated by calling methods within the widget types. Check the documentation for each widget type to see what methods are available. Generally properties are set via setter methods, but any methods on the widget can also be called. Many of these methods are part of an extension trait associated with the widget type. These traits must be in scope to call their methods. For example, if you want to use the set_default_width
method from the GtkWindowExt
trait, you must import the trait directly or glob import it from the prelude (use gtk::prelude::*;
).
To initialize a property with a value:
set_property_name: value,
Initialize a property only if its value is Some
, and do nothing if it's None
:
set_property_name?: value,
Call a method that has multiple arguments:
set_property_name: (value1, value2, ...),
Initialize and automatically update a property.
#[watch]
set_property_name: (value1, value2, ...),
Initialize and automatically update a property with a tracker. The track_expression
can be any expression that returns a bool
. If it's true, it indicates that the method should be called:
#[track(track_expression)]
set_property_name: (value1, value2, ...),
Initialize a property by iterating over an iterator. You can use this for repeated calls to setter functions, like add_class_name
in case you have multiple class names in a Vec
.
#[iterate]
set_property_name: iterator,
Trait disambiguation
It is possible that several traits implement the same method for a type. If both traits are in scope, when using the duplicated method name we need to tell Rust which trait implementation it should use. Otherwise, the Rust compiler will raise an error due to the ambiguity. To specify the intended trait, use the TraitName::method
syntax, similar to Rust's fully qualified syntax for trait disambiguation.
You can also use the full path of the trait if desired.
Signals
When connecting signals emitted by widgets you can clone fields that you need in the closure (for example, the component sender) by listing the corresponding fields in square brackets.
connect_name[cloned_var1, cloned_var2, ...] => move |arg1, arg2, ...| { ... }
Manual code
Sometimes the macro isn't flexible enough. In this case, you can always use manual code that will not be modified by the macro.
Here's a list of all the options available.
#[relm4_macros::component]
impl SimpleComponent for App {
// ...
view! {
// ...
}
additional_fields! {
// ...
}
fn pre_view() {
// ...
}
fn post_view() {
// ...
}
}
Add more fields to your widgets
The widgets struct is automatically generated by the macro, but sometimes you want to add your own fields.
To do so, use the additional_fields!
macro:
additional_fields! {
test: u8,
}
Initialize the variable in the init
function by naming a local variable like your custom field.
let test = 0;
let widgets = view_output!();
Manual view
You can also implement your own view logic, which will be added to the view code that the view macro generates. Code inside pre_view()
will run before the view update, while post_view()
will run after.
Code inside these "functions" isn't like a normal function! The macro disallows returning early in
pre_view
to ensure that the view update will always execute.