Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Graphical User Interfaces (GUIs)

Graphix includes a GUI library built on the Rust iced framework. It provides native desktop windowing with GPU-accelerated rendering via wgpu, giving you high-performance graphical applications with the same reactive programming model used throughout Graphix.

What the GUI Library Offers

  • Native windowing: Real desktop windows managed by the OS, not terminal emulation
  • GPU-accelerated rendering: All drawing goes through wgpu for smooth, high-performance output
  • Reactive widgets: A rich set of interactive widgets (buttons, text inputs, sliders, etc.) that update automatically when their data changes
  • Multi-window support: Programs can create and manage multiple windows simultaneously
  • Theming: 22 built-in themes plus fully custom palettes and per-widget style overrides
  • Auto-detection: The Graphix shell detects GUI mode automatically when the program’s last expression has type Gui – no special flags needed

The Window Type

Every GUI program is built around windows. The Window type describes a window and its content:

/// A window description
type Window = {
  title: &string,
  size: &mut Size,
  theme: &Theme,
  icon: &ImageSource,
  content: &Widget
};

/// The custom display root type. An expression of this type
/// triggers the GUI display mode in the shell.
type Gui = Array<&Window>;

val window: fn(
  ?#title: &string,
  ?#size: &mut Size,
  ?#theme: &Theme,
  ?#icon: &ImageSource,
  content: &Widget
) -> Window;

All parameters except the content widget are optional. Defaults are "Graphix" for the title, { width: 800.0, height: 600.0 } for the size, and `Dark for the theme. #size is &mut because the runtime writes the window’s real size back when the user resizes it.

#icon is the window’s icon, an ImageSource: a file path (a string), encoded image `Bytes, `Svg source text, or raw `Rgba pixels. Most of the book’s examples pass #icon: &icon::icon from icon.gx, a module beside them that holds the Graphix logo as an `Svg; leave #icon out and the window has the platform’s default icon.

The final expression of your program should be an Array<&Window> – the shell sees this type and launches the GUI runtime.

Getting Started

Here is a minimal GUI program that creates a window with text and a button:

use gui::{window, button::button, column::column, text::text};

let clicks = 0;

[&window(
  #title: &"Graphix Demo",
  #theme: &`CatppuccinMocha,
  &column(
    #spacing: &20.0,
    #padding: &`All(40.0),
    #halign: &`Center,
    #width: &`Fill,
    #height: &`Fill,
    &[
      text(#size: &24.0, &"Hello, Graphix GUI!"),
      text(&"A simple window with text and a button"),
      button(
        #on_press: |c| clicks <- c ~ clicks + 1,
        #padding: &`All(10.0),
        &text(&"Click me!")
      ),
      text(&"Clicked [clicks] times")
    ]
  )
)]

Hello GUI

A few things to notice:

  • use gui::{window, ..} imports window from the top-level GUI module, which also holds the shared types like Length, Padding, and Theme (write gui::Length, or import them by name too).
  • Each widget lives in a module of its own name, so the widget function is imported by its path: button::button, column::column, text::text.
  • Widget arguments are passed as references with &. Even literal values like &20.0 and &"Hello, Graphix GUI!" are wrapped in &.
  • The program’s final value is a one-element array [&window(...)], which has type Gui.

The Reference Pattern

GUI widgets take & references so that updates propagate with fine granularity. When you write:

let name = "world";
text(&"Hello, [name]!")

The text widget holds a reference to the string expression "Hello, [name]!". When name changes, only the computations that depend on it run and only this widget’s value changes; the window is then redrawn from the current widget values.

This is especially important for interactive widgets. A text input, for example, takes a &string for its current value and provides a callback to update it:

let name = "";
text_input(#on_input: |v| name <- v, #placeholder: &"Type here...", &name)

The <- connect operator schedules name to update on the next cycle, and because the text input holds a reference to name, it automatically reflects the new value.

Theming

Every window accepts a #theme parameter that controls its visual appearance. Graphix ships with 22 built-in themes plus support for fully custom palettes and stylesheets:

use gui::{window, column::column, container::container, text::text};
mod icon;

[&window(
  #icon: &icon::icon,
  #title: &"Theme Demo",
  #theme: &`TokyoNight,
  &container(
    #width: &`Fill,
    #height: &`Fill,
    #halign: &`Center,
    #valign: &`Center,
    #padding: &`All(40.0),
    &column(
      #spacing: &20.0,
      #halign: &`Center,
      #width: &`Fill,
      &[
        text(#size: &28.0, #halign: &`Center, #width: &`Fill, &"Theme Demo"),
        text(
          #size: &16.0, #halign: &`Center, #width: &`Fill,
          &"Using TokyoNight theme")
      ]
    )
  )
)]

Theme Demo

See the theming page for the full list of built-in themes, custom palette creation, and per-widget style overrides.

Further Reading

  • Types – reference for all shared types (Widget, Length, Padding, Color, Font, etc.)
  • Theming – built-in themes, custom palettes, and per-widget stylesheets