GTK+, Glade, and Cross-Platform UI Development with bindings for C++, Python and Rust

This article examines the role of GTK+ (GIMP Toolkit) and Glade Interface Designer in enabling cross-platform graphical user interface (GUI) development. It explores the toolkit’s architecture, design philosophy, and integration with three major programming languages: C++, Python, and Rust. By analyzing language bindings, developer workflows, and ecosystem maturity, the paper highlights GTK+ as a versatile foundation for modern, portable applications across Linux, Windows, and macOS.

Introduction

Cross-platform GUI development remains a persistent challenge due to differences in operating system APIs and rendering models. GTK+, an open-source widget toolkit originally developed for the GIMP image editor, provides a unified abstraction layer for building native-looking applications. Paired with Glade, a WYSIWYG interface designer, developers can separate UI design from application logic, streamlining workflows across languages.

The application described here is a simple calculator built with GTK in conjunction with Glade. The UI is the same, the only difference is the selected binding (C++, Python and Rust). The source code can be found here. The operating system is Ubuntu 20.04 running on Hyper-V.

Glade Interface Designer

  • Visual UI builder: Allows drag-and-drop design without manual layout code.
  • XML-based output: Stores UI definitions in .glade files, which can be loaded at runtime.
  • Separation of concerns: Designers focus on interface, developers on logic.
  • Cross-language compatibility: Works seamlessly with bindings in C++, Python, and Rust.

Language Bindings

  • C++
    • gtkmm: Official C++ binding for GTK+.
    • Provides type safety and modern C++ features.
    • Commonly used in Linux desktop applications and cross-platform projects.
  • Python
    • PyGObject: Python binding for GTK+.
    • Simplifies rapid prototyping and scripting.
    • Strong integration with Glade, enabling fast iteration cycles.
  • Rust
    • gtk-rs: Rust bindings for GTK, Cairo, and GLib.
    • Offers memory safety and concurrency advantages.
    • Supports Glade-based UI definitions, enabling declarative design.

Comparative Analysis

  • C++
    • gtkmm: Official C++ binding for GTK+.
    • Provides type safety and modern C++ features.
    • Commonly used in Linux desktop applications and cross-platform projects.
  • Python
    • PyGObject: Python binding for GTK+.
    • Simplifies rapid prototyping and scripting.
    • Strong integration with Glade, enabling fast iteration cycles.
  • Rust
    • gtk-rs: Rust bindings for GTK, Cairo, and GLib.
    • Offers memory safety and concurrency advantages.
    • Supports Glade-based UI definitions, enabling declarative design.
LanguageBindingStrengthsLimitations
C++gtkmmType satefy, mature ecosystemVerbose syntax, slower iteration
PythonPyGObjectRapid prototyping, ease of learningPerformance overhead
Rustgtk-rsMemory safety, modern language featuresEcosystem still maturing

As evidenced by the image above (C++ code represents 44.9%, Rust 28.%, Python 13.1%. CMake is used by C++ to build the solution and it represents 13.5%). We’re compiling/using C++ 17.

Now, let’s dissect some of the code that perform the same operation. In this case, the method responsible to connect UI (buttons) to code.

C++

void MainWindowController::connect_buttons() {
const std::vector<std::string> digits = {
"btn_0", "btn_1", "btn_2", "btn_3", "btn_4",
"btn_5", "btn_6", "btn_7", "btn_8", "btn_9"
};

for (const auto& id : digits) {
Gtk::Button* btn;
builder->get_widget(id, btn);
if (btn) {
  btn->signal_clicked().connect([this, btn]() {
  on_digit_pressed(btn->get_label());
});
        }
}

const std::vector<std::pair<std::string, std::string>> operators = {
{"btn_add", "+"}, {"btn_subtract", "-"},
{"btn_multiply", "*"}, {"btn_divide", "/"}
};

for (const auto& [id, symbol] : operators) {
Gtk::Button* btn;
builder->get_widget(id, btn);
if (btn) {
btn->signal_clicked().connect([this, symbol]() {
on_operator_pressed(symbol);
});
}
}

Gtk::Button* btn_clear;
builder->get_widget("btn_clear", btn_clear);
if (btn_clear) {
btn_clear->signal_clicked().connect(sigc::mem_fun(*this, &MainWindowController::on_clear_pressed));
}

Gtk::Button* btn_equals;
builder->get_widget("btn_equals", btn_equals);
if (btn_equals) {
btn_equals->signal_clicked().connect(sigc::mem_fun(*this, &MainWindowController::on_equals_pressed));
}
}

