From 96f4bbb86ac501832af987b9787831338054e6fe Mon Sep 17 00:00:00 2001 From: Hofer-Julian Date: Fri, 18 Aug 2023 08:17:36 +0000 Subject: [PATCH] deploy: 25988d6970a3d10bf5df4614d577c85f2f238291 --- stable/latest/book/print.html | 128 ++++++++++++++++++++++++++++ stable/latest/book/searchindex.js | 2 +- stable/latest/book/searchindex.json | 2 +- stable/latest/book/todo_1.html | 128 ++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 2 deletions(-) diff --git a/stable/latest/book/print.html b/stable/latest/book/print.html index 4c7e1ea47314..88eddba1cf2c 100644 --- a/stable/latest/book/print.html +++ b/stable/latest/book/print.html @@ -6389,6 +6389,134 @@

Task Row

self.imp().tasks_list.set_factory(Some(&factory)); } } +

We also create a method new_task which takes the content of the entry, clears the entry and uses the content to create a new task.

+

Filename: listings/todo/1/window/mod.rs

+
mod imp;
+
+use glib::{clone, Object};
+use gtk::subclass::prelude::*;
+use gtk::{gio, glib, Application, NoSelection, SignalListItemFactory};
+use gtk::{prelude::*, ListItem};
+
+use crate::task_object::TaskObject;
+use crate::task_row::TaskRow;
+
+glib::wrapper! {
+    pub struct Window(ObjectSubclass<imp::Window>)
+        @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
+        @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
+                    gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
+}
+
+impl Window {
+    pub fn new(app: &Application) -> Self {
+        // Create new window
+        Object::builder().property("application", app).build()
+    }
+
+    fn tasks(&self) -> gio::ListStore {
+        // Get state
+        self.imp()
+            .tasks
+            .borrow()
+            .clone()
+            .expect("Could not get current tasks.")
+    }
+
+    fn setup_tasks(&self) {
+        // Create new model
+        let model = gio::ListStore::new::<TaskObject>();
+
+        // Get state and set model
+        self.imp().tasks.replace(Some(model));
+
+        // Wrap model with selection and pass it to the list view
+        let selection_model = NoSelection::new(Some(self.tasks()));
+        self.imp().tasks_list.set_model(Some(&selection_model));
+    }
+
+    fn setup_callbacks(&self) {
+        // Setup callback for activation of the entry
+        self.imp()
+            .entry
+            .connect_activate(clone!(@weak self as window => move |_| {
+                window.new_task();
+            }));
+
+        // Setup callback for clicking (and the releasing) the icon of the entry
+        self.imp().entry.connect_icon_release(
+            clone!(@weak self as window => move |_,_| {
+                window.new_task();
+            }),
+        );
+    }
+
+    fn new_task(&self) {
+        // Get content from entry and clear it
+        let buffer = self.imp().entry.buffer();
+        let content = buffer.text().to_string();
+        if content.is_empty() {
+            return;
+        }
+        buffer.set_text("");
+
+        // Add new task to model
+        let task = TaskObject::new(false, content);
+        self.tasks().append(&task);
+    }
+
+    fn setup_factory(&self) {
+        // Create a new factory
+        let factory = SignalListItemFactory::new();
+
+        // Create an empty `TaskRow` during setup
+        factory.connect_setup(move |_, list_item| {
+            // Create `TaskRow`
+            let task_row = TaskRow::new();
+            list_item
+                .downcast_ref::<ListItem>()
+                .expect("Needs to be ListItem")
+                .set_child(Some(&task_row));
+        });
+
+        // Tell factory how to bind `TaskRow` to a `TaskObject`
+        factory.connect_bind(move |_, list_item| {
+            // Get `TaskObject` from `ListItem`
+            let task_object = list_item
+                .downcast_ref::<ListItem>()
+                .expect("Needs to be ListItem")
+                .item()
+                .and_downcast::<TaskObject>()
+                .expect("The item has to be an `TaskObject`.");
+
+            // Get `TaskRow` from `ListItem`
+            let task_row = list_item
+                .downcast_ref::<ListItem>()
+                .expect("Needs to be ListItem")
+                .child()
+                .and_downcast::<TaskRow>()
+                .expect("The child has to be a `TaskRow`.");
+
+            task_row.bind(&task_object);
+        });
+
+        // Tell factory how to unbind `TaskRow` from `TaskObject`
+        factory.connect_unbind(move |_, list_item| {
+            // Get `TaskRow` from `ListItem`
+            let task_row = list_item
+                .downcast_ref::<ListItem>()
+                .expect("Needs to be ListItem")
+                .child()
+                .and_downcast::<TaskRow>()
+                .expect("The child has to be a `TaskRow`.");
+
+            task_row.unbind();
+        });
+
+        // Set the factory of the list view
+        self.imp().tasks_list.set_factory(Some(&factory));
+    }
+}

