Making external keyboard on Rust
Tags: rust, gtk, gui, beginners
Published: 2022-07-19 04:27:15

#INTRO

Ok we made pure GTK keyboard with GTK buttons in [previous post](https://dev.to/antonov_mike/making-gtk-keyboard-on-rust-2fma). Maybe it is a good idea to compare two ways of developing a small keyboard.
Now we gonna make the same using external markdown file, let’s call it **grid.ui** and it is too huge to post it here. You can just grab it from my [github](https://github.com/antonovmike/gtk_phone_keyboard/blob/552ef41e3c0f1448f88aed55064d5f6845f849f6/src/grid.ui).
If you are rookie like me you can ask “why should we use extra file if we can use GTK only?” I guess we may have at least two reasons:
1) It helps to move some part of GUI stuff to the markdown file to make our code easy to read.
2) Some functions work different in different cases, and it’s interesting how we can use it.

By the way: pure GTK buttons are not scalable by default, but this ones are.

Some things I’ve explained in the previous post, so I just show you what we need in our project.

Open Cargo.toml and add list of crates
```Rust
[dependencies]
gtk = "0.15.5"
glib = "0.15.10"
chrono = "0.4.19"
```

We won’t change **main.rs** file
```Rust
use gtk::prelude::*;

mod buttons;

fn main() {
    let application =
        gtk::Application::new(Some("com.github.gtk-rs.examples.grid"), Default::default());

    application.connect_activate(buttons::build_ui);
    application.run();
}
```

We are starting **buttons.rs** with only few lines of code.

```Rust
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};

pub fn build_ui(application: &gtk::Application) {
    let glade_src = include_str!("grid.ui");
    let builder = Builder::from_string(glade_src);

    let window: ApplicationWindow = builder.object("window").expect("Couldn't get window");
    window.set_application(Some(application));

    // We are gonna add our buttons and functions here

    window.show_all();
}
```
If you `cargo run` it you will see all the elements attached to the grid, because all of them are created in a markdown `src/grid.ui` file. All we need – just connect this file
```Rust
let glade_src = include_str!("grid.ui");
```
Then send **glade_src** to Builder as a string slice **&str**.
```Rust
let builder = Builder::from_string(glade_src);
```
And connect to ApplicationWindow
```Rust
let window: ApplicationWindow = builder.object("window").expect("Couldn't get window");
```
One more time: 
1) Create markdown file with all elements you need
2) Bind it to variable
3) Send this variable to GTK builder
4) Connect it with ApplicationWindow

Let’s look at pure GTK again (do not add this code into our current project)
```Rust
    let window = gtk::ApplicationWindow::new(application);
    window.set_title("Studying GTK");
    window.set_default_size(200, 120);
    let grid = gtk::Grid::builder()
        .margin_start(7)
        .margin_end(7)
        .margin_top(7)
        .margin_bottom(7)
        .halign(gtk::Align::Center)
        .valign(gtk::Align::Center)
        .row_spacing(7)
        .column_spacing(7)
        .build();
    window.set_child(Some(&grid));
```
See the difference? We have to create window, then make grid and connect to our window. Let’s get back to our current project.
We are using markdown file and it should look like on the screenshot below. This is a bunch of useless buttons because Rust doesn’t know they exist. As I said before – it’s just a string slice of our markdown file content. We have to explain Rust how to use it. And here we need class id.
```html
<object class="GtkButton" id="button0">
```
Let’s look at it closer in the next chapter.

![Screenshot №1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7hd1rhtybqfafs2x567.png)

#1. Sliding button

Update your dependencies and let’s connect a grid.
```Rust
use gtk::glib;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
use gtk::{Button, Grid};
```

First we need a variable of type Grid. It calls `gtk::Builder` and method **object()** which receives some `“grid”`. What is that?
```Rust
    let grid: Grid = builder.object("grid").expect("Couldn't get grid");
```
This is the class and id. Class tells what kind of element it is. Id is a unique identifier of this very element. For example it can be element of type `”GtkGrid”` with id `“grid”`. 
```html
<object class="GtkGrid" id="grid">
```
As you remember we told Builder that our GUI stored inside **grid.ui** file 
```Rust
let glade_src = include_str!("grid.ui");
let builder = Builder::from_string(glade_src);
```
Now `gtk::Builder` can find element by it’s id inside the markdown file and bind function to it. And we gonna do it right now.
Let’s create **button0** and call it by id `“button0”`.
```Rust
    let button0: Button = builder.object("button0").expect("Couldn't get button0");
```
Now we can add function to it by calling `connect_clicked()`.
```Rust
    button0.connect_clicked(glib::clone!(@weak grid => move |button| {
        let left_attach = grid.cell_left_attach(button);
        let new_left_attach = if left_attach == 2 { 0 } else { left_attach + 1 };
        grid.set_cell_left_attach(button, new_left_attach);
    }));

    window.show_all();
}
```
We are using `glib::clone!` To make a clone and **@weak** or **@strong** _(looks like both do the same in this case)_ to tell Rust what type of clone we want to use. Then we are moving type button into closure and returning it at the end with a new position in the grid.
Here we are declaring two variables: **left_attach** and **new_left_attach**. After starting the application the first variable contains 1. 
The value of **new_left_attach** depends on if else condition. Each click increases **left_attach** by 1. As soon as it getting 2, else condition makes it equal 0. Try to change conditions, 
```html
<property name="left_attach">1</property>
```
and **button0** width.
Ok, now Button 0 slides right each time we click on it.

#2. Numeric buttons

