The view! macro
Introduction
The view!
macro is used within the #[widget]
implementation.
It allows us to describe the widget hierarchy in a declarative manner.
It’s an alternative to a glade file.
view! {
gtk::Window {
gtk::Box {
orientation: Vertical,
gtk::Button {
clicked => Msg::Increment,
label: "+",
},
gtk::Label {
text: &self.model.counter.to_string(),
},
gtk::Button {
clicked => Msg::Decrement,
label: "-",
},
},
delete_event(_, _) => (Msg::Quit, Inhibit(false)),
}
}
Within the view!
, we can declare:
Widgets and their hierarchy
Declare widgets
view! {
gtk::Window {
gtk::Box {
gtk::Button {
label: "+",
},
gtk::Button {
label: "-",
},
},
}
}
In this setup, we declared a gtk window, containing a gtk box, containing two gtk buttons.
We also set the label
property of each button.
Everytime we nest a widget within the {}
of another widget, we set up a hierarchical parent/child or container/child relationship.
The widgets contained in the view
can be gtk widgets as well as relm widgets.
Relm widgets
Here is an example of adding a relm widget in a view hierarchy:
SearchView((None, SearchItemsType::All, OperationMode::ItemActions)) {
}
In this case, SearchView
is the name of the relm component, and in the brackets we have all the component’s model parameters.
As a reminder, when we declare a relm component, we must define its fn model()
function. That function takes as as second parameter the model parameters.
For instance, in this case:
fn model(
relm: &relm::Relm<Self>,
params: (
Option<String>,
SearchItemsType,
OperationMode,
),
) -> Model {
The component model parameter has three parameters, which we pass through a tuple when building the view!
hierarchy.
Widget attributes
We can also attach attributes to widgets. For instance:
view! {
#[name="toplevel"]
#[style_class="mainwin"]
gtk::Window {
}
}
Let’s now review the possible widget attributes.
The name
widget attribute
#[name="toplevel"]
Allows you to access the widget easily in your relm code.
When you’re in the Widget
implementation, you can then access the widget through self.widgets.toplevel
, or the name you’ve given.
This is often useful to initialize the widget in your init_view()
method, although generally speaking it’s better if you can manage to do everything within the view!
macro.
It’s not always possible though.
The style_class
widget attribute
#[style_class="mainwin"]
Allows to add a gtk style class name to the widget. This is equivalent to running this code in your init_view()
:
widget.get_style_context().add_class("mainwin");
Because it’s possible to add multiple CSS classes to a single widget, you can add multiple style_class
annotations to a single widget.
In that case all the classes that you list will be added to the widget.
Gtk child properties
When declaring the widget hierarchy, we will sometimes have to specify gtk child properties.
This can be done through a child
subnode in the view hierarchy:
gtk::Stack {
child: {
fill: true,
expand: true,
},
},
In this case, we set the fill
and expand
child properties.
Note that this is called on the parent — in this example not on the Stack
widget, but on its parent (since it affects the layout of that Stack
in its parent).
Set widget properties - gtk widgets
We already saw an example of setting a gtk widget property earlier: the label
property of a gtk::Button
.
It is possible to "guess" the available widget properties from the gtk-rs API. For instance, for a gtk Label`
, here is an example with a few properties:
gtk::Label {
hexpand: true,
margin_start: 10,
margin_end: 10,
margin_top: 10,
xalign: 0.1,
yalign: 0.1,
line_wrap: true,
markup: "<big><b>Empty project</b></big>\n\n\"
}
For instance, the line_wrap
property comes from gtk::LabelExt::set_line_wrap.
As you can see, we can just remove the set_
from setter.
But not only plain gtk::LabelExt
functions are covered.
For instance hexpand
ties to gtk::WidgetExt::set_hexpand.
We can also specify dynamic values through properties; for instance in the previous example, we could change the markup
line to: markup: self.model.label_contents
.
If we do that, whenever the model field label_contents
gets modified, the label contents will be automatically updated.
Set widget properties - relm widgets
For relm components, we most often pass properties through model parameters, but they can also be specified in a similar way to gtk properties:
Text {
// Send the message SetText(self.model.text.clone()) at initialization and when
// the model attribute is updated.
SetText: self.model.text.clone(),
},
Connect events
connect events for gtk widgets
Again, same as with properties, you can help yourself with the gtk-rs API to find out to which gtk events you can tie to.
In this example, we bind to two gtk events:
gtk::Window {
delete_event(_, _) => (Msg::Quit, Inhibit(false)),
key_press_event(_, event) => (Msg::KeyPress(event.clone()), Inhibit(false)),
}
the first one is connect_delete_event.
In the same way that for setters we can remove the set_
, for events, we can remove the connect_
.
And we see that the connect function gives two parameters for the callback: self and an event object.
And that’s also what we get in the callback here, although in this case we ignore both parameters.
We can then "return" a tuple, the first parameter of which is a relm event that will be emitted on your widget when the gtk event is emitted.
As you can see for key_press_event, we can also collect the event object and copy it in our relm event.
And of course, same as with setters, we have access to events from the whole gtk hierarchy, from your concrete widget (Button, Window etc) up to Widget for instance.
connect events for relm components
SearchView((None, SearchItemsType::All, OperationMode::ItemActions)) {
SearchViewSearchResultsModified => Msg::SearchResultsModified,
SearchViewShowInfoBar(ref msg) => Msg::ShowInfoBar(msg.clone()),
}
Here we add a relm component, and list some of its relm events, and bind them to relm events on the current widget.
Note that it’s not supported to use ::
tokens when binding to relm events. We might have wanted to type search_view::Msg::SearchResultsModified
instead of SearchViewSearchResultsModified
, but this is not supported.
Instead we must import the symbol and rename it using use
, like so:
use super::search_view::Msg::SearchResultsModified as SearchViewSearchResultsModified;
use super::search_view::Msg::ShowInfoBar as SearchViewShowInfoBar;