Making GTK keyboard on Rust
Tags: rust, gtk, gui, beginners
Published: 2022-07-17 04:54:04

## FOREWORD

So I’m kinda newbie. I’ve never been studying programming before. And as self educated newbie I had to choose some direction as one of the best ways to study: learn, practice, repeat. Because of my experience of develop telegram bot (using carapax) and failure at making buttons I’ve started to learn GUI. I tried couple of other GUI libraries for Rust: druid, fltk and iced. And finally I opted for GTK because it’s docs looks a little bit better then previous ones. But I am not a qualified developer so maybe I am wrong.
First issue I encountered was the difficulty of reading the documentation and source code. It takes some getting used to.
The second – examples and explanations the people post in the internet. Sometimes there are just missed some parts of code. Some answers you won’t be able to guess your own. And you have to spend time for googling answers to questions you can’t even articulate because you have no experience at developing at all.
That’s why I made this post – to explain what I have learned to myself. It helps better understand what I have studied.

## INTRO
Fist of all make a new Rust project:
```
cargo new gtk_grid
```
After that we need all the dependencies to be added in our **Cargo.toml** file
```
[dependencies]
gtk = "0.15.5"
glib = "0.15.12"
chrono = "0.4.19"
```
All the work would be done in an additional file **gui.rs** so let’s create it and add it to the main.rs with this line: 
```rust
mod gui; 
```
Main file **main.rs** would be like this. We won’t change it anymore. Just create GTK application, connect it with gui module and run it.
```rust
use gtk::prelude::*;

mod gui;

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

    application.connect_activate(gui::build_ui);
    application.run();
}
```
All the libraries we have to add to **gui.rs** file
```rust
use gtk::Label;
use glib::clone;
use gtk::prelude::*;
use gtk::Entry;
use chrono::Local;
```
Great, now let’s get to work!

## 1. Window and Entry field
For this little example we only need two libraries:
```rust
use gtk::prelude::*;
use gtk::Entry;
```
If we need to use function from **gui.rs** in **main.rs** we have to mark it as **pub**. This **build_ui()** function creates all the GTK elements, binds them together and displays in GTK window. Method named `set_title(“App Name”)` is a window description, adds your application’s name at the top of the window.
Other methods you can use:
`window.set_titlebar(&header.container);`
– An other window description. Makes a huge title bar. Lets you connect buttons right to the title bar.
`window.set_wmclass("app-name", "App Name");`
– Sets window manager class. Still not sure how it works.
`Window::set_default_icon_name("icon-name");`
– Sets default window’s icon. I tried and failed – no icon at all. Gonna check it later.
Now we are creating grid. Margin is just a digit. You can use different values for different margins and put them right into braces instead of variable. For example `row_spacing(2)`. Connect the grid to the window as a child. We can make another child and connect it to. But as compiler says: _“GtkApplicationWindow can only contain one widget at a time”_, so we can’t add one more grid this way or even `gtk::Button::with_label("Some Button")`. I hope there is another way to connect more widgets.
After grid was connected we can create it’s elements and attach them to grid.
Let’s make an entry field using `Entry::builder`. I’ve never tried to add any functions to it, so it still just a demonstration model. I guess I solve this later.
```rust
pub fn build_ui(applicsation: &gtk::Application) {
    let window = gtk::ApplicationWindow::new(application);
    window.set_title("GTK grid");
    window.set_default_size(200, 120);

    let margin = 6;
    let grid = gtk::Grid::builder()
        .margin_start(margin)
        .margin_end(margin)
        .margin_top(margin)
        .margin_bottom(margin)
        .halign(gtk::Align::Center)
        .valign(gtk::Align::Center)
        .row_spacing(margin)
        .column_spacing(margin)
        .build();

    window.set_child(Some(&grid));

    let entry = Entry::builder()
        .margin_start(margin)
        .margin_top(margin)
        .margin_end(margin)
        .margin_bottom(margin)
        .build();
    grid.attach(&entry, 0, 0, 4 ,1);

    window.show_all();
}
```
!!! Here is the thing about `window.show()` and `window.show_all()`: I saw a few examples of `window.show()` that only displays empty window and doesn’t show any widgets. Changing this line to `window.show_all()` helps. But I have no idea why some code works fine using `show()` and some needs `show_all()`. If you know please explain it to me. 
After you compile and run your application should look like this (colors depend on your operation system settings)

![Screenshot 01](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7vg28nvn3aj6qhrhubk.png)

