Counter example
In the Getting started page, we briefly saw how to create a counter application. Now, let’s describe more in details what is going on under the hood.
Let’s look again at the view, since it is where the most magic happens:
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)),
}
}
Event handling
It was described that the following connects an event to a signal:
gtk::Button {
clicked => Msg::Increment,
}
But how does that work and what is clicked
?
This will actually be converted to the following:
connect!(relm, plus_button, connect_clicked(_), Msg::Increment);
where connect!
is a macro defined by relm
as well and will be
converted to something like:
let stream = relm.stream.clone();
plus_button.connect_clicked(move |_| {
stream.emit(Msg::Increment);
});
which looks more like regular gtk-rs
code, the library on which
relm
is based.
So, connect_clicked
actually comes from gtk-rs.
relm
will take the event mentioned in the macro (clicked
) and
precedes it by connect_
.
Later in the view, we have a more complicated example of event handling:
gtk::Window {
// ...
delete_event(_, _) => (Msg::Quit, Inhibit(false)),
}
Here, we are not only sending a message (Msg::Quit
), but also
returning a value (Inhibit(false)
) from the callback.
We return a value because it is what is required by the original
connect_delete_event
method.
So, the syntax is:
event_name => (message_to_send, value_to_return)
Another difference here is the (_, _)
after the event name.
This is because the connect_delete_event
method takes a callback
with two parameters.
We don’t need it here, but we could use the value from these
parameters.
We’ll talk more about that later.
The reason why we didn’t have that for the other events is that relm
will assume there’s one parameter by default.
So, the following:
clicked => Msg::Increment
is the same as:
clicked(_) => Msg::Increment
Bindings
Like properties, a binding will generate an initial call to set_*
.
For instance, the following code:
gtk::Label {
text: &self.model.counter.to_string(),
}
will first generate a call to:
label.set_text(&self.model.counter.to_string());
using a similar logic as before: relm
will prepend set_
to the
property specified by the user (here text
).
But since it is a binding, relm
will also change the code of the
update()
method so that changes to the model field it is bound to
(i.e. counter
) update the view as well.
So, the following method:
fn update(&mut self, event: Msg) {
match event {
Msg::Decrement => self.model.counter -= 1,
Msg::Increment => self.model.counter += 1,
Msg::Quit => gtk::main_quit(),
}
}
will be converted to something like:
fn update(&mut self, event: Msg) {
match event {
Msg::Decrement => {
self.model.counter -= 1;
label.set_text(&self.model.counter.to_string());
},
Msg::Increment => {
self.model.counter += 1;
label.set_text(&self.model.counter.to_string());
},
Msg::Quit => gtk::main_quit(),
}
}
Code generation
This code generation is actually pretty dump. There are two cases to be aware of.
The first one is that the call to the setter method is done right after the model field is updated, so the following code:
fn update(&mut self, event: Msg) {
for _ in 0..100 {
self.model.counter += 1;
}
}
will be converted to:
fn update(&mut self, event: Msg) {
for _ in 0..100 {
self.model.counter += 1;
self.label1.set_text(&self.model.counter.to_string());
}
}
So your code can be inefficient if used this way.
Second, only assignment to a field of the model will update the view: calling a mutable method on a model field won’t work as expected.
So, instead of doing the following:
fn update(&mut self, event: Msg) {
self.model.text.push_str("Text");
}
you will need to do something else, like:
fn update(&mut self, event: Msg) {
self.model.text += "Text";
}
It’s all there is to know about this example. The next tutorial go
into more details about the syntax of the view!
macro and event
parameters.
The complete code for this tutorial is here.