The gtk-rs way
Sometimes, it’s not possible yet to do something in relm
, for instance
creating a menu or drawing a widget.
This is because some features of GTK+ behaves differently than others
and the assumptions made in relm
does not support these features.
However, it’s always possible to mix the gtk-rs
and the relm
ways
and that’s what we’ll see in this tutorial.
Creating a menu
If we try to create a menu like we would create other widgets in relm, it won’t work. For instance, this code:
gtk::MenuBar {
gtk::MenuItem {
label: "File",
gtk::Menu {
gtk::MenuItem {
label: "Quit",
},
},
},
}
will not show the Quit menu and GTK+ will give this warning:
Attempting to add a widget with type GtkMenu to a container of type GtkMenuItem, but the widget is already inside a container of type GtkWindow, please remove the widget from its existing container first.
So instead of doing this, we’ll write some gtk-rs
code and integrate
it into our relm
widget.
First, let’s create our view with the MenuBar
:
view! {
gtk::Window {
gtk::Box {
orientation: Vertical,
#[name="menubar"]
gtk::MenuBar {
},
},
delete_event(_, _) => (Quit, Inhibit(false)),
}
}
As you can see here, we give a name to the menu bar which will allow
us to access to it with self.menubar
.
Then, we’ll implement the init_view()
method which is called right
after the view is created:
#[widget]
impl Widget for Win {
fn init_view(&mut self) {
let file_menu = gtk::Menu::new();
let file_item = gtk::MenuItem::new_with_label("File");
file_item.set_submenu(Some(&file_menu));
let quit_item = gtk::MenuItem::new_with_label("Quit");
self.menubar.append(&file_item);
file_menu.append(&quit_item);
self.menubar.show_all();
connect!(quit_item, connect_activate(_), self.model.relm, Quit);
}
}
Here, we have normal gtk-rs
code to create the menu and append it to
our menu bar that was created the relm
way.
And we connect the event with the connect!
macro like we saw
earlier.
We also access the relm
object from the model, which we saved in the
model()
method:
fn model(relm: &Relm<Self>, _: ()) -> Model {
Model {
relm: relm.clone(),
}
}
As you can see, the model()
method can have multiple signatures
(actually, the #[widget]
attribute will add these two parameters if
they are not provided).
This one takes the Relm
object as its first parameter. This object
is used to connect events to send a message to a relm widget or to
directly send a message to a relm widget.
The complete code for this section is here.
Drawing example
We’ll write another application that draw the letter pressed by the user.
Our model will contain the letter pressed by the user:
pub struct Model {
letter: Rc<Cell<char>>,
}
Here, we use a Rc<Cell<_>>
as we would do in gtk-rs
because we
want to share it with an event callback.
When relm
will support drawing natively, that should not be required
anymore.
The initial value of the model will be:
fn model() -> Model {
Model {
letter: Rc::new(Cell::new(' ')),
}
}
Our view will contain a drawing area:
view! {
gtk::Window {
#[name="drawing_area"]
gtk::DrawingArea {
},
delete_event(_, _) => (Quit, Inhibit(false)),
key_press_event(_, event) => (KeyPress(event.clone()), Inhibit(false)),
}
}
And we connected the key press event to get the key pressed by the user.
We handle this message as such:
fn update(&mut self, event: Msg) {
match event {
KeyPress(event) => {
if let Some(letter) = gdk::keyval_to_unicode(event.get_keyval()) {
self.model.letter.set(letter);
self.drawing_area.queue_draw();
}
},
Quit => gtk::main_quit(),
}
}
which converts the key to a Unicode character and assign it to our model while requesting to redraw the widget.
Finally, in the init_view()
method, we do the actual drawing:
fn init_view(&mut self) {
let letter = self.model.letter.clone();
self.drawing_area.connect_draw(move |_, context| {
context.set_source_rgb(0.2, 0.4, 0.0);
context.paint();
context.set_font_size(60.0);
context.set_source_rgb(0.0, 0.0, 0.0);
context.move_to(100.0, 100.0);
context.show_text(&letter.get().to_string());
Inhibit(false)
});
}
We clone the reference-counted letter to have a copy of it in the draw
event handler.
In this handler, we draw the letter using cairo
.
The complete code for this section is here.