2 minute read

GTK and Rust

GTK is a free and open-source cross-platform widget toolkit for creating graphical user interfaces (GUIs).

Rust is a fast, reliable and productive language for building software on embedded devices, web services and more. It is a system programming language focused on safety, speed and concurrency.

Focus of this tutorial is to write a counter app with GTK 4 and Rust.

Project Setup

Let’s begin by installing all necessary tools. First, follow the instructions on the GTK website in order to install GTK 4. Then install Rust with rustup. We are targeting GTK4, Rust 1.75 and gtk-rs version 0.7.3 with features v4_12.

Now lets create a new project by executing:

cargo new gtk4-rust-counter-app

Add gtk4 crate to your dependencies in Cargo.toml.

cargo add gtk4 --rename gtk --features v4_12

Now we can run our application by executing:

cargo run

At this moment it would print Hello, world!.

Application

Lets start by creating GTK Application and connecting to activate event of the Application. We will create a method build_ui to create a window and display it.

use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow};

const APP_ID: &str = "org.gtk_rs.GTK4Counter";

fn main() -> glib::ExitCode {
// Create a new application
    let app = Application::builder().application_id(APP_ID).build();

    // Connect to "activate" signal of `app`
    app.connect_activate(build_ui);

    // Run the application
    app.run()
}

fn build_ui(app: &Application) {
    // Create a window and set the title
    let window = ApplicationWindow::builder()
        .application(app)
        .title("GTK Counter App")
        .build();

    // Present window
    window.present();
}

This will dispaly an empty window. Time to add some content.

Empty GTK Window

Label

Lets start by adding a Label and displaying Hello World! text. Add Label to gtk imports.

Add following to build_ui method.

    let label_counter = Label::builder()
        .label("Hello World!")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

And add label_counter as child of window.

    let window = ApplicationWindow::builder()
        .application(app)
        .title("GTK Counter App")
        .child(&label_counter)
        .build();

Running the application now would display Hello World! text in the window.

Hello World

Increment Button

Lets create a button

    let button_increase = Button::builder()
        .label("Increase")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

Now that we have multiple widgets, we would add a gtk_box to hold all the child elements and add that gtk_box as child to window instead.

    let gtk_box = Box::builder()
        .orientation(Orientation::Vertical)
        .build();
    gtk_box.append(&label_counter);
    gtk_box.append(&button_increase);

    ...
        .child(&gtk_box)
    ...
Increase Button

Add Click Handler

Lets add a counter and button click handler. We will update the counter and update the label_counter with the value. Out counter variable would need to be put inside Rc and Cell (not sure if I am using the term correctly) to enable multiple owners and mutable access to variable.

Variable declare will look like following and set label value.

    let counter = Rc::new(Cell::new(0));
    ...
    .label(&counter.get().to_string())
    ...

Lets add a click handler for the button_increase.

    button_increase.connect_clicked(clone!(@strong counter, @weak label_counter =>
        move |_| {
            counter.set(counter.get() + 1);
            label_counter.set_label(&counter.get().to_string());
    }));

Add Decrease Button and Handler

Lets add another button to decrease the value and update the value

    let button_decrease = Button::builder()
        .label("Decrease")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();
    
    button_decrease.connect_clicked(clone!(@weak label_counter =>
        move |_| {
            counter.set(counter.get() - 1);
            label_counter.set_label(&counter.get().to_string());
    }));

Source

Source code for the demo application is hosted on GitHub in blog-code-samples repository.

References

In no particular order

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...