Skip to content

BuLEEto/Skald

Repository files navigation

Skald

An Elm-architecture GUI framework for Odin with immediate-mode-level performance. Pure-functional state, rebuild-every-frame rendering, and a modern dark/light aesthetic out of the box.

Skald gallery — interactive demo

Skald gallery — dark theme Skald gallery — light theme

examples/00_gallery — the shipped widget set in both themes.

Skald reads like iced or Elm at the API level, and its rebuild-every-frame model puts it in the immediate-mode performance tier rather than the retained-tree one. See docs/benchmarks.md for actual numbers.

Pure-Odin text backend (runa) is now the default. Skald ships runa as the default text engine, with vendor:fontstash retained as a fallback (build with -define:SKALD_RUNA=false to opt out). Runa brings real OpenType shaping (ligatures, GPOS kerning, contextual alternates), COLRv0 + COLRv1 colour emoji (with linear / radial / sweep gradients), and subpixel-x positioning — and it's faster than fontstash on every benched workload (gallery 2.2× faster).

Highlights

  • Elm architectureinit / update / view with a pure Msg union. No imperative widget handles, no signal wiring; your app's State struct is the source of truth for app-level data.
  • Immediate-mode performance — the view tree is rebuilt every frame from the app's state, so there's no retained widget graph to diff or keep in sync. Frame times under 1 ms on most apps on a modern desktop GPU (Linux); capped by display refresh on macOS.
  • GPU rendering — pure-Odin Vulkan 1.3 backend (vendor:vulkan), one pipeline for rects + text + images.
  • Two text backends — the pure-Odin runa engine ships vendored at skald/third_party/runa/ and is the default since 1.0: full OpenType shaping (ligatures, GPOS kerning), COLRv0 + COLRv1 colour emoji (with linear / radial / sweep gradients), and subpixel-x positioning. Runa is faster than fontstash on every benched workload (gallery 2.2× faster). vendor:fontstash is retained as a smaller, no-shaping fallback — opt back in with -define:SKALD_RUNA=false if you need it.
  • Async — the Command(Msg) effect system runs file I/O, native dialogs, and delays through core:nbio on the main thread; completions round-trip back as regular Msg values. For sync libraries that aren't nbio-shaped (postgres, sqlite, big-file parsers), cmd_thread runs the blocking call on a worker thread and delivers the return value as a Msg. The UI never freezes; view and update always run single-threaded.
  • Real widget set — buttons, checkboxes, sliders, selects, text inputs (single + multi-line, with undo/clipboard/selection), tables, virtual lists, tabs, menus, dialogs, image, split panes, toasts, drag-and-drop, context menus, command palette, tree, date / time / color pickers.
  • Themable — dark + light themes ship in-tree (GitHub Primer / Radix tuned); custom themes are a plain struct. Ships with i18n Labels, font-fallback chaining (font_add_fallback), and a system_theme() probe plus an on_system_theme_change callback so apps can follow the OS light/dark setting if they want.
  • Window state hooksinitial_window_state to seed the window on launch, on_window_state_change to hear about user resize / move / maximize. Your app decides where/how to persist the state (JSON on disk, embedded in a settings.db, wherever) — Skald just reports the current geometry as a plain struct. Cookbook has the pattern.
  • Multi-windowcmd_open_window / cmd_close_window spawn and tear down extra OS windows (popovers, notification bubbles, panels, HUDs). Each gets its own Vulkan swapchain, input, and widget store; the app's view is called per window with ctx.window set so one proc can render all of them. Device, pipeline, fonts, and images stay shared — only the swapchain and per-frame plumbing are per-window.
  • Keyboard-first — Tab ring, shift-Tab, focus trap inside modals, focus restoration on dialog/palette close, widget_tab_index for explicit ordering, global shortcut() registration.
  • Cross-platform — Linux (primary), Windows, macOS.

One C dependency

