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 = &gtk::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]= &gtk::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 = &gtk::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.

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.