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)]
The view!
macro
The 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 {
...
}
}
Some, widgets don't have a Default
implementation or it may be more convenient to use a constructor method.
In this case, you can use the following syntax:
// Constructor method
gtk::Label::new(Some("Label from constructor method")) { /* ... */ }
// Builder pattern
gtk::Label::builder()
.label("Label from builder pattern")
.selectable(true)
.build() { /* ... */ }
You can also use regular functions. Because macro often needs to know the type of the widget for code generation, you may need to specify it.
set_property_name = new_box() -> gtk::Box { ... }
Child widgets
Child widgets are added by nesting the declarations.
view! {
gtk::Window {
gtk::Box {
}
}
}
If another method is needed to assign a child, you can call it instead like this:
gtk::Box {
// Use append
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:
gtk::Grid {
// Attach the label to a grid
attach[0, 0, 1, 2]= >k::Label { ... }
}
This will expand to __grid.attach(__label, 0, 0, 1, 2)
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 { ... }
Conditional widgets
The view
macro allows you to include if
and match
statements for conditionally showing widgets.
Internally, the macro will use a gtk::Stack
, so you can also use different transition types.
if model.value % 2 == 0 {
gtk::Label {
set_label: "The value is even",
},
gtk::Label {
set_label: "The value is odd",
}
}
// Use a transition type to set an animation when the visible widget changes
#[transition = "SlideRight"]
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",
},
}
}
If your conditional widget uses a match
statement over an enum, you can destructure the enum in the match
arms to access its variables with the help of the #[track]
or #[watch]
macros:
enum Foo {
Bar(f32),
Baz(String),
}
struct FooView {
foo: Foo
}
impl Component for FooView {
type Init = Foo;
// snip
view! {
#[root]
gtk::Box {
append = match &model.foo {
Foo::Bar(num_value) => {
gtk::SpinButton {
// adding the `watch` macro lets you reference the destructured variables
#[watch]
set_value: num_value
}
}
Foo::Baz(str_value) => {
gtk::Label {
#[watch]
set_text: &str_value
}
}
}
}
}
// snip
}
Please note: if you attempt to destructure in the normal way - without the track
or watch
macros - you will get a compilation error, and Rust will 'fail to see' the destructured variables at the point where your code uses them. This is due to limitations in Relm4's component initialization strategy. Please ensure that you use one of those macros to avoid this.
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",
} -> {
// Access the returned widgets (in this case gtk::StackPage)
set_title: "page title",
}
}
The returned widget can be named with the following syntax:
method = &Widget { ... } -> NAME: RETURNED_TYPE { ... }
and can be subsequently accessed via the Widgets struct.
In factories the returned widget is a parameter of the FactoryComponent::init_widgets()
method.
You can use the #[local_ref]
attribute to access it in the view macro, for example when the factory widget is a Stack
which returns a StackPage
:
view! {
#[root]
root = gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_halign: gtk::Align::Center,
set_spacing: 10,
set_margin_all: 12,
#[name(label)]
gtk::Label {
set_use_markup: true,
#[watch]
set_label: &format!("<b>Counter value: {}</b>", self.value),
set_width_chars: 3,
},
},
#[local_ref]
returned_widget -> gtk::StackPage {
set_name: &self.name,
set_title: &self.name,
}
}
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 and you want to use the duplicated method name, you need to tell Rust which trait it should use.
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, ...| { ... }
There is a shorthand for sending a message per event handlers like ComponentSender
or AsyncComponentSender
.
connect_clicked => AppMsg::Increase,
A member of a struct or the result of a method can also be used inside the closure by assigning it to a variable.
connect_name[sender = components.sender.clone()] => move |...| { ... }
Blocking signals temporarily
Some signals are not only emitted after a user interaction, but also when you change values though your code, for example by using #[watch]
.
This might not be the desired behavior and can even cause your application to freeze under certain circumstances.
To avoid this, you can name signal handlers by using an @
and a name after the signal closure.
Then, you can use the signal handler name in the #[block_signal(handler_name)]
attribute to deactivate the signal handler while you edit a value.
gtk::ToggleButton {
set_label: "Counter is even",
#[watch]
#[block_signal(toggle_handler)]
set_active: counter.value % 2 == 0,
connect_toggled[sender] => move |_| {
sender.input(AppMsg::Increment);
} @toggle_handler,
},
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 you can also add fields manually.
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 macro generates.
Code inside pre_view()
will run before the code of the macro, and post_view()
will run after it.
Code inside these "functions" isn't like a normal function! The macro disallows returning early in
pre_view
to ensure that the code of the macro will always be executed.