In Window::setup_callbacks we connect to the "activate" signal of the entry. This signal is triggered when we press the enter key in the entry. Then a new TaskObject with the content will be created and appended to the model. diff --git a/stable/latest/book/searchindex.js b/stable/latest/book/searchindex.js index 9bcda17a19cc..b93b47176495 100644 --- a/stable/latest/book/searchindex.js +++ b/stable/latest/book/searchindex.js @@ -1 +1 @@ -Object.assign(window.search, {"doc_urls":["introduction.html#gui-development-with-rust-and-gtk-4","introduction.html#who-this-book-is-for","introduction.html#how-to-use-this-book","introduction.html#license","installation.html#installation","installation_linux.html#linux","installation_macos.html#macos","installation_windows.html#windows","installation_windows.html#install-rustup","installation_windows.html#install-gtk-4","installation_windows.html#set-rust-toolchain-to-msvc","installation_windows.html#build-gtk-4","installation_windows.html#update-path-environment-variable","installation_windows.html#set-rust-toolchain-to-msvc-1","installation_windows.html#visual-studio","installation_windows.html#git","installation_windows.html#cmake","installation_windows.html#python","installation_windows.html#meson","installation_windows.html#gettext-021","installation_windows.html#pkg-config","installation_windows.html#update-environment-variables","installation_windows.html#compile-and-install-gtk-4","installation_windows.html#install-rustup-1","installation_windows.html#remove-residues-from-the-msvc-toolchain","installation_windows.html#msys2","installation_windows.html#install-gtk-4-1","installation_windows.html#update-path-environment-variable-1","installation_windows.html#setup-the-gnu-toolchain-for-rust","project_setup.html#project-setup","hello_world.html#hello-world","widgets.html#widgets","g_object_concepts.html#gobject-concepts","g_object_memory_management.html#memory-management","g_object_subclassing.html#subclassing","g_object_subclassing.html#adding-functionality","g_object_values.html#generic-values","g_object_values.html#value","g_object_values.html#variant","g_object_properties.html#properties","g_object_properties.html#adding-properties-to-custom-gobjects","g_object_signals.html#signals","g_object_signals.html#adding-signals-to-custom-gobjects","main_event_loop.html#the-main-event-loop","main_event_loop.html#how-to-avoid-blocking-the-main-loop","settings.html#settings","saving_window_state.html#saving-window-state","list_widgets.html#list-widgets","list_widgets.html#views","list_widgets.html#expressions","list_widgets.html#string-list","list_widgets.html#conclusion","composite_templates.html#composite-templates","composite_templates.html#resources","composite_templates.html#custom-widgets","composite_templates.html#template-callbacks","composite_templates.html#registering-types","composite_templates.html#conclusion","todo_1.html#building-a-simple-to-do-app","todo_1.html#window","todo_1.html#task-object","todo_1.html#task-row","actions.html#actions","actions.html#parameter-and-state","actions.html#actionable","actions.html#menus","actions.html#settings","todo_2.html#manipulating-state-of-to-do-app","todo_2.html#filtering-tasks","todo_2.html#saving-and-restoring-tasks","css.html#css","css.html#style-classes-applied-by-gtk","css.html#adding-your-own-style-class","css.html#specifying-name-of-a-widget","css.html#css-rules-provided-by-gtk","css.html#interface-builder","css.html#pseudo-classes","css.html#nodes","css.html#set-css-name-and-use-exported-colors","css.html#adapt-todo-app","css.html#conclusion","libadwaita.html#libadwaita","libadwaita.html#linux","libadwaita.html#macos","libadwaita.html#windows","libadwaita.html#if-using-gvsbuild","libadwaita.html#if-building-manually-with-msvc","libadwaita.html#work-around-missing-icons","libadwaita.html#gvsbuild","libadwaita.html#manually-with-msvc","todo_3.html#let-to-do-app-use-libadwaita","todo_3.html#boxed-lists","todo_4.html#adding-collections","todo_4.html#sidebar","todo_4.html#placeholder-page","todo_4.html#collections","todo_4.html#window","todo_4.html#leaflet-and-dialog"],"index":{"documentStore":{"docInfo":{"0":{"body":83,"breadcrumbs":6,"title":5},"1":{"body":43,"breadcrumbs":2,"title":1},"10":{"body":9,"breadcrumbs":6,"title":4},"11":{"body":17,"breadcrumbs":5,"title":3},"12":{"body":48,"breadcrumbs":6,"title":4},"13":{"body":9,"breadcrumbs":6,"title":4},"14":{"body":15,"breadcrumbs":4,"title":2},"15":{"body":3,"breadcrumbs":3,"title":1},"16":{"body":3,"breadcrumbs":3,"title":1},"17":{"body":12,"breadcrumbs":3,"title":1},"18":{"body":7,"breadcrumbs":3,"title":1},"19":{"body":9,"breadcrumbs":4,"title":2},"2":{"body":78,"breadcrumbs":3,"title":2},"20":{"body":14,"breadcrumbs":4,"title":2},"21":{"body":39,"breadcrumbs":5,"title":3},"22":{"body":117,"breadcrumbs":6,"title":4},"23":{"body":5,"breadcrumbs":4,"title":2},"24":{"body":14,"breadcrumbs":6,"title":4},"25":{"body":3,"breadcrumbs":3,"title":1},"26":{"body":49,"breadcrumbs":5,"title":3},"27":{"body":21,"breadcrumbs":6,"title":4},"28":{"body":36,"breadcrumbs":6,"title":4},"29":{"body":71,"breadcrumbs":4,"title":2},"3":{"body":17,"breadcrumbs":2,"title":1},"30":{"body":269,"breadcrumbs":4,"title":2},"31":{"body":194,"breadcrumbs":2,"title":1},"32":{"body":46,"breadcrumbs":4,"title":2},"33":{"body":1342,"breadcrumbs":6,"title":2},"34":{"body":309,"breadcrumbs":4,"title":1},"35":{"body":215,"breadcrumbs":5,"title":2},"36":{"body":24,"breadcrumbs":6,"title":2},"37":{"body":333,"breadcrumbs":5,"title":1},"38":{"body":184,"breadcrumbs":5,"title":1},"39":{"body":310,"breadcrumbs":4,"title":1},"4":{"body":26,"breadcrumbs":2,"title":1},"40":{"body":833,"breadcrumbs":7,"title":4},"41":{"body":227,"breadcrumbs":4,"title":1},"42":{"body":457,"breadcrumbs":7,"title":4},"43":{"body":160,"breadcrumbs":6,"title":3},"44":{"body":708,"breadcrumbs":7,"title":4},"45":{"body":682,"breadcrumbs":2,"title":1},"46":{"body":356,"breadcrumbs":6,"title":3},"47":{"body":195,"breadcrumbs":4,"title":2},"48":{"body":1359,"breadcrumbs":3,"title":1},"49":{"body":911,"breadcrumbs":3,"title":1},"5":{"body":44,"breadcrumbs":3,"title":1},"50":{"body":294,"breadcrumbs":4,"title":2},"51":{"body":35,"breadcrumbs":3,"title":1},"52":{"body":215,"breadcrumbs":4,"title":2},"53":{"body":598,"breadcrumbs":3,"title":1},"54":{"body":282,"breadcrumbs":4,"title":2},"55":{"body":581,"breadcrumbs":4,"title":2},"56":{"body":268,"breadcrumbs":4,"title":2},"57":{"body":73,"breadcrumbs":3,"title":1},"58":{"body":24,"breadcrumbs":6,"title":3},"59":{"body":534,"breadcrumbs":4,"title":1},"6":{"body":20,"breadcrumbs":3,"title":1},"60":{"body":221,"breadcrumbs":5,"title":2},"61":{"body":1450,"breadcrumbs":5,"title":2},"62":{"body":410,"breadcrumbs":2,"title":1},"63":{"body":208,"breadcrumbs":3,"title":2},"64":{"body":486,"breadcrumbs":2,"title":1},"65":{"body":671,"breadcrumbs":2,"title":1},"66":{"body":1063,"breadcrumbs":2,"title":1},"67":{"body":0,"breadcrumbs":6,"title":3},"68":{"body":2464,"breadcrumbs":5,"title":2},"69":{"body":1042,"breadcrumbs":6,"title":3},"7":{"body":24,"breadcrumbs":3,"title":1},"70":{"body":208,"breadcrumbs":2,"title":1},"71":{"body":47,"breadcrumbs":5,"title":4},"72":{"body":153,"breadcrumbs":4,"title":3},"73":{"body":154,"breadcrumbs":4,"title":3},"74":{"body":103,"breadcrumbs":5,"title":4},"75":{"body":80,"breadcrumbs":3,"title":2},"76":{"body":122,"breadcrumbs":3,"title":2},"77":{"body":107,"breadcrumbs":2,"title":1},"78":{"body":428,"breadcrumbs":7,"title":6},"79":{"body":36,"breadcrumbs":4,"title":3},"8":{"body":5,"breadcrumbs":4,"title":2},"80":{"body":52,"breadcrumbs":2,"title":1},"81":{"body":100,"breadcrumbs":2,"title":1},"82":{"body":21,"breadcrumbs":2,"title":1},"83":{"body":3,"breadcrumbs":2,"title":1},"84":{"body":0,"breadcrumbs":2,"title":1},"85":{"body":9,"breadcrumbs":3,"title":2},"86":{"body":43,"breadcrumbs":4,"title":3},"87":{"body":6,"breadcrumbs":5,"title":4},"88":{"body":16,"breadcrumbs":2,"title":1},"89":{"body":11,"breadcrumbs":3,"title":2},"9":{"body":6,"breadcrumbs":5,"title":3},"90":{"body":547,"breadcrumbs":7,"title":3},"91":{"body":2067,"breadcrumbs":6,"title":2},"92":{"body":0,"breadcrumbs":5,"title":2},"93":{"body":1171,"breadcrumbs":4,"title":1},"94":{"body":225,"breadcrumbs":5,"title":2},"95":{"body":281,"breadcrumbs":4,"title":1},"96":{"body":5907,"breadcrumbs":4,"title":1},"97":{"body":3173,"breadcrumbs":5,"title":2}},"docs":{"0":{"body":"by Julian Hofer, with contributions from the community GTK 4 is the newest version of a popular cross-platform widget toolkit written in C. Thanks to GObject-Introspection, GTK's API can be easily targeted by various programming languages. The API even describes the ownership of its parameters! Managing ownership without giving up speed is one of Rust's greatest strengths, which makes it an excellent choice to develop GTK apps with. With this combination you don't have to worry about hitting bottlenecks mid-project anymore. Additionally, with Rust you will have nice things such as thread safety, memory safety, sensible dependency management as well as excellent third party libraries. The gtk-rs project provides bindings to many GTK-related libraries which we will be using throughout this book.","breadcrumbs":"Introduction » GUI development with Rust and GTK 4","id":"0","title":"GUI development with Rust and GTK 4"},"1":{"body":"This book assumes that you know your way around Rust code. If this is not already the case, reading The Rust Programming Language is an enjoyable way to get you to that stage. If you have experience with another low-level language such as C or C++ you might find that reading A half hour to learn Rust gives you sufficient information as well. Luckily, this — together with the wish to develop graphical applications — is all that is necessary to benefit from this book.","breadcrumbs":"Introduction » Who this book is for","id":"1","title":"Who this book is for"},"10":{"body":"Set the Rust toolchain to MSVC by executing: rustup default stable-msvc","breadcrumbs":"Installation » Windows » Set Rust toolchain to MSVC","id":"10","title":"Set Rust toolchain to MSVC"},"11":{"body":"Follow the gvsbuild docs to build GTK 4 . When choosing the GTK version to build, select gtk4 instead of gtk3: gvsbuild build gtk4","breadcrumbs":"Installation » Windows » Build GTK 4","id":"11","title":"Build GTK 4"},"12":{"body":"Update your Path environment variable to include the GTK 4 libraries: Go to settings -> Search and open Advanced system settings -> Click on Environment variables Select Path -> Click on Edit -> Add C:\\gtk-build\\gtk\\x64\\release\\bin You can now continue with the project setup . Build GTK 4 manually with MSVC If it's not possible to build with gvsbuild (or you want to customize your build), you can build GTK 4 and the minimum dependencies you need manually.","breadcrumbs":"Installation » Windows » Update Path environment variable","id":"12","title":"Update Path environment variable"},"13":{"body":"Set the Rust toolchain to MSVC by executing: rustup default stable-msvc","breadcrumbs":"Installation » Windows » Set Rust toolchain to MSVC","id":"13","title":"Set Rust toolchain to MSVC"},"14":{"body":"Install Visual Studio Community from visualstudio.microsoft.com . Make sure to check the box \"Desktop development with C++\" during the installation process.","breadcrumbs":"Installation » Windows » Visual Studio","id":"14","title":"Visual Studio"},"15":{"body":"Download git from gitforwindows.org .","breadcrumbs":"Installation » Windows » Git","id":"15","title":"Git"},"16":{"body":"Download CMake from https://cmake.org/download/","breadcrumbs":"Installation » Windows » CMake","id":"16","title":"CMake"},"17":{"body":"Download python from python.org . Make sure to opt-in to adding Python to your Path during the installation process.","breadcrumbs":"Installation » Windows » Python","id":"17","title":"Python"},"18":{"body":"Install meson by executing: pip install meson ninja","breadcrumbs":"Installation » Windows » Meson","id":"18","title":"Meson"},"19":{"body":"Download Gettext 0.21 from mlocati.github.io . Make sure to select the static version.","breadcrumbs":"Installation » Windows » Gettext 0.21","id":"19","title":"Gettext 0.21"},"2":{"body":"In general, this book assumes that you are reading it in sequence from front to back. However, if you are using it as a reference for a certain topic, you might find it useful to just jump into it. There are two kinds of chapters in this book: concept chapters and project chapters. In concept chapters, you will learn about an aspect of GTK development. In project chapters, we will build small programs together, applying what you've learned so far. The book strives to explain essential GTK concepts paired with practical examples. However, if a concept can be better conveyed with a less practical example, we took this path most of the time. If you are interested in contained and useful examples, we refer you to the corresponding section of gtk4-rs' repository . Every valid code snippet in the book is part of a listing. Like the examples, the listings be found in the repository of gtk4-rs.","breadcrumbs":"Introduction » How to use this book","id":"2","title":"How to use this book"},"20":{"body":"Download pkg-config-lite from sourceforge.net . Then extract and unpack it in C:/, so that the executable is in C:\\pkg-config-lite-0.28-1\\bin.","breadcrumbs":"Installation » Windows » Pkg-config","id":"20","title":"Pkg-config"},"21":{"body":"Go to settings -> Search and open Advanced system settings -> Click on Environment variables Select Path -> Click on Edit -> Add the following entries: C:\\pkg-config-lite-0.28-1\\bin\nC:\\gnome\\bin Go back to Environment variables Under User variables click on New and add: Variable name: PKG_CONFIG_PATH Variable value: C:\\gnome\\lib\\pkgconfig","breadcrumbs":"Installation » Windows » Update environment variables","id":"21","title":"Update environment variables"},"22":{"body":"From the Windows start menu, search for x64 Native Tools Command Prompt for VS 2019. That will open a terminal configured to use MSVC x64 tools. From there, run the following commands: cd /\ngit clone https://gitlab.gnome.org/GNOME/gtk.git --depth 1\ngit clone https://gitlab.gnome.org/GNOME/libxml2.git --depth 1\ngit clone https://gitlab.gnome.org/GNOME/librsvg.git --depth 1 :: Make sure that cmd finds pkg-config-lite when searching for pkg-config\nwhere pkg-config cd gtk\nmeson setup builddir --prefix=C:/gnome -Dbuild-tests=false -Dmedia-gstreamer=disabled\nmeson install -C builddir\ncd / cd libxml2\ncmake -S . -B build -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=C:\\gnome -D LIBXML2_WITH_ICONV=OFF -D LIBXML2_WITH_LZMA=OFF -D LIBXML2_WITH_PYTHON=OFF -D LIBXML2_WITH_ZLIB=OFF\ncmake --build build --config Release\ncmake --install build\ncd / cd librsvg/win32\nnmake /f generate-msvc.mak generate-nmake-files\nnmake /f Makefile.vc CFG=release install PREFIX=C:\\gnome\ncd / You can now continue with the project setup . Install GTK 4 with MSYS2 and the GNU toolchain","breadcrumbs":"Installation » Windows » Compile and install GTK 4","id":"22","title":"Compile and install GTK 4"},"23":{"body":"Install the rust toolchain via rustup .","breadcrumbs":"Installation » Windows » Install Rustup","id":"23","title":"Install Rustup"},"24":{"body":"If you used the MSVC toolchain before, make sure to revert all changes you made to environment variables during the installation process.","breadcrumbs":"Installation » Windows » Remove residues from the MSVC toolchain","id":"24","title":"Remove residues from the MSVC toolchain"},"25":{"body":"Install MSYS2 from www.msys2.org","breadcrumbs":"Installation » Windows » MSYS2","id":"25","title":"MSYS2"},"26":{"body":"From the Windows start menu, search for MSYS2 MinGW 64-bit. That will open a terminal configured to use MinGW x64 tools. There, execute the following commands to install GTK 4, pkgconf and gcc. pacman -S mingw-w64-x86_64-gtk4 mingw-w64-x86_64-gettext mingw-w64-x86_64-libxml2 mingw-w64-x86_64-librsvg mingw-w64-x86_64-pkgconf mingw-w64-x86_64-gcc","breadcrumbs":"Installation » Windows » Install GTK 4","id":"26","title":"Install GTK 4"},"27":{"body":"Go to settings -> Search and open Advanced system settings -> Click on Environment variables Select Path -> Click on Edit -> Add the following three entries: C:\\msys64\\mingw64\\include\nC:\\msys64\\mingw64\\bin\nC:\\msys64\\mingw64\\lib","breadcrumbs":"Installation » Windows » Update Path environment variable","id":"27","title":"Update Path environment variable"},"28":{"body":"The default toolchain on windows is stable-msvc. To switch to stable-gnu, run the following commands from your terminal: rustup toolchain install stable-gnu rustup default stable-gnu Please note that this command might change in the future. If it does not work anymore, please open an issue on our repo. You can now continue with the project setup .","breadcrumbs":"Installation » Windows » Setup the GNU toolchain for Rust","id":"28","title":"Setup the GNU toolchain for Rust"},"29":{"body":"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 . Now, create a new project by executing: cargo new my-gtk-app Find out the GTK 4 version on your machine by running pkg-config --modversion gtk4 Use this information to add the gtk4 crate to your dependencies in Cargo.toml. At the time of this writing the newest version is 4.8. cargo add gtk4 --rename gtk --features v4_8 By specifying this feature you opt-in to API that was added with minor releases of GTK 4. Now, you can run your application by executing: cargo run","breadcrumbs":"Project Setup » Project Setup","id":"29","title":"Project Setup"},"3":{"body":"The book itself is licensed under the Creative Commons Attribution 4.0 International license . The only exception are the code snippets which are licensed under the MIT license .","breadcrumbs":"Introduction » License","id":"3","title":"License"},"30":{"body":"Now that we've got a working installation, let's get right into it! At the very least, we need to create a gtk::Application instance with an application id . For that we use the builder pattern which many gtk-rs objects support. Note that we also import the prelude to bring the necessary traits into scope. Filename: listings/hello_world/1/main.rs use gtk::prelude::*;\nuse gtk::{glib, Application}; const APP_ID: &str = \"org.gtk_rs.HelloWorld1\"; fn main() -> glib::ExitCode { // Create a new application let app = Application::builder().application_id(APP_ID).build(); // Run the application app.run()\n} It builds fine, but nothing but a warning in our terminal appears. GLib-GIO-WARNING: Your application does not implement g_application_activate()\nand has no handlers connected to the 'activate' signal. It should do one of these. GTK tells us that something should be called in its activate step. So let's create a gtk::ApplicationWindow there. Filename: listings/hello_world/2/main.rs use gtk::prelude::*;\nuse gtk::{glib, Application, ApplicationWindow}; const APP_ID: &str = \"org.gtk_rs.HelloWorld2\"; 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()\n} fn build_ui(app: &Application) { // Create a window and set the title let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .build(); // Present window window.present();\n} That is better! Normally we expect to be able to interact with the user interface. Also, the name of the chapter suggests that the phrase \"Hello World!\" will be involved. Filename: listings/hello_world/3/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# const APP_ID: &str = \"org.gtk_rs.HelloWorld3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# fn build_ui(app: &Application) { // Create a button with label and margins let button = Button::builder() .label(\"Press me!\") .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); // Connect to \"clicked\" signal of `button` button.connect_clicked(|button| { // Set the label to \"Hello World!\" after the button has been clicked on button.set_label(\"Hello World!\"); }); // Create a window let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .child(&button) .build(); // Present window window.present();\n} If you look closely at the code snippet you will notice that it has a small eye symbol on its top right. After you press on it you can see the full code of the listing. We will use this throughout the book to hide details which are not important to bring the message across. Pay attention to this if you want to write apps by following the book step-by-step. Here, we've hidden that we brought gtk::Button into scope. There is now a button and if we click on it, its label becomes \"Hello World!\". A video which shows that pressing on the button changes it's label Wasn't that hard to create our first gtk-rs app, right? Let's now get a better understanding of what we did here.","breadcrumbs":"Hello World! » Hello World!","id":"30","title":"Hello World!"},"31":{"body":"Widgets are the components that make up a GTK application. GTK offers many widgets and if those don't fit, you can even create custom ones. There are, for example, display widgets, buttons, containers and windows. One kind of widget might be able to contain other widgets, it might present information and it might react to interaction. The Widget Gallery is useful to find out which widget fits your needs. Let's say we want to add a button to our app. We have quite a bit of choice here, but let's take the simplest one — a Button. GTK is an object-oriented framework, so all widgets are part of an inheritance tree with GObject at the top. The inheritance tree of a Button looks like this: GObject\n╰── Widget ╰── Button The GTK documentation also tells us that Button implements the interfaces GtkAccessible, GtkActionable, GtkBuildable, GtkConstraintTarget. Now let's compare that with the corresponding Button struct in gtk-rs. The gtk-rs documentation tells us which traits it implements. We find that these traits either have a corresponding base class or interface in the GTK docs. In the \"Hello World\" app we wanted to react to a button click. This behavior is specific to a button, so we expect to find a suitable method in the ButtonExt trait. And indeed, ButtonExt includes the method connect_clicked . Filename: listings/hello_world/3/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# const APP_ID: &str = \"org.gtk_rs.HelloWorld3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Create a button with label and margins let button = Button::builder() .label(\"Press me!\") .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); // Connect to \"clicked\" signal of `button` button.connect_clicked(|button| { // Set the label to \"Hello World!\" after the button has been clicked on button.set_label(\"Hello World!\"); });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# }","breadcrumbs":"Widgets » Widgets","id":"31","title":"Widgets"},"32":{"body":"GTK is an object-oriented framework. It is written in C, which does not support object-orientation out of the box. That is why GTK relies on the GObject library to provide the object system. We have already learned that gtk-rs maps GObject concepts, like inheritance and interfaces, to Rust traits. In this chapter we will learn: How memory of GObjects is managed How to create our own GObjects via subclassing How to deal with generic values How to use properties How to emit and receive signals","breadcrumbs":"GObject Concepts » GObject Concepts","id":"32","title":"GObject Concepts"},"33":{"body":"Memory management when writing a gtk-rs app can be a bit tricky. Let's have a look why that is the case and how to deal with that. With our first example, we have window with a single button. Every button click should increment an integer number by one. #use gtk::prelude::*;\n#use gtk::{self, glib, Application, ApplicationWindow, Button};\n#\n#const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement0\";\n#\n// DOES NOT COMPILE!\nfn 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()\n} fn build_ui(application: &Application) { // Create two buttons let button_increase = Button::builder() .label(\"Increase\") .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); // A mutable integer let mut number = 0; // Connect callbacks // When a button is clicked, `number` should be changed button_increase.connect_clicked(|_| number += 1); // Create a window let window = ApplicationWindow::builder() .application(application) .title(\"My GTK App\") .child(&button_increase) .build(); // Present the window window.present();\n} The Rust compiler refuses to compile this application while spitting our multiple error messages. Let's have a look at them one by one. error[E0373]: closure may outlive the current function, but it borrows `number`, which is owned by the current function |\n32 | button_increase.connect_clicked(|_| number += 1); | ^^^ ------ `number` is borrowed here | | | may outlive borrowed value `number` |\nnote: function requires argument type to outlive `'static` |\n32 | button_increase.connect_clicked(|_| number += 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nhelp: to force the closure to take ownership of `number` (and any other referenced variables), use the `move` keyword |\n32 | button_increase.connect_clicked(move |_| number += 1); | Our closure only borrows number. Signal handlers in GTK require static' lifetimes for their references, so we cannot borrow a variable that only lives for the scope of the function build_ui. The compiler also suggests how to fix this. By adding the move keyword in front of the closure, number will be moved into the closure. #use gtk::prelude::*;\n#use gtk::{self, glib, Application, ApplicationWindow, Button};\n#\n#const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement0\";\n#\n#fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n#\n# // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n#\n# // Run the application\n# app.run()\n#}\n#\n#fn build_ui(application: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // DOES NOT COMPILE! // A mutable integer let mut number = 0; // Connect callbacks // When a button is clicked, `number` should be changed button_increase.connect_clicked(move |_| number += 1);\n#\n# // Create a window\n# let window = ApplicationWindow::builder()\n# .application(application)\n# .title(\"My GTK App\")\n# .child(&button_increase)\n# .build();\n#\n# // Present the window\n# window.present();\n#} This still leaves the following error message: error[E0594]: cannot assign to `number`, as it is a captured variable in a `Fn` closure |\n32 | button_increase.connect_clicked(move |_| number += 1); | ^^^^^^^^^^^ cannot assign In order to understand that error message we have to understand the difference between the three closure traits FnOnce, FnMut and Fn. APIs that take closures implementing the FnOnce trait give the most freedom to the API consumer. The closure is called only once, so it can even consume its state. Signal handlers can be called multiple times, so they cannot accept FnOnce. The more restrictive FnMut trait doesn't allow closures to consume their state, but they can still mutate it. Signal handlers can't allow this either, because they can be called from inside themselves. This would lead to multiple mutable references which the borrow checker doesn't appreciate at all. This leaves Fn. State can be immutably borrowed, but then how can we modify number? We need a data type with interior mutability like std::cell::Cell . The Cell class is only suitable for objects that implement the Copy trait. For other objects, RefCell is the way to go. You can learn more about interior mutability in this section of the book Rust Atomics and Locks . Filename: listings/g_object_memory_management/1/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# use std::cell::Cell;\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# fn build_ui(application: &Application) { // Create two buttons let button_increase = Button::builder() .label(\"Increase\") .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); // A mutable integer let number = Cell::new(0); // Connect callbacks // When a button is clicked, `number` should be changed button_increase.connect_clicked(move |_| number.set(number.get() + 1)); // Create a window let window = ApplicationWindow::builder() .application(application) .title(\"My GTK App\") .child(&button_increase) .build(); // Present the window window.present();\n} This now compiles as expected. Let's try a slightly more complicated example: two buttons which both modify the same number. For that, we need a way that both closures take ownership of the same value? That is exactly what the std::rc::Rc type is there for. Rc counts the number of strong references created via Clone::clone and released via Drop::drop , and only deallocates the value when this number drops to zero. If we want to modify the content of our Rc , we can again use the Cell type. Filename: listings/g_object_memory_management/2/main.rs # use std::cell::Cell;\n# use std::rc::Rc;\n# # use gtk::prelude::*;\n# use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement2\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# fn build_ui(app: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let button_decrease = Button::builder()\n# .label(\"Decrease\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // Reference-counted object with inner-mutability let number = Rc::new(Cell::new(0)); // Connect callbacks, when a button is clicked `number` will be changed let number_copy = number.clone(); button_increase.connect_clicked(move |_| number_copy.set(number_copy.get() + 1)); button_decrease.connect_clicked(move |_| number.set(number.get() - 1));\n# # // Add buttons to `gtk_box`\n# let gtk_box = gtk::Box::builder()\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_increase);\n# gtk_box.append(&button_decrease);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } It is not very nice though to fill the scope with temporary variables like number_copy. We can improve that by using the glib::clone! macro. Filename: listings/g_object_memory_management/3/main.rs # use std::cell::Cell;\n# use std::rc::Rc;\n# # use glib::clone;\n# use gtk::prelude::*;\n# use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# fn build_ui(app: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let button_decrease = Button::builder()\n# .label(\"Decrease\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# # // Reference-counted object with inner mutability\n# let number = Rc::new(Cell::new(0));\n# // Connect callbacks\n# // When a button is clicked, `number` will be changed button_increase.connect_clicked(clone!(@strong number => move |_| { number.set(number.get() + 1); })); button_decrease.connect_clicked(move |_| { number.set(number.get() - 1); });\n# # // Add buttons to `gtk_box`\n# let gtk_box = gtk::Box::builder()\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_increase);\n# gtk_box.append(&button_decrease);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } Just like Rc>, GObjects are reference-counted and mutable. Therefore, we can pass the buttons the same way to the closure as we did with number. Filename: listings/g_object_memory_management/4/main.rs # use std::cell::Cell;\n# use std::rc::Rc;\n# # use glib::clone;\n# use gtk::prelude::*;\n# use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement4\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let button_decrease = Button::builder()\n# .label(\"Decrease\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# # let number = Rc::new(Cell::new(0));\n# // Connect callbacks // When a button is clicked, `number` and label of the other button will be changed button_increase.connect_clicked(clone!(@weak number, @strong button_decrease => move |_| { number.set(number.get() + 1); button_decrease.set_label(&number.get().to_string()); })); button_decrease.connect_clicked(clone!(@strong button_increase => move |_| { number.set(number.get() - 1); button_increase.set_label(&number.get().to_string()); }));\n# # // Add buttons to `gtk_box`\n# let gtk_box = gtk::Box::builder()\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_increase);\n# gtk_box.append(&button_decrease);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } If we now click on one button, the other button's label gets changed. But whoops! Did we forget about one annoyance of reference-counted systems? Yes we did: reference cycles . button_increase holds a strong reference to button_decrease and vice-versa. A strong reference keeps the referenced value from being deallocated. If this chain leads to a circle, none of the values in this cycle ever get deallocated. With weak references we can break this cycle, because they don't keep their value alive but instead provide a way to retrieve a strong reference if the value is still alive. Since we want our apps to free unneeded memory, we should use weak references for the buttons instead. Filename: listings/g_object_memory_management/5/main.rs # use std::cell::Cell;\n# use std::rc::Rc;\n# # use glib::clone;\n# use gtk::prelude::*;\n# use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement5\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let button_decrease = Button::builder()\n# .label(\"Decrease\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# # // Reference-counted object with inner mutability\n# let number = Rc::new(Cell::new(0));\n# // Connect callbacks // When a button is clicked, `number` and label of the other button will be changed button_increase.connect_clicked(clone!(@weak number, @weak button_decrease => move |_| { number.set(number.get() + 1); button_decrease.set_label(&number.get().to_string()); })); button_decrease.connect_clicked(clone!(@weak button_increase => move |_| { number.set(number.get() - 1); button_increase.set_label(&number.get().to_string()); }));\n# # // Add buttons to `gtk_box`\n# let gtk_box = gtk::Box::builder()\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_increase);\n# gtk_box.append(&button_decrease);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } The reference cycle is broken. Every time the button is clicked, glib::clone tries to upgrade the weak reference. If we now for example click on one button and the other button is not there anymore, the callback will be skipped. Per default, it immediately returns from the closure with () as return value. In case the closure expects a different return value @default-return can be specified. Notice that we move number in the second closure. If we had moved weak references in both closures, nothing would have kept number alive and the closure would have never been called. Thinking about this, button_increase and button_decrease are also dropped at the end of the scope of build_ui. Who then keeps the buttons alive? Filename: listings/g_object_memory_management/5/main.rs # use std::cell::Cell;\n# use std::rc::Rc;\n# # use glib::clone;\n# use gtk::prelude::*;\n# use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement5\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let button_decrease = Button::builder()\n# .label(\"Decrease\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# # // Reference-counted object with inner mutability\n# let number = Rc::new(Cell::new(0));\n# # // Connect callbacks\n# // When a button is clicked, `number` and label of the other button will be changed\n# button_increase.connect_clicked(clone!(@weak number, @weak button_decrease =>\n# move |_| {\n# number.set(number.get() + 1);\n# button_decrease.set_label(&number.get().to_string());\n# }));\n# button_decrease.connect_clicked(clone!(@weak button_increase =>\n# move |_| {\n# number.set(number.get() - 1);\n# button_increase.set_label(&number.get().to_string());\n# }));\n# // Add buttons to `gtk_box` let gtk_box = gtk::Box::builder() .orientation(Orientation::Vertical) .build(); gtk_box.append(&button_increase); gtk_box.append(&button_decrease);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } When we append the buttons to the gtk_box, gtk_box keeps a strong reference to them. Filename: listings/g_object_memory_management/5/main.rs # use std::cell::Cell;\n# use std::rc::Rc;\n# # use glib::clone;\n# use gtk::prelude::*;\n# use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectMemoryManagement5\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create two buttons\n# let button_increase = Button::builder()\n# .label(\"Increase\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let button_decrease = Button::builder()\n# .label(\"Decrease\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# # // Reference-counted object with inner mutability\n# let number = Rc::new(Cell::new(0));\n# # // Connect callbacks\n# // When a button is clicked, `number` and label of the other button will be changed\n# button_increase.connect_clicked(clone!(@weak number, @weak button_decrease =>\n# move |_| {\n# number.set(number.get() + 1);\n# button_decrease.set_label(&number.get().to_string());\n# }));\n# button_decrease.connect_clicked(clone!(@weak button_increase =>\n# move |_| {\n# number.set(number.get() - 1);\n# button_increase.set_label(&number.get().to_string());\n# }));\n# # // Add buttons to `gtk_box`\n# let gtk_box = gtk::Box::builder()\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_increase);\n# gtk_box.append(&button_decrease);\n# // Create a window let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .child(>k_box) .build();\n# # // Present the window\n# window.present();\n# } When we set gtk_box as child of window, window keeps a strong reference to it. Until we close the window it keeps gtk_box and with it the buttons alive. Since our application has only one window, closing it also means exiting the application. As long as you use weak references whenever possible, you will find it perfectly doable to avoid memory cycles within your application. Without memory cycles, you can rely on GTK to properly manage the memory of GObjects you pass to it.","breadcrumbs":"GObject Concepts » Memory Management » Memory Management","id":"33","title":"Memory Management"},"34":{"body":"GObjects rely heavily on inheritance. Therefore, it makes sense that if we want to create a custom GObject, this is done via subclassing. Let's see how this works by replacing the button in our \"Hello World!\" app with a custom one. First, we need to create an implementation struct that holds the state and overrides the virtual methods. Filename: listings/g_object_subclassing/1/custom_button/imp.rs use gtk::glib;\nuse gtk::subclass::prelude::*; // Object holding the state\n#[derive(Default)]\npub struct CustomButton; // The central trait for subclassing a GObject\n#[glib::object_subclass]\nimpl ObjectSubclass for CustomButton { const NAME: &'static str = \"MyGtkAppCustomButton\"; type Type = super::CustomButton; type ParentType = gtk::Button;\n} // Trait shared by all GObjects\nimpl ObjectImpl for CustomButton {} // Trait shared by all widgets\nimpl WidgetImpl for CustomButton {} // Trait shared by all buttons\nimpl ButtonImpl for CustomButton {} The description of the subclassing is in ObjectSubclass. NAME should consist of crate-name and object-name in order to avoid name collisions. Use UpperCamelCase here. Type refers to the actual GObject that will be created afterwards. ParentType is the GObject we inherit of. After that, we would have the option to override the virtual methods of our ancestors. Since we only want to have a plain button for now, we override nothing. We still have to add the empty impl though. Next, we describe the public interface of our custom GObject. Filename: listings/g_object_subclassing/1/custom_button/mod.rs mod imp; use glib::Object;\nuse gtk::glib; glib::wrapper! { pub struct CustomButton(ObjectSubclass) @extends gtk::Button, gtk::Widget, @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;\n} impl CustomButton { pub fn new() -> Self { Object::builder().build() } pub fn with_label(label: &str) -> Self { Object::builder().property(\"label\", label).build() }\n}\n# # impl Default for CustomButton {\n# fn default() -> Self {\n# Self::new()\n# }\n# } glib::wrapper! implements the same traits that our ParentType implements. Theoretically that would mean that the ParentType is also the only thing we have to specify here. Unfortunately, nobody has yet found a good way to do that. Which is why, as of today, subclassing of GObjects in Rust requires to mention all ancestors and interfaces apart from GObject and GInitiallyUnowned. For gtk::Button, we can look up the ancestors and interfaces in the corresponding doc page of GTK4. After these steps, nothing is stopping us from replacing gtk::Button with our CustomButton. Filename: listings/g_object_subclassing/1/main.rs mod custom_button; use custom_button::CustomButton;\nuse gtk::prelude::*;\nuse gtk::{glib, Application, ApplicationWindow}; const APP_ID: &str = \"org.gtk_rs.GObjectSubclassing1\"; 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()\n} fn build_ui(app: &Application) { // Create a button let button = CustomButton::with_label(\"Press me!\"); button.set_margin_top(12); button.set_margin_bottom(12); button.set_margin_start(12); button.set_margin_end(12); // Connect to \"clicked\" signal of `button` button.connect_clicked(move |button| { // Set the label to \"Hello World!\" after the button has been clicked on button.set_label(\"Hello World!\"); }); // Create a window let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .child(&button) .build(); // Present window window.present();\n} Describing objects with two structs is a peculiarity coming from how GObjects are defined in C. imp::CustomButton handles the state of the GObject and the overridden virtual methods. CustomButton determines the exposed methods from the implemented traits and added methods.","breadcrumbs":"GObject Concepts » Subclassing » Subclassing","id":"34","title":"Subclassing"},"35":{"body":"We are able to use CustomButton as a drop-in replacement for gtk::Button. This is cool, but also not very tempting to do in a real application. For the gain of zero benefits, it did involve quite a bit of boilerplate after all. So let's make it a bit more interesting! gtk::Button does not hold much state, but we can let CustomButton hold a number. Filename: listings/g_object_subclassing/2/custom_button/imp.rs use std::cell::Cell; use gtk::glib;\nuse gtk::prelude::*;\nuse gtk::subclass::prelude::*; // Object holding the state\n#[derive(Default)]\npub struct CustomButton { number: Cell,\n} // The central trait for subclassing a GObject\n#[glib::object_subclass]\nimpl ObjectSubclass for CustomButton { const NAME: &'static str = \"MyGtkAppCustomButton\"; type Type = super::CustomButton; type ParentType = gtk::Button;\n} // Trait shared by all GObjects\nimpl ObjectImpl for CustomButton { fn constructed(&self) { self.parent_constructed(); self.obj().set_label(&self.number.get().to_string()); }\n} // Trait shared by all widgets\nimpl WidgetImpl for CustomButton {} // Trait shared by all buttons\nimpl ButtonImpl for CustomButton { fn clicked(&self) { self.number.set(self.number.get() + 1); self.obj().set_label(&self.number.get().to_string()) }\n} We override constructed in ObjectImpl so that the label of the button initializes with number. We also override clicked in ButtonImpl so that every click increases number and updates the label. Filename: listings/g_object_subclassing/2/main.rs # mod custom_button;\n# # use custom_button::CustomButton;\n# use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectSubclassing2\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# fn build_ui(app: &Application) { // Create a button let button = CustomButton::new(); button.set_margin_top(12); button.set_margin_bottom(12); button.set_margin_start(12); button.set_margin_end(12); // Create a window let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .child(&button) .build(); // Present window window.present();\n} In build_ui we stop calling connect_clicked, and that was it. After a rebuild, the app now features our CustomButton with the label \"0\". Every time we click on the button, the number displayed by the label increases by 1. A video showing that pressing on a button increases the number So, when do we want to inherit from GObject? We want to use a certain widget, but with added state and overridden virtual functions. We want to pass a Rust object to a function, but the function expects a GObject. We want to add properties or signals to an object.","breadcrumbs":"GObject Concepts » Subclassing » Adding Functionality","id":"35","title":"Adding Functionality"},"36":{"body":"Some GObject-related functions rely on generic values for their arguments or return parameters. Since GObject introspection works through a C interface, these functions cannot rely on any powerful Rust concepts. In these cases glib::Value or glib::Variant are used.","breadcrumbs":"GObject Concepts » Generic Values » Generic Values","id":"36","title":"Generic Values"},"37":{"body":"Let's start with Value. Conceptually, a Value is similar to a Rust enum defined like this: enum Value { bool(bool), i8(i8), i32(i32), u32(u32), i64(i64), u64(u64), f32(f32), f64(f64), // boxed types String(Option), Object(Option>),\n} For example, this is how you would use a Value representing an i32. Filename: listings/g_object_values/1/main.rs # use gtk::prelude::*;\n# # fn main() { // Store `i32` as `Value` let integer_value = 10.to_value(); // Retrieve `i32` from `Value` let integer = integer_value .get::() .expect(\"The value needs to be of type `i32`.\"); // Check if the retrieved value is correct assert_eq!(integer, 10);\n# # // Store string as `Value`\n# let string_value = \"Hello!\".to_value();\n# # // Retrieve `String` from `Value`\n# let string = string_value\n# .get::()\n# .expect(\"The value needs to be of type `String`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(string, \"Hello!\".to_string());\n# # // Store `Option` as `Value`\n# let string_some_value = \"Hello!\".to_value();\n# let string_none_value = None::.to_value();\n# # // Retrieve `String` from `Value`\n# let string_some = string_some_value\n# .get::>()\n# .expect(\"The value needs to be of type `Option`.\");\n# let string_none = string_none_value\n# .get::>()\n# .expect(\"The value needs to be of type `Option`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(string_some, Some(\"Hello!\".to_string()));\n# assert_eq!(string_none, None);\n# } Also note that in the enum above boxed types such as String or glib::Object are wrapped in an Option. This comes from C, where every boxed type can potentially be None (or NULL in C terms). You can still access it the same way as with the i32 above. get will then not only return Err if you specified the wrong type, but also if the Value represents None. Filename: listings/g_object_values/1/main.rs # use gtk::prelude::*;\n# # fn main() {\n# // Store `i32` as `Value`\n# let integer_value = 10.to_value();\n# # // Retrieve `i32` from `Value`\n# let integer = integer_value\n# .get::()\n# .expect(\"The value needs to be of type `i32`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(integer, 10);\n# // Store string as `Value` let string_value = \"Hello!\".to_value(); // Retrieve `String` from `Value` let string = string_value .get::() .expect(\"The value needs to be of type `String`.\"); // Check if the retrieved value is correct assert_eq!(string, \"Hello!\".to_string());\n# # // Store `Option` as `Value`\n# let string_some_value = \"Hello!\".to_value();\n# let string_none_value = None::.to_value();\n# # // Retrieve `String` from `Value`\n# let string_some = string_some_value\n# .get::>()\n# .expect(\"The value needs to be of type `Option`.\");\n# let string_none = string_none_value\n# .get::>()\n# .expect(\"The value needs to be of type `Option`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(string_some, Some(\"Hello!\".to_string()));\n# assert_eq!(string_none, None);\n# } If you want to differentiate between specifying the wrong type and a Value representing None, just call get::> instead. Filename: listings/g_object_values/1/main.rs # use gtk::prelude::*;\n# # fn main() {\n# // Store `i32` as `Value`\n# let integer_value = 10.to_value();\n# # // Retrieve `i32` from `Value`\n# let integer = integer_value\n# .get::()\n# .expect(\"The value needs to be of type `i32`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(integer, 10);\n# # // Store string as `Value`\n# let string_value = \"Hello!\".to_value();\n# # // Retrieve `String` from `Value`\n# let string = string_value\n# .get::()\n# .expect(\"The value needs to be of type `String`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(string, \"Hello!\".to_string());\n# // Store `Option` as `Value` let string_some_value = \"Hello!\".to_value(); let string_none_value = None::.to_value(); // Retrieve `String` from `Value` let string_some = string_some_value .get::>() .expect(\"The value needs to be of type `Option`.\"); let string_none = string_none_value .get::>() .expect(\"The value needs to be of type `Option`.\"); // Check if the retrieved value is correct assert_eq!(string_some, Some(\"Hello!\".to_string())); assert_eq!(string_none, None);\n# } We will use Value when we deal with properties and signals later on.","breadcrumbs":"GObject Concepts » Generic Values » Value","id":"37","title":"Value"},"38":{"body":"A Variant is used whenever data needs to be serialized, for example for sending it to another process or over the network, or for storing it on disk. Although GVariant supports arbitrarily complex types, the Rust bindings are currently limited to bool, u8, i16, u16, i32, u32, i64, u64, f64, &str/String, and VariantDict . Containers of the above types are possible as well, such as HashMap, Vec, Option, tuples up to 16 elements, and Variant. Variants can even be derived from Rust structs as long as its members can be represented by variants. In the most simple case, converting Rust types to Variant and vice-versa is very similar to the way it worked with Value. Filename: listings/g_object_values/2/main.rs # use gtk::prelude::*;\n# # fn main() { // Store `i32` as `Variant` let integer_variant = 10.to_variant(); // Retrieve `i32` from `Variant` let integer = integer_variant .get::() .expect(\"The variant needs to be of type `i32`.\"); // Check if the retrieved value is correct assert_eq!(integer, 10);\n# # let variant = vec![\"Hello\", \"there!\"].to_variant();\n# assert_eq!(variant.n_children(), 2);\n# let vec = &variant\n# .get::>()\n# .expect(\"The variant needs to be of type `String`.\");\n# assert_eq!(vec[0], \"Hello\");\n# } However, a Variant is also able to represent containers such as HashMap or Vec . The following snippet shows how to convert between Vec and Variant. More examples can be found in the docs . Filename: listings/g_object_values/2/main.rs # use gtk::prelude::*;\n# # fn main() {\n# // Store `i32` as `Variant`\n# let integer_variant = 10.to_variant();\n# # // Retrieve `i32` from `Variant`\n# let integer = integer_variant\n# .get::()\n# .expect(\"The variant needs to be of type `i32`.\");\n# # // Check if the retrieved value is correct\n# assert_eq!(integer, 10);\n# let variant = vec![\"Hello\", \"there!\"].to_variant(); assert_eq!(variant.n_children(), 2); let vec = &variant .get::>() .expect(\"The variant needs to be of type `String`.\"); assert_eq!(vec[0], \"Hello\");\n# } We will use Variant when saving settings using gio::Settings or activating actions via gio::Action .","breadcrumbs":"GObject Concepts » Generic Values » Variant","id":"38","title":"Variant"},"39":{"body":"Properties provide a public API for accessing state of GObjects. Let's see how this is done by experimenting with the Switch widget. One of its properties is called active . According to the GTK docs, it can be read and be written to. That is why gtk-rs provides corresponding is_active and set_active methods. Filename: listings/g_object_properties/1/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Align, Application, ApplicationWindow, Box, Orientation, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectProperties1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Create the switch let switch = Switch::new(); // Set and then immediately obtain active property switch.set_active(true); let switch_active = switch.is_active(); // This prints: \"The active property of switch is true\" println!(\"The active property of switch is {}\", switch_active);\n# # // Set up box\n# let gtk_box = Box::builder()\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .spacing(12)\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&switch);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } Properties can not only be accessed via getters & setters, they can also be bound to each other. Let's see how that would look like for two Switch instances. Filename: listings/g_object_properties/2/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Align, Application, ApplicationWindow, Box, Orientation, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectProperties3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Create the switches let switch_1 = Switch::new(); let switch_2 = Switch::new();\n# # switch_1\n# .bind_property(\"active\", &switch_2, \"active\")\n# .bidirectional()\n# .build();\n# # // Set up box\n# let gtk_box = Box::builder()\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .spacing(12)\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&switch_1);\n# gtk_box.append(&switch_2);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } In our case, we want to bind the \"active\" property of switch_1 to the \"active\" property of switch_2. We also want the binding to be bidirectional, so we specify by calling the bidirectional method. Filename: listings/g_object_properties/2/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Align, Application, ApplicationWindow, Box, Orientation, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectProperties3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create the switches\n# let switch_1 = Switch::new();\n# let switch_2 = Switch::new();\n# switch_1 .bind_property(\"active\", &switch_2, \"active\") .bidirectional() .build();\n# # // Set up box\n# let gtk_box = Box::builder()\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .spacing(12)\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&switch_1);\n# gtk_box.append(&switch_2);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } Now when we click on one of the two switches, the other one is toggled as well. A video which shows that toggling one button also toggles the other one","breadcrumbs":"GObject Concepts » Properties » Properties","id":"39","title":"Properties"},"4":{"body":"In order to develop a gtk-rs app, you basically need two things on your workstation: the Rust toolchain, and the GTK 4 library. As so often the devil hides in the details, which is why we will list the installation instructions for each operating system in the following chapters.","breadcrumbs":"Installation » Installation","id":"4","title":"Installation"},"40":{"body":"We can also add properties to custom GObjects. We can demonstrate that by binding the number of our CustomButton to a property. Most of the work is done by the glib::Properties derive macro. We tell it that the wrapper type is super::CustomButton. We also annotate number, so that macro knows that it should create a property \"number\" that is readable and writable. It also generates wrapper methods which we are going to use later in this chapter. Filename: listings/g_object_properties/3/custom_button/imp.rs # use std::cell::Cell;\n# # use glib::Properties;\n# use gtk::glib;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# // Object holding the state\n#[derive(Properties, Default)]\n#[properties(wrapper_type = super::CustomButton)]\npub struct CustomButton { #[property(get, set)] number: Cell,\n}\n# # // The central trait for subclassing a GObject\n# #[glib::object_subclass]\n# impl ObjectSubclass for CustomButton {\n# const NAME: &'static str = \"MyGtkAppCustomButton\";\n# type Type = super::CustomButton;\n# type ParentType = gtk::Button;\n# }\n# # // Trait shared by all GObjects\n# #[glib::derived_properties]\n# impl ObjectImpl for CustomButton {\n# fn constructed(&self) {\n# self.parent_constructed();\n# # // Bind label to number\n# // `SYNC_CREATE` ensures that the label will be immediately set\n# let obj = self.obj();\n# obj.bind_property(\"number\", obj.as_ref(), \"label\")\n# .sync_create()\n# .build();\n# }\n# }\n# # // Trait shared by all widgets\n# impl WidgetImpl for CustomButton {}\n# # // Trait shared by all buttons\n# impl ButtonImpl for CustomButton {\n# fn clicked(&self) {\n# let incremented_number = self.obj().number() + 1;\n# self.obj().set_number(incremented_number);\n# }\n# } The glib::derived_properties macro generates boilerplate that is the same for every GObject that generates its properties with the Property macro. In constructed we use our new property \"number\" by binding the \"label\" property to it. bind_property converts the integer value of \"number\" to the string of \"label\" on its own. Now we don't have to adapt the label in the \"clicked\" callback anymore. Filename: listings/g_object_properties/3/custom_button/imp.rs # use std::cell::Cell;\n# # use glib::Properties;\n# use gtk::glib;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# # // Object holding the state\n# #[derive(Properties, Default)]\n# #[properties(wrapper_type = super::CustomButton)]\n# pub struct CustomButton {\n# #[property(get, set)]\n# number: Cell,\n# }\n# # // The central trait for subclassing a GObject\n# #[glib::object_subclass]\n# impl ObjectSubclass for CustomButton {\n# const NAME: &'static str = \"MyGtkAppCustomButton\";\n# type Type = super::CustomButton;\n# type ParentType = gtk::Button;\n# }\n# // Trait shared by all GObjects\n#[glib::derived_properties]\nimpl ObjectImpl for CustomButton { fn constructed(&self) { self.parent_constructed(); // Bind label to number // `SYNC_CREATE` ensures that the label will be immediately set let obj = self.obj(); obj.bind_property(\"number\", obj.as_ref(), \"label\") .sync_create() .build(); }\n}\n# # // Trait shared by all widgets\n# impl WidgetImpl for CustomButton {}\n# # // Trait shared by all buttons\n# impl ButtonImpl for CustomButton {\n# fn clicked(&self) {\n# let incremented_number = self.obj().number() + 1;\n# self.obj().set_number(incremented_number);\n# }\n# } We also have to adapt the clicked method. Before we modified number directly, now we can use the generated wrapper methods number and set_number. This way the \"notify\" signal will be emitted, which is necessary for the bindings to work as expected. # use std::cell::Cell;\n# # use glib::Properties;\n# use gtk::glib;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# # // Object holding the state\n# #[derive(Properties, Default)]\n# #[properties(wrapper_type = super::CustomButton)]\n# pub struct CustomButton {\n# #[property(get, set)]\n# number: Cell,\n# }\n# # // The central trait for subclassing a GObject\n# #[glib::object_subclass]\n# impl ObjectSubclass for CustomButton {\n# const NAME: &'static str = \"MyGtkAppCustomButton\";\n# type Type = super::CustomButton;\n# type ParentType = gtk::Button;\n# }\n# # // Trait shared by all GObjects\n# #[glib::derived_properties]\n# impl ObjectImpl for CustomButton {\n# fn constructed(&self) {\n# self.parent_constructed();\n# # // Bind label to number\n# // `SYNC_CREATE` ensures that the label will be immediately set\n# let obj = self.obj();\n# obj.bind_property(\"number\", obj.as_ref(), \"label\")\n# .sync_create()\n# .build();\n# }\n# }\n# # // Trait shared by all widgets\n# impl WidgetImpl for CustomButton {}\n# // Trait shared by all buttons\nimpl ButtonImpl for CustomButton { fn clicked(&self) { let incremented_number = self.obj().number() + 1; self.obj().set_number(incremented_number); }\n} Let's see what we can do with this by creating two custom buttons. Filename: listings/g_object_properties/3/main.rs # mod custom_button;\n# # use custom_button::CustomButton;\n# use gtk::prelude::*;\n# use gtk::{glib, Align, Application, ApplicationWindow, Box, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectProperties4\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Create the buttons let button_1 = CustomButton::new(); let button_2 = CustomButton::new();\n# # // Assure that \"number\" of `button_2` is always 1 higher than \"number\" of `button_1`\n# button_1\n# .bind_property(\"number\", &button_2, \"number\")\n# // How to transform \"number\" from `button_1` to \"number\" of `button_2`\n# .transform_to(|_, number: i32| {\n# let incremented_number = number + 1;\n# Some(incremented_number.to_value())\n# })\n# // How to transform \"number\" from `button_2` to \"number\" of `button_1`\n# .transform_from(|_, number: i32| {\n# let decremented_number = number - 1;\n# Some(decremented_number.to_value())\n# })\n# .bidirectional()\n# .sync_create()\n# .build();\n# # // The closure will be called\n# // whenever the property \"number\" of `button_1` gets changed\n# button_1.connect_number_notify(|button| {\n# println!(\"The current number of `button_1` is {}.\", button.number());\n# });\n# # // Set up box\n# let gtk_box = Box::builder()\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .spacing(12)\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_1);\n# gtk_box.append(&button_2);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } We have already seen that bound properties don't necessarily have to be of the same type. By leveraging transform_to and transform_from , we can assure that button_2 always displays a number which is 1 higher than the number of button_1. Filename: listings/g_object_properties/3/main.rs # mod custom_button;\n# # use custom_button::CustomButton;\n# use gtk::prelude::*;\n# use gtk::{glib, Align, Application, ApplicationWindow, Box, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectProperties4\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create the buttons\n# let button_1 = CustomButton::new();\n# let button_2 = CustomButton::new();\n# // Assure that \"number\" of `button_2` is always 1 higher than \"number\" of `button_1` button_1 .bind_property(\"number\", &button_2, \"number\") // How to transform \"number\" from `button_1` to \"number\" of `button_2` .transform_to(|_, number: i32| { let incremented_number = number + 1; Some(incremented_number.to_value()) }) // How to transform \"number\" from `button_2` to \"number\" of `button_1` .transform_from(|_, number: i32| { let decremented_number = number - 1; Some(decremented_number.to_value()) }) .bidirectional() .sync_create() .build();\n# # // The closure will be called\n# // whenever the property \"number\" of `button_1` gets changed\n# button_1.connect_number_notify(|button| {\n# println!(\"The current number of `button_1` is {}.\", button.number());\n# });\n# # // Set up box\n# let gtk_box = Box::builder()\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .spacing(12)\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_1);\n# gtk_box.append(&button_2);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } Now if we click on one button, the \"number\" and \"label\" properties of the other button change as well. A video which shows that pressing on one button also changes the number on the other one Another nice feature of properties is, that you can connect a callback to the event, when a property gets changed. For example like this: Filename: listings/g_object_properties/3/main.rs # mod custom_button;\n# # use custom_button::CustomButton;\n# use gtk::prelude::*;\n# use gtk::{glib, Align, Application, ApplicationWindow, Box, Orientation};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectProperties4\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create the buttons\n# let button_1 = CustomButton::new();\n# let button_2 = CustomButton::new();\n# # // Assure that \"number\" of `button_2` is always 1 higher than \"number\" of `button_1`\n# button_1\n# .bind_property(\"number\", &button_2, \"number\")\n# // How to transform \"number\" from `button_1` to \"number\" of `button_2`\n# .transform_to(|_, number: i32| {\n# let incremented_number = number + 1;\n# Some(incremented_number.to_value())\n# })\n# // How to transform \"number\" from `button_2` to \"number\" of `button_1`\n# .transform_from(|_, number: i32| {\n# let decremented_number = number - 1;\n# Some(decremented_number.to_value())\n# })\n# .bidirectional()\n# .sync_create()\n# .build();\n# // The closure will be called // whenever the property \"number\" of `button_1` gets changed button_1.connect_number_notify(|button| { println!(\"The current number of `button_1` is {}.\", button.number()); });\n# # // Set up box\n# let gtk_box = Box::builder()\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .spacing(12)\n# .orientation(Orientation::Vertical)\n# .build();\n# gtk_box.append(&button_1);\n# gtk_box.append(&button_2);\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(>k_box)\n# .build();\n# # // Present the window\n# window.present();\n# } Now, whenever the \"number\" property gets changed, the closure gets executed and prints the current value of \"number\" to standard output. Introducing properties to your custom GObjects is useful if you want to bind state of (different) GObjects notify consumers whenever a property value changes Note that it has a (computational) cost to send a signal each time the value changes. If you only want to expose internal state, adding getter and setter methods is the better option.","breadcrumbs":"GObject Concepts » Properties » Adding Properties to Custom GObjects","id":"40","title":"Adding Properties to Custom GObjects"},"41":{"body":"GObject signals are a system for registering callbacks for specific events. For example, if we press on a button, the \"clicked\" signal will be emitted. The signal then takes care that all the registered callbacks will be executed. gtk-rs provides convenience methods for registering callbacks. In our \"Hello World\" example we connected the \"clicked\" signal to a closure which sets the label of the button to \"Hello World\" as soon as it gets called. Filename: listings/hello_world/3/main.rs # use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# const APP_ID: &str = \"org.gtk_rs.HelloWorld3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a button with label and margins\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // Connect to \"clicked\" signal of `button` button.connect_clicked(|button| { // Set the label to \"Hello World!\" after the button has been clicked on button.set_label(\"Hello World!\"); });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } If we wanted to, we could have connected to it with the generic connect_closure method and the glib::closure_local! macro. Filename: listings/g_object_signals/1/main.rs # use glib::closure_local;\n# use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectSignals1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a button\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // Connect to \"clicked\" signal of `button` button.connect_closure( \"clicked\", false, closure_local!(move |button: Button| { // Set the label to \"Hello World!\" after the button has been clicked on button.set_label(\"Hello World!\"); }), ); # # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } The advantage of connect_closure is that it also works with custom signals. If you need to clone reference counted objects into your closure you don't have to wrap it within another clone! macro. closure_local! accepts the same syntax for creating strong/weak references, plus a watch feature that automatically disconnects the closure once the watched object is dropped.","breadcrumbs":"GObject Concepts » Signals » Signals","id":"41","title":"Signals"},"42":{"body":"Let's see how we can create our own signals. Again we do that by extending our CustomButton. First we override the signals method in ObjectImpl. In order to do that, we need to lazily initialize a static item SIGNALS. Until std::sync::LazyLock is stabilized, we use once_cell::sync::Lazy instead. This also means, we have to add the once_cell crate as dependency by executing: cargo add once_cell Filename: listings/g_object_signals/2/custom_button/imp.rs # use std::cell::Cell;\n# # use glib::subclass::Signal;\n# use glib::Properties;\n# use gtk::glib;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# use once_cell::sync::Lazy;\n# # // Object holding the state\n# #[derive(Properties, Default)]\n# #[properties(wrapper_type = super::CustomButton)]\n# pub struct CustomButton {\n# #[property(get, set)]\n# number: Cell,\n# }\n# # // The central trait for subclassing a GObject\n# #[glib::object_subclass]\n# impl ObjectSubclass for CustomButton {\n# const NAME: &'static str = \"MyGtkAppCustomButton\";\n# type Type = super::CustomButton;\n# type ParentType = gtk::Button;\n# }\n# // Trait shared by all GObjects\n#[glib::derived_properties]\nimpl ObjectImpl for CustomButton { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![Signal::builder(\"max-number-reached\") .param_types([i32::static_type()]) .build()] }); SIGNALS.as_ref() }\n# # fn constructed(&self) {\n# self.parent_constructed();\n# # // Bind label to number\n# // `SYNC_CREATE` ensures that the label will be immediately set\n# let obj = self.obj();\n# obj.bind_property(\"number\", obj.as_ref(), \"label\")\n# .sync_create()\n# .build();\n# }\n# }\n# # // Trait shared by all widgets\n# impl WidgetImpl for CustomButton {}\n# # static MAX_NUMBER: i32 = 8;\n# # // Trait shared by all buttons\n# impl ButtonImpl for CustomButton {\n# fn clicked(&self) {\n# let incremented_number = self.obj().number() + 1;\n# let obj = self.obj();\n# // If `number` reached `MAX_NUMBER`,\n# // emit \"max-number-reached\" signal and set `number` back to 0\n# if incremented_number == MAX_NUMBER {\n# obj.emit_by_name::<()>(\"max-number-reached\", &[&incremented_number]);\n# obj.set_number(0);\n# } else {\n# obj.set_number(incremented_number);\n# }\n# }\n# } The signals method is responsible for defining a set of signals. In our case, we only create a single signal named \"max-number-reached\". When naming our signal, we make sure to do that in kebab-case . When emitted, it sends a single i32 value. We want the signal to be emitted, whenever number reaches MAX_NUMBER. Together with the signal we send the value number currently holds. After we did that, we set number back to 0. Filename: listings/g_object_signals/2/custom_button/imp.rs # use std::cell::Cell;\n# # use glib::subclass::Signal;\n# use glib::Properties;\n# use gtk::glib;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# use once_cell::sync::Lazy;\n# # // Object holding the state\n# #[derive(Properties, Default)]\n# #[properties(wrapper_type = super::CustomButton)]\n# pub struct CustomButton {\n# #[property(get, set)]\n# number: Cell,\n# }\n# # // The central trait for subclassing a GObject\n# #[glib::object_subclass]\n# impl ObjectSubclass for CustomButton {\n# const NAME: &'static str = \"MyGtkAppCustomButton\";\n# type Type = super::CustomButton;\n# type ParentType = gtk::Button;\n# }\n# # // Trait shared by all GObjects\n# #[glib::derived_properties]\n# impl ObjectImpl for CustomButton {\n# fn signals() -> &'static [Signal] {\n# static SIGNALS: Lazy> = Lazy::new(|| {\n# vec![Signal::builder(\"max-number-reached\")\n# .param_types([i32::static_type()])\n# .build()]\n# });\n# SIGNALS.as_ref()\n# }\n# # fn constructed(&self) {\n# self.parent_constructed();\n# # // Bind label to number\n# // `SYNC_CREATE` ensures that the label will be immediately set\n# let obj = self.obj();\n# obj.bind_property(\"number\", obj.as_ref(), \"label\")\n# .sync_create()\n# .build();\n# }\n# }\n# # // Trait shared by all widgets\n# impl WidgetImpl for CustomButton {}\n# static MAX_NUMBER: i32 = 8; // Trait shared by all buttons\nimpl ButtonImpl for CustomButton { fn clicked(&self) { let incremented_number = self.obj().number() + 1; let obj = self.obj(); // If `number` reached `MAX_NUMBER`, // emit \"max-number-reached\" signal and set `number` back to 0 if incremented_number == MAX_NUMBER { obj.emit_by_name::<()>(\"max-number-reached\", &[&incremented_number]); obj.set_number(0); } else { obj.set_number(incremented_number); } }\n} If we now press on the button, the number of its label increases until it reaches MAX_NUMBER. Then it emits the \"max-number-reached\" signal which we can nicely connect to. Whenever we now receive the \"max-number-reached\" signal, the accompanying number is printed to standard output . Filename: listings/g_object_signals/2/main.rs # mod custom_button;\n# # use custom_button::CustomButton;\n# use glib::closure_local;\n# use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow};\n# # const APP_ID: &str = \"org.gtk_rs.GObjectSignals2\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# fn build_ui(app: &Application) {\n# // Create a button\n# let button = CustomButton::new();\n# button.set_margin_top(12);\n# button.set_margin_bottom(12);\n# button.set_margin_start(12);\n# button.set_margin_end(12);\n# button.connect_closure( \"max-number-reached\", false, closure_local!(move |_button: CustomButton, number: i32| { println!(\"The maximum number {} has been reached\", number); }), );\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } You now know how to connect to every kind of signal and how to create your own. Custom signals are especially useful, if you want to notify consumers of your GObject that a certain event occurred.","breadcrumbs":"GObject Concepts » Signals » Adding Signals to Custom GObjects","id":"42","title":"Adding Signals to Custom GObjects"},"43":{"body":"We now got comfortable using callbacks, but how do they actually work? All of this happens asynchronously, so there must be something managing the events and scheduling the responses. Unsurprisingly, this is called the main event loop. The main loop manages all kinds of events — from mouse clicks and keyboard presses to file events. It does all of that within the same thread. Quickly iterating between all tasks gives the illusion of parallelism. That is why you can move the window at the same time as a progress bar is growing. However, you surely saw GUIs that became unresponsive, at least for a few seconds. That happens when a single task takes too long. Let's look at one example. Filename: listings/main_event_loop/1/main.rs use std::thread;\nuse std::time::Duration; use gtk::prelude::*;\nuse gtk::{self, glib, Application, ApplicationWindow, Button}; const APP_ID: &str = \"org.gtk_rs.MainEventLoop1\"; 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()\n} fn build_ui(app: &Application) { // Create a button let button = Button::builder() .label(\"Press me!\") .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); // Connect to \"clicked\" signal of `button` button.connect_clicked(move |_| { // GUI is blocked for 5 seconds after the button is pressed let five_seconds = Duration::from_secs(5); thread::sleep(five_seconds); }); // Create a window let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .child(&button) .build(); // Present window window.present();\n} After we press the button, the GUI is completely frozen for five seconds. We can't even move the window. The sleep call is an artificial example, but it is not unusual wanting to run a slightly longer operation in one go.","breadcrumbs":"The Main Event Loop » The Main Event Loop","id":"43","title":"The Main Event Loop"},"44":{"body":"In order to avoid blocking the main loop we can spawn a new thread with gio::spawn_blocking and let the operation run there. Filename: listings/main_event_loop/2/main.rs # use std::thread;\n# use std::time::Duration;\n# # use gtk::prelude::*;\n# use gtk::{self, gio, glib, Application, ApplicationWindow, Button};\n# # const APP_ID: &str = \"org.gtk_rs.MainEventLoop2\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a button\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // Connect to \"clicked\" signal of `button` button.connect_clicked(move |_| { // The long running operation runs now in a separate thread gio::spawn_blocking(move || { let five_seconds = Duration::from_secs(5); thread::sleep(five_seconds); }); });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } A video which shows that after pressing the button, the window can still be moved If you come from another language than Rust, you might be uncomfortable with the thought of spawning new threads before even looking at other options. Luckily, Rust's safety guarantees allow you to stop worrying about the nasty bugs that concurrency tends to bring. Normally we want to keep track of the work in the thread. In our case, we don't want the user to spawn additional threads while an existing one is still running. In order to achieve that we can create a channel. The main loop allows us to send a message from multiple places to a single receiver at the main thread. We want to send a bool to inform, whether we want the button to react to clicks or not. Filename: listings/main_event_loop/3/main.rs # use std::thread;\n# use std::time::Duration;\n# # use glib::{clone, MainContext, Priority};\n# use gtk::prelude::*;\n# use gtk::{gio, glib, Application, ApplicationWindow, Button};\n# # const APP_ID: &str = \"org.gtk_rs.MainEventLoop3\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a button\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let (sender, receiver) = MainContext::channel(Priority::default()); // Connect to \"clicked\" signal of `button` button.connect_clicked(move |_| { let sender = sender.clone(); // The long running operation runs now in a separate thread gio::spawn_blocking(move || { // Deactivate the button until the operation is done sender.send(false).expect(\"Could not send through channel\"); let ten_seconds = Duration::from_secs(10); thread::sleep(ten_seconds); // Activate the button again sender.send(true).expect(\"Could not send through channel\"); }); }); // The main loop executes the closure as soon as it receives the message receiver.attach( None, clone!(@weak button => @default-return glib::ControlFlow::Break, move |enable_button| { button.set_sensitive(enable_button); glib::ControlFlow::Continue } ), );\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } The button now stops being responsive for 10 seconds after being pressed Per default, glib::clone! returns () when upgrading of a weak reference fails. glib::Receiver::attach expects a closure with a return value of type glib::ControlFlow . This is why we specify @default-return as glib::ControlFlow::Break to clarify that the closure not be called anymore as soon as the upgrade of a weak reference fails. Spawning threads is not the only way to run operations asynchronously. You can also let the main loop take care of running async functions. If you do that from the main thread use spawn_local , from other threads spawn has to be used. The converted code looks and behaves very similar to the multi-threaded code. Filename: listings/main_event_loop/4/main.rs # use glib::{clone, timeout_future_seconds, MainContext, Priority};\n# use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# # const APP_ID: &str = \"org.gtk_rs.MainEventLoop4\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a button\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# let (sender, receiver) = MainContext::channel(Priority::default()); // Connect to \"clicked\" signal of `button` button.connect_clicked(move |_| { let main_context = MainContext::default(); // The main loop executes the asynchronous block main_context.spawn_local(clone!(@strong sender => async move { // Deactivate the button until the operation is done sender.send(false).expect(\"Could not send through channel\"); timeout_future_seconds(5).await; // Activate the button again sender.send(true).expect(\"Could not send through channel\"); })); }); // The main loop executes the closure as soon as it receives the message receiver.attach( None, clone!(@weak button => @default-return glib::ControlFlow::Break, move |enable_button| { button.set_sensitive(enable_button); glib::ControlFlow::Continue } ), );\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } Since we are single-threaded again, we could even get rid of the channels while achieving the same result. Filename: listings/main_event_loop/5/main.rs # use glib::{clone, timeout_future_seconds, MainContext};\n# use gtk::prelude::*;\n# use gtk::{glib, Application, ApplicationWindow, Button};\n# # const APP_ID: &str = \"org.gtk_rs.MainEventLoop5\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a button\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // Connect to \"clicked\" signal of `button` button.connect_clicked(move |button| { let main_context = MainContext::default(); // The main loop executes the asynchronous block main_context.spawn_local(clone!(@weak button => async move { // Deactivate the button until the operation is done button.set_sensitive(false); timeout_future_seconds(5).await; // Activate the button again button.set_sensitive(true); })); });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&button)\n# .build();\n# # // Present window\n# window.present();\n# } But why did we not do the same thing with our multi-threaded example? # use std::{thread, time::Duration};\n# # use glib::{clone, MainContext, PRIORITY_DEFAULT};\n# use gtk::{glib, gio};\n# use gtk::prelude::*;\n# use gtk::{Application, ApplicationWindow, Button};\n# # fn main() {\n# // Create a new application\n# let app = Application::builder()\n# .application_id(\"org.gtk_rs.MainEventLoop6\")\n# .build();\n#\n# // Connect to \"activate\" signal\n# app.connect_activate(build_ui);\n# # // Get command-line arguments\n# let args: Vec = args().collect();\n# // Run the application\n# app.run(&args);\n# }\n# # // When the application is launched…\n# fn build_ui(application: &Application) {\n# // Create a window\n# let window = ApplicationWindow::builder()\n# .application(application)\n# .title(\"My GTK App\")\n# .build();\n# # // Create a button\n# let button = Button::builder()\n# .label(\"Press me!\")\n# .margin_top(12)\n# .margin_bottom(12)\n# .margin_start(12)\n# .margin_end(12)\n# .build();\n# // DOES NOT COMPILE! // Connect to \"clicked\" signal of `button` button.connect_clicked(move |button| { button.clone(); // The long running operation runs now in a separate thread gio::spawn_blocking(move || { // Deactivate the button until the operation is done button.set_sensitive(false); let five_seconds = Duration::from_secs(5); thread::sleep(five_seconds); // Activate the button again button.set_sensitive(true); }); });\n# # // Add button\n# window.set_child(Some(&button));\n# window.present();\n# } Simply because we would get this error message: error[E0277]: `NonNull` cannot be shared between threads safely help: within `gtk4::Button`, the trait `Sync` is not implemented for `NonNull` After reference cycles we found the second disadvantage of GTK GObjects: They are not thread safe. So when should you spawn an async block and when should you spawn a thread? If you have async functions for your IO-bound operations at your disposal, feel free to spawn them on the main loop. If your operation is computation-bound or there is no async function available, you have to spawn threads.","breadcrumbs":"The Main Event Loop » How to Avoid Blocking the Main Loop","id":"44","title":"How to Avoid Blocking the Main Loop"},"45":{"body":"We have now learned multiple ways to handle states. However, every time we close the application all of it is gone. Let's learn how to use gio::Settings by storing the state of a Switch in it. At the very beginning we have to create a GSchema xml file in order to describe the kind of data our application plans to store in the settings. Filename: listings/settings/1/org.gtk_rs.Settings1.gschema.xml \n false

Default switch state \n Let's get through it step by step. The id is the same application id we used when we created our application. Filename: listings/settings/1/main.rs # use gio::Settings;\n# use gtk::prelude::*;\n# use gtk::{gio, glib, Align, Application, ApplicationWindow, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.Settings1\";\n# # fn main() -> glib::ExitCode { // Create a new application let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Initialize settings\n# let settings = Settings::new(APP_ID);\n# # // Get the last switch state from the settings\n# let is_switch_enabled = settings.boolean(\"is-switch-enabled\");\n# # // Create a switch\n# let switch = Switch::builder()\n# .margin_top(48)\n# .margin_bottom(48)\n# .margin_start(48)\n# .margin_end(48)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .state(is_switch_enabled)\n# .build();\n# # switch.connect_state_set(move |_, is_enabled| {\n# // Save changed switch state in the settings\n# settings\n# .set_boolean(\"is-switch-enabled\", is_enabled)\n# .expect(\"Could not set setting.\");\n# // Allow to invoke other event handlers\n# glib::Propagation::Proceed\n# });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&switch)\n# .build();\n# # // Present window\n# window.present();\n# } The path must start and end with a forward slash character ('/') and must not contain two sequential slash characters. When creating a path, we advise to take the id, replace the '.' with '/' and add '/' at the front and end of it. We only want to store a single key with the name \"is-switch-enabled\". This is a boolean value so its type is \"b\" (see GVariant Format Strings for the other options). We also set its default value to false (see GVariant Text Format for the full syntax). Finally, we add a summary. Now we need to copy and compile the schema. You can install the schema by executing the following commands on a Linux or macOS machine: mkdir -p $HOME/.local/share/glib-2.0/schemas\ncp org.gtk_rs.Settings1.gschema.xml $HOME/.local/share/glib-2.0/schemas/\nglib-compile-schemas $HOME/.local/share/glib-2.0/schemas/ On Windows run: mkdir C:/ProgramData/glib-2.0/schemas/\ncp org.gtk_rs.Settings1.gschema.xml C:/ProgramData/glib-2.0/schemas/\nglib-compile-schemas C:/ProgramData/glib-2.0/schemas/ We initialize the Settings object by specifying the application id. Filename: listings/settings/1/main.rs # use gio::Settings;\n# use gtk::prelude::*;\n# use gtk::{gio, glib, Align, Application, ApplicationWindow, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.Settings1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Initialize settings let settings = Settings::new(APP_ID);\n# # // Get the last switch state from the settings\n# let is_switch_enabled = settings.boolean(\"is-switch-enabled\");\n# # // Create a switch\n# let switch = Switch::builder()\n# .margin_top(48)\n# .margin_bottom(48)\n# .margin_start(48)\n# .margin_end(48)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .state(is_switch_enabled)\n# .build();\n# # switch.connect_state_set(move |_, is_enabled| {\n# // Save changed switch state in the settings\n# settings\n# .set_boolean(\"is-switch-enabled\", is_enabled)\n# .expect(\"Could not set setting.\");\n# // Allow to invoke other event handlers\n# glib::Propagation::Proceed\n# });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&switch)\n# .build();\n# # // Present window\n# window.present();\n# } Then we get the settings key and use it when we create our Switch. Filename: listings/settings/1/main.rs # use gio::Settings;\n# use gtk::prelude::*;\n# use gtk::{gio, glib, Align, Application, ApplicationWindow, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.Settings1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Initialize settings\n# let settings = Settings::new(APP_ID);\n# // Get the last switch state from the settings let is_switch_enabled = settings.boolean(\"is-switch-enabled\"); // Create a switch let switch = Switch::builder() .margin_top(48) .margin_bottom(48) .margin_start(48) .margin_end(48) .valign(Align::Center) .halign(Align::Center) .state(is_switch_enabled) .build();\n# # switch.connect_state_set(move |_, is_enabled| {\n# // Save changed switch state in the settings\n# settings\n# .set_boolean(\"is-switch-enabled\", is_enabled)\n# .expect(\"Could not set setting.\");\n# // Allow to invoke other event handlers\n# glib::Propagation::Proceed\n# });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&switch)\n# .build();\n# # // Present window\n# window.present();\n# } Finally, we assure that the switch state is stored in the settings whenever we click on it. Filename: listings/settings/1/main.rs # use gio::Settings;\n# use gtk::prelude::*;\n# use gtk::{gio, glib, Align, Application, ApplicationWindow, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.Settings1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Initialize settings\n# let settings = Settings::new(APP_ID);\n# # // Get the last switch state from the settings\n# let is_switch_enabled = settings.boolean(\"is-switch-enabled\");\n# # // Create a switch\n# let switch = Switch::builder()\n# .margin_top(48)\n# .margin_bottom(48)\n# .margin_start(48)\n# .margin_end(48)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .state(is_switch_enabled)\n# .build();\n# switch.connect_state_set(move |_, is_enabled| { // Save changed switch state in the settings settings .set_boolean(\"is-switch-enabled\", is_enabled) .expect(\"Could not set setting.\"); // Allow to invoke other event handlers glib::Propagation::Proceed });\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&switch)\n# .build();\n# # // Present window\n# window.present();\n# } A video which shows that the app can now store the app state The Switch now retains its state even after closing the application. But we can make this even better. The Switch has a property \"active\" and Settings allows us to bind properties to a specific setting. So let's do exactly that. We can remove the boolean call before initializing the Switch as well as the connect_state_set call. We then bind the setting to the property by specifying the key, object and name of the property. Filename: listings/settings/2/main.rs # use gio::Settings;\n# use gtk::prelude::*;\n# use gtk::{gio, glib, Align, Application, ApplicationWindow, Switch};\n# # const APP_ID: &str = \"org.gtk_rs.Settings2\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Initialize settings\n# let settings = Settings::new(APP_ID);\n# # // Create a switch\n# let switch = Switch::builder()\n# .margin_top(48)\n# .margin_bottom(48)\n# .margin_start(48)\n# .margin_end(48)\n# .valign(Align::Center)\n# .halign(Align::Center)\n# .build();\n# settings .bind(\"is-switch-enabled\", &switch, \"active\") .build();\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .child(&switch)\n# .build();\n# # // Present window\n# window.present();\n# } Whenever you have a property which nicely correspond to a setting, you probably want to bind it to it. In other cases, interacting with the settings via the getter and setter methods tends to be the right choice.","breadcrumbs":"Settings » Settings","id":"45","title":"Settings"},"46":{"body":"Quite often, we want the window state to persist between sessions. If the user resizes or maximizes the window, they might expect to find it in the same state the next time they open the app. GTK does not provide this functionality out of the box, but luckily it is not too hard to manually implement it. We basically want two integers (height & width) and a boolean (is_maximized) to persist. We already know how to do this by using gio::Settings . Filename: listings/saving_window_state/1/org.gtk_rs.SavingWindowState1.gschema.xml \n -1 Default window width -1 Default window height false Default window maximized behaviour \n Since we don't care about intermediate state, we only load the window state when the window is constructed and save it when we close the window. That can be done by creating a custom window. First, we create one and add convenience methods for accessing settings as well as the window state. Filename: listings/saving_window_state/1/custom_window/mod.rs # mod imp;\n# # use gio::Settings;\n# use glib::Object;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# use gtk::{gio, glib, Application};\n# # use crate::APP_ID;\n# glib::wrapper! { pub struct Window(ObjectSubclass) @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget, @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;\n} impl Window { pub fn new(app: &Application) -> Self { // Create new window Object::builder().property(\"application\", app).build() } fn setup_settings(&self) { let settings = Settings::new(APP_ID); self.imp() .settings .set(settings) .expect(\"`settings` should not be set before calling `setup_settings`.\"); } fn settings(&self) -> &Settings { self.imp() .settings .get() .expect(\"`settings` should be set in `setup_settings`.\") } pub fn save_window_size(&self) -> Result<(), glib::BoolError> { // Get the size of the window let size = self.default_size(); // Set the window state in `settings` self.settings().set_int(\"window-width\", size.0)?; self.settings().set_int(\"window-height\", size.1)?; self.settings() .set_boolean(\"is-maximized\", self.is_maximized())?; Ok(()) } fn load_window_size(&self) { // Get the window state from `settings` let width = self.settings().int(\"window-width\"); let height = self.settings().int(\"window-height\"); let is_maximized = self.settings().boolean(\"is-maximized\"); // Set the size of the window self.set_default_size(width, height); // If the window was maximized when it was closed, maximize it again if is_maximized { self.maximize(); } }\n} We set the property \"application\" by passing it to glib::Object::new . You can even set multiple properties that way. When creating new GObjects, this is nicer than calling the setter methods manually. The implementation struct holds the settings. You can see that we embed Settings in std::cell::OnceCell . This is a nice alternative to RefCell> when you know that you will initialize the value only once. We also override the constructed and close_request methods, where we load or save the window state. Filename: listings/saving_window_state/1/custom_window/imp.rs # use gio::Settings;\n# use gtk::subclass::prelude::*;\n# use gtk::{gio, glib, ApplicationWindow};\n# use std::cell::OnceCell;\n# #[derive(Default)]\npub struct Window { pub settings: OnceCell,\n} #[glib::object_subclass]\nimpl ObjectSubclass for Window { const NAME: &'static str = \"MyGtkAppWindow\"; type Type = super::Window; type ParentType = ApplicationWindow;\n}\nimpl ObjectImpl for Window { fn constructed(&self) { self.parent_constructed(); // Load latest window state let obj = self.obj(); obj.setup_settings(); obj.load_window_size(); }\n}\nimpl WidgetImpl for Window {}\nimpl WindowImpl for Window { // Save window state right before the window will be closed fn close_request(&self) -> glib::Propagation { // Save window size self.obj() .save_window_size() .expect(\"Failed to save window state\"); // Allow to invoke other event handlers glib::Propagation::Proceed }\n}\nimpl ApplicationWindowImpl for Window {} That is it! Now our window retains its state between app sessions.","breadcrumbs":"Saving Window State » Saving Window State","id":"46","title":"Saving Window State"},"47":{"body":"Sometimes you want to display a list of elements in a certain arrangement. gtk::ListBox and gtk::FlowBox are two container widgets which allow you to do this. ListBox describes a vertical list and FlowBox describes a grid. Let's explore this concept by adding labels to a ListBox. Each label will display an integer starting from 0 and ranging up to 100. Filename: listings/list_widgets/1/main.rs # use gtk::prelude::*;\n# use gtk::{\n# glib, Application, ApplicationWindow, Label, ListBox, PolicyType, ScrolledWindow,\n# };\n# # const APP_ID: &str = \"org.gtk_rs.ListWidgets1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Create a `ListBox` and add labels with integers from 0 to 100 let list_box = ListBox::new(); for number in 0..=100 { let label = Label::new(Some(&number.to_string())); list_box.append(&label); }\n# # let scrolled_window = ScrolledWindow::builder()\n# .hscrollbar_policy(PolicyType::Never) // Disable horizontal scrolling\n# .min_content_width(360)\n# .child(&list_box)\n# .build();\n# # // Create a window\n# let window = ApplicationWindow::builder()\n# .application(app)\n# .title(\"My GTK App\")\n# .default_width(600)\n# .default_height(300)\n# .child(&scrolled_window)\n# .build();\n# # // Present window\n# window.present();\n# } We cannot display so many widgets at once. Therefore, we add ListBox to a gtk::ScrolledWindow . Now we can scroll through our elements. Filename: listings/list_widgets/1/main.rs # use gtk::prelude::*;\n# use gtk::{\n# glib, Application, ApplicationWindow, Label, ListBox, PolicyType, ScrolledWindow,\n# };\n# # const APP_ID: &str = \"org.gtk_rs.ListWidgets1\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) {\n# // Create a `ListBox` and add labels with integers from 0 to 100\n# let list_box = ListBox::new();\n# for number in 0..=100 {\n# let label = Label::new(Some(&number.to_string()));\n# list_box.append(&label);\n# }\n# let scrolled_window = ScrolledWindow::builder() .hscrollbar_policy(PolicyType::Never) // Disable horizontal scrolling .min_content_width(360) .child(&list_box) .build(); // Create a window let window = ApplicationWindow::builder() .application(app) .title(\"My GTK App\") .default_width(600) .default_height(300) .child(&scrolled_window) .build(); // Present window window.present();\n# }","breadcrumbs":"List Widgets » List Widgets","id":"47","title":"List Widgets"},"48":{"body":"That was easy enough. However, we currently create one widget per element. Since each widget takes up a bit of resources, many of them can lead to slow and unresponsive user interfaces. Depending on the widget type even thousands of elements might not be a problem. But how could we possibly deal with the infinite amount of posts in a social media timeline? We use scalable lists instead! The model holds our data, filters it and describes its order. The list item factory defines how the data transforms into widgets. The view specifies how the widgets are then arranged. What makes this concept scalable is that GTK only has to create slightly more widgets than we can currently look at. As we scroll through our elements, the widgets which become invisible will be reused. The following figure demonstrates how this works in practice. 100 000 elements is something ListBox will struggle with, so let's use this to demonstrate scalable lists. We start by defining and filling up our model. The model is an instance of gio::ListStore . The main limitation here is that gio::ListStore only accepts GObjects. So let's create a custom GObject IntegerObject that which is initialized with a number. Filename: listings/list_widgets/2/integer_object/mod.rs # mod imp;\n# # use glib::Object;\n# use gtk::glib;\n# glib::wrapper! { pub struct IntegerObject(ObjectSubclass);\n} impl IntegerObject { pub fn new(number: i32) -> Self { Object::builder().property(\"number\", number).build() }\n}\n# This number represents the internal state of IntegerObject. Filename: listings/list_widgets/2/integer_object/imp.rs # use std::cell::Cell;\n# # use glib::Properties;\n# use gtk::glib;\n# use gtk::prelude::*;\n# use gtk::subclass::prelude::*;\n# // Object holding the state\n#[derive(Properties, Default)]\n#[properties(wrapper_type = super::IntegerObject)]\npub struct IntegerObject { #[property(get, set)] number: Cell,\n}\n# # // The central trait for subclassing a GObject\n# #[glib::object_subclass]\n# impl ObjectSubclass for IntegerObject {\n# const NAME: &'static str = \"MyGtkAppIntegerObject\";\n# type Type = super::IntegerObject;\n# }\n# # // Trait shared by all GObjects\n# #[glib::derived_properties]\n# impl ObjectImpl for IntegerObject {}\n# We now fill the model with integers from 0 to 100 000. Please note that models only takes care of the data. Neither Label nor any other widget is mentioned here. Filename: listings/list_widgets/2/main.rs # mod integer_object;\n# # use gtk::{\n# gio, glib, Application, ApplicationWindow, Label, ListView, PolicyType,\n# ScrolledWindow, SignalListItemFactory, SingleSelection,\n# };\n# use gtk::{prelude::*, ListItem};\n# use integer_object::IntegerObject;\n# # const APP_ID: &str = \"org.gtk_rs.ListWidgets2\";\n# # fn main() -> glib::ExitCode {\n# // Create a new application\n# let app = Application::builder().application_id(APP_ID).build();\n# # // Connect to \"activate\" signal of `app`\n# app.connect_activate(build_ui);\n# # // Run the application\n# app.run()\n# }\n# # fn build_ui(app: &Application) { // Create a `Vec` with numbers from 0 to 100_000 let vector: Vec = (0..=100_000).map(IntegerObject::new).collect(); // Create new model let model = gio::ListStore::new::(); // Add the vector to the model model.extend_from_slice(&vector);\n# # let factory = SignalListItemFactory::new();\n# factory.connect_setup(move |_, list_item| {\n# let label = Label::new(None);\n# list_item\n# .downcast_ref::()\n# .expect(\"Needs to be ListItem\")\n# .set_child(Some(&label));\n# });\n# # factory.connect_bind(move |_, list_item| {\n# // Get `IntegerObject` from `ListItem`\n# let integer_object = list_item\n# .downcast_ref::()\n# .expect(\"Needs to be ListItem\")\n# .item()\n# .and_downcast::()\n# .expect(\"The item has to be an `IntegerObject`.\");\n# # // Get `Label` from `ListItem`\n# let label = list_item\n# .downcast_ref::()\n# .expect(\"Needs to be ListItem\")\n# .child()\n# .and_downcast::