Rust

// Get the display entry
let entry: Entry = builder.object("entry_display").unwrap();

// Shared state for current expression
let expression = Rc::new(RefCell::new(String::new()));

// Helper to wire a button by id
let connect_button = |id: &str, expr: Rc<RefCell<String>>, entry: Entry| {
let entry_clone = entry.clone();
let button: Button = builder.object(id).unwrap();
let label = button.label().unwrap_or_else(|| "".into());

button.connect_clicked(move |_| {
let mut exp = expr.borrow_mut();
exp.push_str(&label);
entry_clone.set_text(&exp);
});
};

// Wire numeric buttons
for id in &["btn_0","btn_1","btn_2","btn_3","btn_4","btn_5","btn_6","btn_7","btn_8","btn_9"] {
connect_button(id, expression.clone(), entry.clone());
}

// Wire operator buttons
for id in &["btn_add","btn_subtract","btn_multiply","btn_divide"] {
connect_button(id, expression.clone(), entry.clone());
}

// Clear button
{
let button: Button = builder.object("btn_clear").unwrap();
let entry_clone = entry.clone();
let expr = expression.clone();
button.connect_clicked(move |_| {
expr.borrow_mut().clear();
entry_clone.set_text("");
});
}

// Equals button
{
let entry_clone = entry.clone();
let button: Button = builder.object("btn_equals").unwrap();
let expr = expression.clone();
button.connect_clicked(move |_| {
let exp = expr.borrow().clone();
let result = evaluate_expression(&exp);
entry_clone.set_text(&result);
expr.borrow_mut().clear();
});
}

Python

# Wire numeric buttons
for i in range(10):
btn = builder.get_object(f"btn_{i}")
btn.connect("clicked", self.on_button_clicked, entry)

# Wire operator buttons
for op in ["add", "subtract", "multiply", "divide"]:
btn = builder.get_object(f"btn_{op}")
btn.connect("clicked", self.on_button_clicked, entry)

# Clear button
    builder.get_object("btn_clear").connect("clicked", self.on_clear_clicked, entry)

# Equals button
     builder.get_object("btn_equals").connect("clicked", self.on_equals_clicked, entry)

def on_button_clicked(self, button, entry):
label = button.get_label()
self.expression += label
entry.set_text(self.expression)

def on_clear_clicked(self, button, entry):
self.expression = ""
entry.set_text("")

def on_equals_clicked(self, button, entry):
try:
expr = (self.expression
.replace("×", "*")
.replace("÷", "/")
.replace("−", "-"))
result = eval(expr)
entry.set_text(str(result))
self.expression = ""
except Exception:
entry.set_text("Error")
self.expression = ""

Case Study: Glade + Rust

A simple Rust application can load a .glade file and bind widgets to logic using gtk-rs. For example, a button click can update a label dynamically. This demonstrates how declarative UI design integrates with Rust’s safe concurrency model, offering both productivity and reliability.

Conclusion

GTK+ and Glade provide a robust foundation for cross-platform UI development, enabling developers to choose the language that best fits their project goals.

  • C++ offers maturity and performance.
  • Python excels in accessibility and rapid iteration.
  • Rust introduces safety and modern paradigms.

Together, these bindings ensure GTK+ remains relevant in diverse application domains, from desktop productivity tools to experimental cross-platform projects.

References

GNOME Project. (n.d.). GTK: The GTK Project. Retrieved from https://www.gtk.org
GNOME Project. (n.d.). Glade Interface Designer. Retrieved from https://glade.gnome.org
gtkmm Development Team. (n.d.). gtkmm: C++ interface for GTK. Retrieved from https://www.gtkmm.org
PyGObject Documentation. (n.d.). Python GTK+ 3 Tutorial. Retrieved from
https://pygobject.readthedocs.io
gtk-rs Project. (n.d.). Rust bindings for GTK. Retrieved from https://gtk-rs.org

Leave a Reply

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