I told you `gtk::Builder` can bind function to element’s id but it also can send data. Let’s add numeric buttons and made them change their own labels.
Connect buttons using their id
```Rust
    let button1: Button = builder.object("button1").expect("Couldn't get button1");
    let button2: Button = builder.object("button2").expect("Couldn't get button2");
    let button3: Button = builder.object("button3").expect("Couldn't get button3");
    let button4: Button = builder.object("button4").expect("Couldn't get button4");
    let button5: Button = builder.object("button5").expect("Couldn't get button5");
    let button6: Button = builder.object("button6").expect("Couldn't get button6");
    let button7: Button = builder.object("button7").expect("Couldn't get button7");
    let button8: Button = builder.object("button8").expect("Couldn't get button8");
    let button9: Button = builder.object("button9").expect("Couldn't get button9");
```
Add some function that gonna set button’s label to numerical letter after button has been clicked on. Notice we don’t have to use curly braces in this case. Also we don’t need **glib::clone!** because we don’t move any data inside the closure. Method **set_label()** sets new label.
```Rust
    button1.connect_clicked( move |button| button.set_label("I") );
    button2.connect_clicked( move |button| button.set_label("II") );
    button3.connect_clicked( move |button| button.set_label("III") );
    button4.connect_clicked( move |button| button.set_label("IV") );
    button5.connect_clicked( move |button| button.set_label("V") );
    button6.connect_clicked( move |button| button.set_label("VI") );
    button7.connect_clicked( move |button| button.set_label("VII") );
    button8.connect_clicked( move |button| button.set_label("VIII") );
    button9.connect_clicked( move |button| button.set_label("IX") );
```
Run and behold! Numeric buttons 1-9 change their labels after first click.

![Screenshot №2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfbpaj5op9kqamjjg1c5.png)

#3. Quit button

Another dependency
```Rust
use glib::clone;
```
In this case **@weak** and **@strong** are the same. Probably. Still not sure how it works.
Declare **quit_button** with type of Button. Pay attention at `object()` name! Here we are calling object by it’s id. In previous chapter `|button|` was a type not a data. But now we need `glib::clone!` because we move window inside the closure to destroy it.
```Rust
    let quit_button: Button = builder.object("quit_button").expect("Couldn't get quit_button");
    quit_button.connect_clicked(clone!(@weak window => move |_|
        unsafe {
            window.destroy()
        }
    ));
```
Let’s compare it with no markdown file project from my previous post (do not add this code into our current project). As you can see, here we are creating button themselves.
```Rust
let quit_button = gtk::Button::with_label("Quit");
```
Connecting function,which is completely the same as before…
```Rust
    quit_button.connect_clicked(clone!(@weak window => move |_| 
        unsafe {
            window.destroy()
        }
    ));
```
and attaching it to grid.
```Rust
grid.attach(&quit_button, 3, 1, 1, 4);
```
The difference is: 1) declaring 2) attaching

#4. Updating label

Now make label receiving data by clicking button.
Hope you have noticed when we are using external markdown file, type annotations needed. We have to tell to compiler – this is a GTK object of type Label.
```Rust
let counter_label: gtk::Label = builder.object("GtkLabel_1").expect("Couldn't get GtkLabel_1");
```
Declaring **minus_button** and **plus_button** is the same as before, nothing special.
```Rust
let minus_button: Button = builder.object("minus").expect("Couldn't get minus");
let plus_button: Button = builder.object("plus").expect("Couldn't get plus");
```
Functions would be hundred percent the same as we made in pure GTK project.
```Rust
    plus_button.connect_clicked(glib::clone!(@weak counter_label => move |_| {
        let nb = counter_label.text()
            .parse()
            .unwrap_or(0.0);
        counter_label.set_text(&format!("{}", nb + 1.1));
    }));
    minus_button.connect_clicked(glib::clone!(@weak counter_label => move |_| {
        let nb = counter_label.text()
            .parse()
            .unwrap_or(0.0);
        counter_label.set_text(&format!("{}", nb - 1.2));
    }));
```
Now buttons work and send data to label.

![Screenshot №3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehoo42rq132av84o2ybw.png)


#5. Timer

Bring a new path into scope.
```Rust
use chrono::Local;
```
At the end list of paths looks like this.
```Rust
use gtk::glib;
use glib::clone;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
use gtk::{Button, Grid};
use chrono::Local;
```
Declare time variable like we did it on a previous project.
```Rust
let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
```
But this part is different!
```Rust
let label_time: gtk::Label = builder.object("GtkLabel_2").expect("Couldn't get GtkLabel_2");
label_time.set_text(&time);
```
Just compare with the similar part in previous project (do not add this code into our current project).
```Rust
let label_time = gtk::Label::new(None);
label_time.set_text(&time);
grid.attach(&label_time, 0, 6, 4, 1);
```
This is the same as in the case with pure GTK grid.
```Rust
    let tick = move || {
        let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
        label_time.set_text(&time);
        // What is this?
        glib::Continue(true)
    };
    glib::timeout_add_seconds_local(1, tick);
```

Now cargo run it and you will see this. All the buttons work fine. Just one little detail left…

![Screenshot №4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqt9vqrhk0ufois5x7g7.png)

#6. Entry doesn’t work!

[As documented](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Entry.html) Entry should look like this, but unfortunately, I wasn’t able to get it to work

```html
<object class="GtkEntry">
  <attributes>
    <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
    <attribute name="background" value="red" start="5" end="10"/>
  </attributes>
</object>
```
I spend some time trying to solve this riddle. I'll probably come back to this issue later.

#CONCLUSION 
There are still a few things here that I don't quite understand yet. And also there are some ideas that I can't implement right now. For example a built-in display or a save file button. But I think I will gradually figure this out as well.
Hope this post was useful and educational
Take care