Skald has one external C dependency: SDL3, for cross-platform windowing, input, clipboard, file dialogs, and HiDPI / multi-monitor handling. We use it via Odin's vendor:sdl3 bindings — those are Odin procs that map to SDL3's C ABI, but the runtime that actually does the work is the C library (libSDL3.so / SDL3.dll / libSDL3.dylib), so a Skald binary links against and loads compiled C code at startup. vendor:sdl3 is bindings, not a re-implementation.

Everything else — Vulkan renderer, OpenType shaping (via the vendored pure-Odin runa engine), layout, widgets, atlas, theming, async — is pure Odin top-to-bottom.

Why SDL3 specifically: cross-platform windowing and input is a vast edge-case surface (Wayland ↔ X11 transitions, IME, pen tablets, fractional scaling, multi-monitor refresh, macOS Spaces, Windows raw input, fullscreen modes, drag-and-drop) and SDL3 is the most battle-tested option that handles all of it. Rolling our own per-OS platform layer (X11+Wayland / Win32 / Cocoa) would be months of work and a long tail of edge cases to re-discover. A pure-Odin platform layer is on the table post-1.0 if uptake justifies the maintenance cost; for v1 the trade-off doesn't pay back.

Quick start

Prerequisites vary by platform. See PLATFORMS.md for the exact commands; the short version:

  • Linux — install SDL3 (sudo apt install libsdl3-0 or equivalent) and a Vulkan loader (libvulkan1 on most distros). On Ubuntu 24.04 LTS, SDL3 isn't yet packaged — PLATFORMS.md has the build-from- source recipe. First-time Odin install also needs the stb static libs built once: make -C $ODIN_ROOT/vendor/stb/src.
  • Windows — Odin's vendor:sdl3 bundles SDL3.dll and build.bat copies it next to the exe automatically. Vulkan loader ships with recent GPU drivers or the LunarG Vulkan SDK.
  • macOSbrew install sdl3 plus the LunarG Vulkan SDK (installs MoltenVK + loader). Source ~/VulkanSDK/*/setup-env.sh in your shell, or run the SDK's system-install script.
git clone <your-fork> skald && cd skald
./build.sh 07_counter run  # build + run the counter example

On Windows:

build.bat 07_counter run

Hello, Skald

package hello

import "core:fmt"
import "gui:skald"

State :: struct { count: int }

Msg :: enum { Inc, Dec }

init :: proc() -> State { return {} }

update :: proc(s: State, m: Msg) -> (State, skald.Command(Msg)) {
    switch m {
    case .Inc: return {count = s.count + 1}, {}
    case .Dec: return {count = s.count - 1}, {}
    }
    return s, {}
}

view :: proc(s: State, ctx: ^skald.Ctx(Msg)) -> skald.View {
    th := ctx.theme
    return skald.col(
        skald.text(fmt.tprintf("Count: %d", s.count),
            th.color.fg, th.font.size_display),
        skald.spacer(th.spacing.xl),
        skald.row(
            skald.button(ctx, "", Msg.Dec, width = 64),
            skald.button(ctx, "+", Msg.Inc, width = 64,
                bg = th.color.primary, fg = th.color.on_primary),
            spacing = th.spacing.md,
        ),
        padding     = th.spacing.xl,
        main_align  = .Center,
        cross_align = .Center,
    )
}

main :: proc() {
    skald.run(skald.App(State, Msg){
        title  = "Hello",
        size   = {640, 400},
        theme  = skald.theme_dark(),
        init   = init,
        update = update,
        view   = view,
    })
}

See examples/07_counter for the annotated version.

Documentation

  • docs/getting_started.md — install, run an example, understand what the code is doing.
  • docs/guide.md — build a small app from scratch, picking up widgets and patterns as you go.
  • docs/cookbook.md — short recipes for the things you'll keep reaching for: forms, dialogs, shortcuts, theming, async, persistence.
  • docs/gotchas.md — the foot-guns worth knowing about before you hit them.
  • docs/widgets.md — per-widget reference.
  • docs/widget_choice.md — short decision tree for "which widget should I reach for here?"
  • docs/examples.md — annotated index of every example, grouped by concept.
  • docs/distributing.md — shipping your Skald app to end users: bundling SDL3 on Linux, app bundles on macOS, what each platform's user actually needs installed.
  • docs/architecture.md — how the framework fits together (for people extending Skald, not using it).
  • docs/benchmarks.md — frame-time and memory numbers with the env vars to reproduce them.
  • Per-function reference — run odin doc ./skald from the project root.

Repository layout

skald/          the framework package (pure Odin; imports vendor:vulkan/sdl3)
examples/       NN_topic/main.odin — runnable demos (52 of them)
docs/           tutorial, cookbook, widget reference, benchmarks
build.sh/.bat   build one example into ./build/ (use RELEASE=1 to strip -debug)
bench.sh        run the canonical bench suite
CHANGELOG.md    per-release notes
LICENSE         zlib
NOTICE          bundled-Inter + third-party acknowledgements
PLATFORMS.md    per-platform setup + known quirks

Platforms

Platform Status
Linux Primary target (X11 + Wayland via SDL3)
Windows Supported, SDL3 DLL auto-copied by build
macOS Supported via MoltenVK

The name

A skáld (Old Norse) was a court poet — composer and reciter of ornate stanzaic verse in intricate alliterative meters, built up from individual stanzas into longer praise poems called drápur. The metaphor fits: a Skald app is one view composed from small widgets, each shaped with care. It's also a thematic nod to Odin itself, which the framework is built on.

License

Skald is licensed under the zlib license — see LICENSE. Permissive: commercial use, modification, and redistribution are all fine, with three light restrictions (don't misrepresent origin, mark altered versions, keep the notice). Matches Odin's own license for zero ecosystem friction.

Bundled-asset and third-party acknowledgements live in NOTICE (separated from LICENSE so GitHub's auto-detector recognises the zlib header cleanly).

The bundled Inter typeface — InterVariable plus the static-weight Inter-Bold, Inter-Italic, and Inter-BoldItalic faces used by rich_text for emphasis — ships under the SIL Open Font License 1.1; the full text travels alongside the fonts at skald/assets/InterVariable-OFL.txt.

Acknowledgments

Skald stands on good shoulders. Credit and thanks to:

  • Odin (Ginger Bill) — the amazing language itself.
  • SDL3 (Sam Lantinga and the SDL contributors) — windowing, input, clipboard, file dialogs, drag-and-drop.
  • Inter (The Inter Project Authors) — the bundled UI typeface. InterVariable is the default; the static-weight Bold / Italic / BoldItalic faces ship alongside it for rich_text's emphasis spans.
  • runa (Lee Fry + contributors) — pure-Odin text engine vendored at skald/third_party/runa/. zlib licence. Default text backend since 1.0; full OpenType shaping (GSUB / GPOS), Arabic / Indic / SEA shaping, RTL + bidi, and COLRv0 / COLRv1 colour emoji.
  • fontstash (Mikko Mononen) — glyph atlas management, shipped via vendor:fontstash. Legacy text backend retained as a fallback (-define:SKALD_RUNA=false).
  • stb (Sean T. Barrett) — stb_truetype (used inside fontstash) and stb_image (PNG loading).
  • Vulkan — specification by the Khronos Group; on macOS, Vulkan calls are translated to Metal by MoltenVK (originally by Brenwill Workshop Ltd., now maintained under Khronos).

If you build something nice with Skald, no obligation — but I'd love to see it.

Contributing

Small fixes: send a PR. Larger changes: open an issue first. The open work areas the project would most benefit from — particularly complex-script shaping (Arabic / Hebrew / Devanagari / Thai), where the right contributor is a native speaker of the target language — are written up in CONTRIBUTING.md.

About

A declarative GUI framework for Odin. The predictability of Elm with the performance of immediate mode.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages