Graphical User Interfaces

Modern software 2D rendering, native desktop presentation, rich interactive widgets, and deterministic headless testing with zero-cost region discipline.

🏛️ Architecture & Core Philosophy

Vela's standard GUI library (std/gui) is built around three foundational design principles:

1. Software 2D Rasterizer

A fast, pixel-accurate CPU rasterizer drawing directly into a linear 32-bit RGBA frame buffer. No external rendering engine or heavyweight GPU stack required.

2. Native Presentation Backend

Direct presentation to native platform windows (X11 on Linux) when a display server is present, with automatic graceful fallback to virtual headless operation in CI.

3. Per-Frame Region Discipline

All transient frame allocations (formatted strings, layout calculations, temporary bounds) live in a single frame region that resets each tick with zero heap fragmentation.

📐 Geometry & Color Primitives

All widgets and canvas operations are positioned using straightforward value shapes:

; Geometry primitives
fixed pt = Point { x: 30, y: 50 }
fixed sz = Size { width: 200, height: 40 }
fixed box = Rect { x: 20, y: 30, width: 160, height: 36 }

; Check if a point falls within a rectangle (e.g. for mouse clicks)
when box.contains(mouse_pos)
    show("clicked inside box")
fi

Color Representation

Colors are represented as 32-bit RGBA values with convenient constructor helpers:

fixed c1 = Color.rgb(37, 99, 235)           ; Solid RGB (alpha = 255)
fixed c2 = Color.rgba(15, 23, 42, 180)       ; RGBA with transparency
fixed c3 = Color.hex(0x38BDF8)              ; Hexadecimal literal (0xRRGGBB)

; Built-in color constants
fixed white = Color.white()
fixed black = Color.black()
fixed trans = Color.transparent()

🎨 Styling System (Themes & Modifiers)

The Style shape provides a clean, unified way to theme any widget. You can use standard presets or customize individual properties fluently:

Preset Background Border Intended Usage
Style.primary() Royal Blue (0x2563EB) Light Blue Primary call-to-action buttons, submit actions
Style.secondary() Muted Slate (0x475569) Slate 400 Secondary actions, auxiliary controls
Style.success() Emerald Green (0x059669) Green 400 Confirmations, add items, positive feedback
Style.danger() Crimson Red (0xDC2626) Red 400 Deletions, resets, critical cancellations
Style.dark() Obsidian (0x0F172A) Slate 700 Dark mode panels, text inputs, list boxes
Style.light() Crisp Gray (0xF1F5F9) Slate 300 Light mode elements and high-contrast containers

Fluent Style Customization

Modify presets without rewriting full shapes using modifier methods:

fixed custom_style = Style.primary()
    .with_bg(Color.hex(0x6366F1))           ; Change background to Indigo
    .with_border(Color.hex(0xA5B4FC), 2)    ; 2px lighter border
    .with_padding(8)                        ; Adjust internal padding

🧩 Rich Widget Gallery

Vela provides out-of-the-box widgets designed for common desktop application tasks:

Button

Interactive push button with automated hover, active pressed state, border styling, and centered label drawing.

Label

Crisp typography display supporting custom text colors and optional background pill pill rendering.

TextBox

Single-line input with focus outline glow, placeholder support, cursor pipe indicator, and backspace handling.

ListBox

Selectable list holding items with mouse hover highlighting, row click selection, and active item queries.

CheckBox

Interactive toggle box with animated check fill, hover feedback, and paired text description.

ProgressBar

Horizontal progress indicator bounded between 0% and 100% with customizable fill color.

ImageView

Graphic container with framed borders and built-in checkerboard pattern for media previews.

MessageBox & InputBox

Modal dialog overlays with darkened backdrop dimming, title bar, message/input fields, and OK buttons.

VBox & HBox

Flow layout helpers that automatically advance cursor coordinates with consistent widget spacing.

🔄 The Window & Event Loop

Desktop applications run on an event loop. Opening a Window creates the native window and gives access to its Canvas:

include std/gui { Window, Canvas, GuiEvent, Point, Rect, Color, Button, is_interactive }

task main()
    flex win = Window.open("My Application", 400, 300) { failure err
        show("Failed to open window")
        <-
    }
    fixed canvas = win.canvas()

    loop win.is_open
        region frame default
        fixed ev = win.poll_event()

        match ev
        case GuiEvent.Redraw
            canvas.clear(Color.hex(0x0F172A))
            canvas.draw_text("Hello, Vela GUI!" at frame, Point { x: 40, y: 50 }, Color.white())
            win.present()

        case GuiEvent.MouseDown x y btn
            show("Mouse clicked at: ", x, ", ", y)

        case GuiEvent.CloseRequested
            win.close()
            break

        case _
        fi
    fi