## 2. First buttons
After we made an entry we are going to create a keyboard. We have to create each keyboard button with label, connect function to it and attach buttons to grid. Here you can see dummy functions like print string to console. Or you can make something more complicated. An example of the running button
```rust
    let button_1 = gtk::Button::with_label("Button 1"); 
    button_1.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);
    }));
    grid.attach(&button_1, 0, 1, 1, 1);
```
Don’t forget to remove **button_2** and **button_3** first. You can try it on your own so let’s get rid of it and use code that would make your app looks like on the next screenshot
```rust
    // --> KEYBOARD STARTS HERE <--

    let button_1 = gtk::Button::with_label("Button 1");
    let button_2 = gtk::Button::with_label("Button 2");
    let button_3 = gtk::Button::with_label("Button 3");
    button_1.connect_clicked(move |_| println!("Button 1"));
    button_2.connect_clicked(move |_| println!("Button 2"));
    button_3.connect_clicked(move |_| println!("Button 3"));
    grid.attach(&button_1, 0, 1, 1, 1);
    grid.attach(&button_2, 1, 1, 1, 1);
    grid.attach(&button_3, 2, 1, 1, 1);
    
    // --> ROW 2
    let button_4 = gtk::Button::with_label("Button 4");
    let button_5 = gtk::Button::with_label("Button 5");
    let button_6 = gtk::Button::with_label("Button 6");
    button_4.connect_clicked(move |_| println!("Button 4"));
    button_5.connect_clicked(move |_| println!("Button 5"));
    button_6.connect_clicked(move |_| println!("Button 6"));
    grid.attach(&button_4, 0, 2, 1, 1);
    grid.attach(&button_5, 1, 2, 1, 1);
    grid.attach(&button_6, 2, 2, 1, 1);

    // --> KEYBOARD ENDS HERE <--
```
Grid attach takes 5 parameters: 1) the element you are attaching, 2) it’s horizontal position, 3) it’s vertical position, 4) it’s width, 5) and height.
These buttons are not scalable.

![Screenshot 02](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubynwxjtzjxzvmwbg8wz.png)

## 3. Other numeric buttons
Finish previous code with this part. Now we have a numeric phone-like keyboard full of useless buttons. Isn’t that wonderful?! Joking! I am gonna show you button with very important function in the next chapter. Just add this piece of code and check the screenshot №3.
```rust
    // --> KEYBOARD STARTS HERE <--

    // --> ROW 1
    let button_1 = gtk::Button::with_label("Button 1");
    let button_2 = gtk::Button::with_label("Button 2");
    let button_3 = gtk::Button::with_label("Button 3");
    button_1.connect_clicked(move |_| println!("Button 1"));
    button_2.connect_clicked(move |_| println!("Button 2"));
    button_3.connect_clicked(move |_| println!("Button 3"));
    grid.attach(&button_1, 0, 1, 1, 1);
    grid.attach(&button_2, 1, 1, 1, 1);
    grid.attach(&button_3, 2, 1, 1, 1);
    
    // --> ROW 2
    let button_4 = gtk::Button::with_label("Button 4");
    let button_5 = gtk::Button::with_label("Button 5");
    let button_6 = gtk::Button::with_label("Button 6");
    button_4.connect_clicked(move |_| println!("Button 4"));
    button_5.connect_clicked(move |_| println!("Button 5"));
    button_6.connect_clicked(move |_| println!("Button 6"));
    grid.attach(&button_4, 0, 2, 1, 1);
    grid.attach(&button_5, 1, 2, 1, 1);
    grid.attach(&button_6, 2, 2, 1, 1);
    
    // --> ROW 3
    let button_7 = gtk::Button::with_label("Button 7");
    let button_8 = gtk::Button::with_label("Button 8");
    let button_9 = gtk::Button::with_label("Button 9");
    button_7.connect_clicked(move |_| println!("Button 7"));
    button_8.connect_clicked(move |_| println!("Button 8"));
    button_9.connect_clicked(move |_| println!("Button 9"));
    grid.attach(&button_7, 0, 3, 1, 1);
    grid.attach(&button_8, 1, 3, 1, 1);
    grid.attach(&button_9, 2, 3, 1, 1);

    // --> ROW 4
    let button_0 = gtk::Button::with_label("Button 0");
    button_0.connect_clicked(move |_| println!("Button 0"));
    grid.attach(&button_0, 1, 4, 1, 1);

    // --> KEYBOARD ENDS HERE <--
```
That how it gonna look like

![Screenshot 03](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tquik8v9oeraluyma2dy.png)

