GUI Toolkit Guide
Build native desktop applications with Zamin's cross-platform GUI toolkits.
Overview
Zamin ships with two GUI toolkits that share the same API surface, so code written for one works on the other with no changes:
- Sol (Windows) — A Win32-based toolkit that ships as a built-in module on Windows. No external dependencies are required.
- Luna (Linux) — A GTK4-based toolkit available behind the
lunafeature flag. Requires GTK4 development libraries on the host system.
Both toolkits expose the same function names and widget constructors. You pick the one that matches your platform, and the rest of your code stays the same.
On Windows, sol() is always available. On Linux, use luna() and build with --features luna.
Quick Start
Here is a minimal GUI application that displays a window with a label:
let gui = sol() // or luna() on Linux
let win = gui.Leo("My App", 400, 300)
let label = gui.Label(win, "Hello, Zamin!")
gui.pack(label)
gui.mainloop(win)
Save this as app.zamin and run it:
$ zamin run app.zamin
A window titled "My App" appears with the text "Hello, Zamin!" centered inside it.
Widget Reference
Leo (Window)
The top-level window that holds all other widgets.
| Signature | Description |
|---|---|
Leo(title?, width?, height?) |
Creates a new top-level window. Defaults to 640×480 if width/height are omitted. |
let gui = sol()
let win = gui.Leo("Settings", 800, 600)
Frame
A container widget used to group and organize child widgets.
| Signature | Description |
|---|---|
Frame(parent) |
Creates a frame inside the given parent widget. |
let gui = sol()
let win = gui.Leo("App", 400, 300)
let frame = gui.Frame(win)
gui.pack(frame)
Label
Displays static text. Does not accept user input.
| Signature | Description |
|---|---|
Label(parent, text?) |
Creates a label with the given text inside the parent widget. |
let gui = sol()
let win = gui.Leo("App", 400, 300)
let lbl = gui.Label(win, "Status: Ready")
gui.pack(lbl)
Button
A clickable button that triggers a callback when pressed.
| Signature | Description |
|---|---|
Button(parent, text?, callback?) |
Creates a button with optional label and click handler. |
let gui = sol()
let win = gui.Leo("App", 400, 300)
func on_click() {
print("Button was clicked!");
}
let btn = gui.Button(win, "Click Me", on_click)
gui.pack(btn)
gui.mainloop(win)
Callbacks are plain Zamin functions. They can accept arguments or be closures that capture local state.
Entry
A single-line text input field for user input.
| Signature | Description |
|---|---|
Entry(parent) |
Creates a text input field inside the parent widget. |
let gui = sol()
let win = gui.Leo("App", 400, 300)
let entry = gui.Entry(win)
gui.pack(entry)
Layout Management
Zamin GUI toolkits provide two layout methods for arranging widgets:
pack
| Signature | Description |
|---|---|
pack(widget, side?, padx?, pady?) |
Places a widget using the pack geometry manager. side can be "top", "bottom", "left", or "right". padx and pady add horizontal and vertical padding in pixels. |
gui.pack(label, "top", 10, 5)
gui.pack(btn, "left", 5, 5)
place
| Signature | Description |
|---|---|
place(widget, x, y, w?, h?) |
Places a widget at absolute coordinates. w and h set the width and height if desired. |
gui.place(label, 20, 50, 200, 30)
Widget Operations
These functions modify or query widgets after they have been created.
| Function | Description |
|---|---|
config(widget, property, value) |
Set a property on a widget. Common properties: "text", "command", "font", "fg", "bg". |
get(entry) |
Return the current text content of an Entry widget. |
insert(entry, pos, text) |
Insert text at the given position in an Entry widget. |
delete(entry, start, end?) |
Delete characters from start to end in an Entry widget. If end is omitted, deletes one character. |
title(window, text) |
Change the title of a window. |
geometry(window, w, h) |
Resize a window to the given width and height. |
destroy(widget) |
Remove a widget from the layout and free its resources. |
messagebox(text, title?) |
Show a modal message dialog with the given text and optional title. |
gui.config(btn, "text", "Updated Label")
let value = gui.get(entry)
gui.title(win, "New Title")
gui.messagebox("Operation complete!", "Info")
Example: Calculator
A complete calculator application with a display field and arithmetic buttons:
let gui = sol()
let win = gui.Leo("Calculator", 280, 350)
let display = gui.Entry(win)
gui.pack(display, "top", 10, 10)
let expression = ""
func on_digit(d) {
expression = expression + d;
gui.config(display, "text", expression);
}
func on_operator(op) {
expression = expression + " " + op + " ";
gui.config(display, "text", expression);
}
func on_clear() {
expression = "";
gui.config(display, "text", "");
}
func on_equal() {
let result = eval(expression);
gui.config(display, "text", str(result));
expression = str(result);
}
// Build the button row for digits 1-9
for row in 0..3 {
let frame = gui.Frame(win);
gui.pack(frame, "top");
for col in 0..3 {
let digit = str(row * 3 + col + 1);
let d = digit;
let btn = gui.Button(frame, digit, func() { on_digit(d) });
gui.pack(btn, "left", 2, 2);
}
}
// Bottom row: 0, clear, equals
let bottom = gui.Frame(win)
gui.pack(bottom, "top")
let b0 = gui.Button(bottom, "0", func() { on_digit("0") })
gui.pack(b0, "left", 2, 2)
let bc = gui.Button(bottom, "C", on_clear)
gui.pack(bc, "left", 2, 2)
let be = gui.Button(bottom, "=", on_equal)
gui.pack(be, "left", 2, 2)
// Operator buttons
let ops = gui.Frame(win)
gui.pack(ops, "top")
for op in ["+", "-", "*", "/"] {
let o = op;
let b = gui.Button(ops, op, func() { on_operator(o) });
gui.pack(b, "left", 2, 2);
}
gui.mainloop(win)
Example: Text Editor
A basic text editor with an input field, open button, and save button:
let gui = sol()
let win = gui.Leo("Text Editor", 600, 400)
// File path entry
let top_frame = gui.Frame(win)
gui.pack(top_frame, "top", 5, 5)
gui.Label(top_frame, "File:")
let path_entry = gui.Entry(top_frame)
gui.pack(path_entry, "left", 5, 5)
// Text area (simulated with a large Entry)
let text_area = gui.Entry(win)
gui.pack(text_area, "top", 10, 10)
// Button bar
let btn_frame = gui.Frame(win)
gui.pack(btn_frame, "top", 5, 5)
func on_open() {
let path = gui.get(path_entry);
if path == "" {
gui.messagebox("Enter a file path first.", "Error");
return;
}
let content = read(path);
gui.config(text_area, "text", content);
gui.title(win, "Text Editor - {path}");
}
func on_save() {
let path = gui.get(path_entry);
if path == "" {
gui.messagebox("Enter a file path first.", "Error");
return;
}
let content = gui.get(text_area);
write(path, content);
gui.messagebox("File saved!", "Success");
}
let open_btn = gui.Button(btn_frame, "Open", on_open)
gui.pack(open_btn, "left", 5, 5)
let save_btn = gui.Button(btn_frame, "Save", on_save)
gui.pack(save_btn, "left", 5, 5)
gui.mainloop(win)
Platform Notes
Sol (Windows)
- Built on top of the Win32 API directly — no external dependencies or runtime libraries required.
- Ships as a built-in module on Windows builds of Zamin.
- Supports standard Windows look-and-feel with native widget rendering.
Luna (Linux)
- Built on GTK4 for modern Linux desktop environments.
- Requires GTK4 development libraries to be installed:
# Debian / Ubuntu
sudo apt install libgtk-4-dev
# Fedora
sudo dnf install gtk4-devel
# Arch
sudo pacman -S gtk4
- Build Zamin with the
lunafeature enabled:
cargo build --release --features luna
macOS
- macOS support is planned for a future release. On macOS today, you can use the standard library modules to build command-line tools.
Since both Sol and Luna share the same API, you can write your GUI code once and compile it for different platforms by selecting the appropriate toolkit constructor at the top of your file.