fi

🤖 Headless Automated Testing

Deterministic GUI Testing Without an X Server

In CI environments or server runners where DISPLAY is unavailable, is_interactive() returns false. You can push synthetic events using win.push_event(...) to simulate exact user interactions deterministically!

when not is_interactive()
    ; Synthetic event sequence:
    win.push_event(1, 50, 140, 1)   ; Simulate MouseDown at (50, 140)
    win.push_event(7)               ; Simulate CloseRequested
fi

🚀 Complete Example: Modern Counter Application

Here is the complete code for an interactive counter featuring custom button styling, a progress bar, and automated test dispatching:

; An interactive counter application built with std/gui.
include std/gui { Window, Canvas, GuiEvent, Point, Rect, Color, Style, Button, Label, ProgressBar, is_interactive }

task draw_scene(canvas: Canvas, flex& win: Window, count: int, btn_inc: Button, btn_dec: Button, btn_rst: Button, progress: ProgressBar, title_label: Label, r: region default)
    canvas.clear(Color.hex(0x0F172A))

    title_label.draw(canvas)
    canvas.draw_text("Current count: {count}" at r, Point { x: 30, y: 55 }, Color.hex(0x38BDF8))

    ; Draw progress bar reflecting count percentage (0 - 100%)
    progress.draw(canvas)

    ; Draw styled action buttons
    btn_inc.draw(canvas, Point { x: 0, y: 0 }, false)
    btn_dec.draw(canvas, Point { x: 0, y: 0 }, false)
    btn_rst.draw(canvas, Point { x: 0, y: 0 }, false)

    win.present()
fi

task main()
    flex win = Window.open("Rich GUI Counter", 340, 220) { failure _
        show("failed opening window")
        <-
    }

    fixed title_label = Label { pos: Point { x: 30, y: 25 }, text: "Vela Modern Counter", color: Color.white() }

    fixed btn_inc = Button { rect: Rect { x: 30, y: 130, width: 85, height: 36 }, label: "+ Add", style: Style.success() }
    fixed btn_dec = Button { rect: Rect { x: 125, y: 130, width: 85, height: 36 }, label: "- Sub", style: Style.primary() }
    fixed btn_rst = Button { rect: Rect { x: 220, y: 130, width: 85, height: 36 }, label: "Reset", style: Style.danger() }

    flex progress = ProgressBar { rect: Rect { x: 30, y: 85, width: 275, height: 18 }, fill_color: Color.hex(0x10B981) }

    flex count = 0
    fixed canvas = win.canvas()

    ; Simulate user clicks when running in automated headless testing
    when not is_interactive()
        win.push_event(1, 50, 140, 1)   ; click "+ Add"
        win.push_event(1, 50, 140, 1)   ; click "+ Add"
        win.push_event(1, 50, 140, 1)   ; click "+ Add"
        win.push_event(1, 145, 140, 1)  ; click "- Sub"
        win.push_event(1, 240, 140, 1)  ; click "Reset"
        win.push_event(7)               ; CloseRequested
    fi

    loop win.is_open
        region frame default
        fixed ev = win.poll_event()
        match ev
        case GuiEvent.Redraw
            progress.set_value(count * 20)
            draw_scene(canvas, &win, count, btn_inc, btn_dec, btn_rst, progress, title_label, frame)
        case GuiEvent.MouseDown x y btn
            fixed click_pos = Point { x: x, y: y }
            when btn_inc.rect.contains(click_pos)
                count++
                progress.set_value(count * 20)
                show("count incremented: ", count, " (progress: ", progress.value, "%)")
                draw_scene(canvas, &win, count, btn_inc, btn_dec, btn_rst, progress, title_label, frame)
            else when btn_dec.rect.contains(click_pos)
                count--
                progress.set_value(count * 20)
                show("count decremented: ", count, " (progress: ", progress.value, "%)")
                draw_scene(canvas, &win, count, btn_inc, btn_dec, btn_rst, progress, title_label, frame)
            else when btn_rst.rect.contains(click_pos)
                count = 0
                progress.set_value(0)
                show("count reset: ", count, " (progress: ", progress.value, "%)")
                draw_scene(canvas, &win, count, btn_inc, btn_dec, btn_rst, progress, title_label, frame)
            fi
        case GuiEvent.CloseRequested
            win.close()
            show("counter app closed; final count: ", count)
            break
        case _
        fi
    fi
fi