## 4. Quit button
First of all add this to your **gui.rs** file
```rust
use glib::clone;
```
Now it looks like that:
```rust
use gtk::prelude::*;
use gtk::Entry;
use glib::clone;
```
Add the following code after row 4. Here we are making Quit Button and connecting it to function that gonna destroy our window, I mean close it. This part of code with **window.destroy()** is unsafe and I used it for the first time in my life. Such  a historic moment!
Don’t forget to attach it to our grid: it would be the 4th element from the left and 2nd from the top with width equals 1 and height equals 4. Feel free to change everything
```rust
    // --> ROW 2 COLUMN 4 Quit button
    let quit_button = gtk::Button::with_label("Quit");
    quit_button.connect_clicked(clone!(@weak window => move |_| 
        unsafe {
            window.destroy()
        }
    ));
    grid.attach(&quit_button, 3, 1, 1, 4);

    // --> KEYBOARD ENDS HERE <--

    window.show_all();
}
```
Let’s check the appearance

![Screenshot 04](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2ni0jnxjna5oa8ruukb.png)

## 5. Label that contains a data
Add this to **gui.rs** file
```rust
use gtk::Label;
```
That was an issue! But I did it and gonna tell you how.
Label is just a text. For example: `let some_label = Label::new(Some("make code not war"));` But we can connect function that gonna change this text. Let’s make label and two buttons for increase and decrease floating point integer.
Important: I still have no idea how to collect characters and keep them in the label. I can only replace them, so I can’t see nothing but 1 new character at a time. Maybe this is because the text() method consumes the value since it assembles all the chunks together, constructs a String is constructed, and returns it to you. We can't use our variable after calling **text()**. Don’t know. But month ago I had no idea how to make a GTK keyboard, so I guess I’m gonna solve this riddle to.
We are creating **counter_label** and attaching it to grid. That’s easy now.
The function (closure) need to clone some data. And we have to specify if this a weak or a strong clone. I’m not sure how it works I’m just following LSP and compiler’s hints, so we need a @weak clone here. Inside the closure we are making a variable by calling **text()** method and parsing it into floating point digit. If it succeed we can do some mathematics using **set_text()** method and **format!()** macro. The first one sets new text to our counter label. The second – calculates and turning the result into a String.
Now just attach all the parts to the appropriate places.
```rust
    // --> ROW 5 - LABEL
    let counter_label = Label::new(Some("0.0"));
    grid.attach(&counter_label, 0, 5, 4, 1);

    // --> ROW 4
    let plus_button = gtk::Button::with_label("+");
    let button_0 = gtk::Button::with_label("Button 0");
    let minus_button = gtk::Button::with_label("-");
    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));
    }));
    button_0.connect_clicked(move |_| println!("Button 0"));
    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));
    }));
    grid.attach(&plus_button, 0, 4, 1, 1);
    grid.attach(&button_0, 1, 4, 1, 1);
    grid.attach(&minus_button, 2, 4, 1, 1);
```
It should look like this. Take a look at the bottom of our window. Here you can see a result of few clicks on “-” and “+” buttons.

![Screenshot 05](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9di5jj5o9x4rxcvnqo20.png)

## 6. Frozen timer
Timer needs this
```rust
use chrono::Local;
```
Ok, that was hard! I made timer, but only for displaying a start time which is pretty useless. I need a real clock! And I show how to make it in the next chapter. 
We need special label for our timer and String variable that contains time in this format `%Y-%m-%d %H:%M:%S` (you can choose your own format for example `%m-%d-%Y` which is confusing me a lot).
After all we are attaching our **label_time** `gui::gtk::Label` to our grid and getting beautiful frozen timer.
```rust
    let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
    let label_time = gtk::Label::new(None);
    label_time.set_text(&time);
    grid.attach(&label_time, 0, 6, 4, 1);
```
Here it is – a clock that shows the correct time only when you start the program

![Screenshot 06](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lp44yrozhme2hkhplmbl.png)

## 7. This watch works!
Finally! We just need to create a variable and add a function (closure) to it. I have no idea how it actually works. It’s something like “get local time in this format each X seconds”. The first digit is the rate of timer updates `glib::timeout_add_seconds_local(1, tick)` is in seconds. The second is our variable that updates each second.
Not sure how `glib::Continue(true)` works.
```rust
    let tick = move || {
        let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
        label_time.set_text(&time);
        glib::Continue(true)
    };
    glib::timeout_add_seconds_local(1, tick);
```
Now cargo run it. The application should look like this. Timer should update time every second

![Screenshot 07](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhhiqw4dm5d8c6zgrahz.png)

![Screenshot 08](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehc5364qcegts1zz6tft.png)

Now let's try to make this app using [external markdown](https://dev.to/antonov_mike/making-external-keyboard-on-rust-2c6p) file.


## CONCLUSION

I did my best at writing this post and I hope it was useful not only for me. Anyway it was a good experience: solve an issue and repeat the process from small useless app with just one element of GUI to big and… ok it still useless, but I learn something developing it.
Cheers
Good luck
Take care
And all the kind words that exist