The widget macro reference
There are quite a lot of examples where the widget macro is used in this book. Yet, we haven't covered everything in the previous chapters and having all the information in one place is nice, too.
Property names
The widget macros uses setter methods of gtk4-rs. You can find them at the gtk4-rs docs.
Many properties are also part of a trait. Make sure that this trait is in scope. In many cases you need to use gtk::prelude::TraitName
.
For example, if you want to use the set_default_width
method of the GtkWindowExt
trait you need to use gtk::prelude::GtkWindowExt
.
Trait disambiguation
Sometimes you use several traits that implement the same method for a type so you need to tell Rust which trait it should use. For example the set_child
function is implemented by both gtk::prelude::GtkWindowExt
and libadwaita::traits::ApplicationWindowExt
. If we use the regular syntax, the Rust compiler will get confused and tells us to specify the trait. So instead we use the TraitName::method
syntax that's similar to Rust's fully qualified syntax for trait disambiguation.
You can also use the full path of the trait.
libadwaita::traits::ApplicationWindowExt::set_child = Some(>k::Box) { ... }
Public widgets
If you want to make the widgets struct generated by the macro public, you can simply use pub
as an attribute for the macro.
#[relm4::widget(pub)]
Assign properties
Initialize a property with a value:
property_name: value,
Initialize an optional property only if it's Some
and ignore if it's none:
property_name?: value,
Initialize a property that has multiple properties:
property_name: args!(value1, value2, ...),
Initialize and automatically update a property:
property_name: watch!(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 property should be updated:
property_name: track!(track_expression, 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
.
property_name: iterate!(iterator),
Add widgets
Without name:
property_name = gtk::Box { ... }
A common mistake is to accidentally use
:
instead of=
for assigning widgets.
With name:
property_name: name = gtk::Box { ... }
As reference:
property_name = >k::Box { ... }
As Option
:
property_name = Some(gtk::Box) { ... }
As reference in an Option
:
property_name = Some(>k::Box) { ... }
Pass additional arguments with the widget. This will call widget.property_name(box_widget, value1, value2, ...)
and can be used to call attach on a gtk::Grid
for example.
property_name(value1, value2, ...) = gtk::Box { ... }
property_name(value1, value2, ...): name = gtk::Box { ... }
The type of the widget created in all the examples above will always be gtk::Box
. However, some properties are set with references or references in Options
where this syntax becomes handy.
Functions
Sometimes there's no default implementation for a widget, so you need a constructor or you want to pass a function that returns the widget.
If the function is associated with a type, you can simply use this syntax. The macro will assume the type of gtk::Box::new()
is gtk::Box
:
property_name = gtk::Box::new() { ... }
For some functions, the macro can't guess the type or might even assume a wrong type. In such a case, add the type your function:
property_name = new_box() -> gtk::Box { ... }
Connecting events
When connecting events you can clone elements you need in the closure by putting it into the parentheses.
connect_name(cloned_var1, cloned_var2, ...) => move |arg1, arg2, ...| { ... }
Connecting to components
For connecting events directly to components you need to use brackets. In the brackets you can create new sender variables from the senders of your components.
connect_name[sender1 = components.name1.sender(),
sender2 = components.name2.sender(), ...] => move |arg1, arg2, ...|
The send macro
The send macro simply provides some syntactical sugar. This code
send!(sender, AppMsg::Increment)
expands to this code:
sender.send(AppMsg::Increment).expect("Receiver was dropped!")
The unwrap is save because send errors should never happen, especially because Relm4 usually keeps the receiver alive.
Factories
property_name = gtk::Box {
factory!(model.data)
}
Manual
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::widget]
impl Widgets<AppModel, ()> for AppWidgets {
view! {
// ...
}
additional_fields! {
// ...
}
fn pre_init() {
// ...
}
fn post_init() {
// ...
}
fn pre_view() {
// ...
}
fn post_view() {
// ...
}
}
Run custom initialization code
You can use the pre_init
function inside the widgets macro to run code before the initialization of the view macro starts. This is useful if you want to generate values you later use in the view macro.
fn pre_init() {
// ...
}
You can use the post_init
function to run code after the initialization of the view macro. This can be used to modify the widgets generated by the view macro for manual initialization. All variables and widget names used in the view macro and the pre_init
function can still be used here.
fn post_init() {
// ...
}
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,
}
To initialize the variable, you can use either pre_init
or post_init
. Simply name a local variable like your custom field. I this case we could simply do this:
fn post_init() {
let test = 0;
}
The macro will then put all parts together to create the widgets struct and the init_view
function.
struct AppWidgets {
...
test: u8,
}
impl Widgets<AppModel, ()> for AppWidgets {
...
fn init_view(model: &AppModel, components: &(), sender: Sender<AppMsg>) -> Self {
...
let test = 0;
AppWidgets {
...
test,
}
}
...
}
Manual view
You can also implement your own view logic that's added to the view code the view macro generates, before and after, respectively with pre_view()
and post_view()
To refer to the widgets, use self
and model
for the model.
fn pre_view() {
// ...
}
fn post_view() {
// ...
}