slint

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 7 Imported by: 0

README

go-slint (wip)

Go bindings for the Slint declarative UI toolkit.

Example

package main

import (
	_ "embed"
	"runtime"

	"github.com/rileylov/go-slint"
)

func init() { runtime.LockOSThread() } // Slint is thread-affine

//go:embed app.slint
var ui string

func main() {
	app, _ := slint.Compile(ui, slint.WithStyle("fluent"))
	defer app.Close()
	win, _ := app.Create("AppWindow")
	defer win.Close()

	win.OnCallback("clicked", func([]any) any {
		n, _ := win.Int("counter")
		win.Set("counter", n+1)
		return nil
	})
	win.Run()
}

Runnable examples in cmd/examples: hello, counter, todo (live model), clock (timer).

make lib                       # build the native shim into lib/<os>_<arch>/
go run ./cmd/examples/todo

Quickstart

Everything goes through one CLI, goslint. You need Go and a C compiler (for cgo) — not Rust: the native Slint library is downloaded prebuilt.

go install github.com/rileylov/go-slint/cmd/goslint@latest

goslint init myapp        # scaffold a project (go.mod + main.go + app.slint)
cd myapp
goslint setup             # download the native lib matching your go.mod
goslint dev .             # run with live reload — edit app.slint, save, see it update

Ship it:

goslint build -o myapp .                # single self-contained binary
goslint android build -o myapp.apk .    # signed APK (arm64-v8a + x86_64)

How it fits together:

  • goslint setup reads the go-slint version from your go.mod and downloads the matching prebuilt libgoslint for your platform (checksum-verified) into ~/.cache/goslint/, writing a pkg-config file that describes how to link it.
  • goslint dev runs your app and live-reloads .slint edits with no Go rebuild (the interpreter loads markup at runtime); it rebuilds + restarts on .go changes.
  • goslint build/run wrap go build/go run with the linker flags set. Prefer plain go? eval "$(goslint env)"; go build -tags goslint_pkgconfig .
  • goslint doctor checks your toolchain and the cached library.

The desktop result is a single self-contained binary: libgoslint (and the Slint interpreter inside it) is linked statically, leaving only ubiquitous system libraries as runtime dependencies — on Linux, OpenGL and fontconfig (the fontconfig dev package is also needed at build time to link).

Platforms

Current platform status as of writing:

Platform Tested Maintained
Linux ✅ 7.0.3-arch1-1
Android ✅ CinnamonBun
Windows
iOS
macOS

Cross-compiling for Windows (from Linux)

Slint cross-compiles cleanly to Windows (its text stack is pure Rust). You need the Rust target and the mingw-w64 toolchain:

rustup target add x86_64-pc-windows-gnu
sudo pacman -S mingw-w64-gcc          # Arch (or: apt install gcc-mingw-w64-x86-64)

make build-windows                    # cross-builds goslint.dll + all examples to build/windows/

Copy build/windows/ to a Windows machine and run an .exe — keep goslint.dll in the same folder (or on PATH).

Android

Slint's Android backend (skia, via android-activity) renders straight to a NativeActivity. An APK ships two libraries per ABI: libgoslint.so (the native Slint shim — NativeActivity entry + the goslint_* C ABI) and libgoslintapp.so (your Go package built as a c-shared, dlopen'd by the Rust android_main).

Your Go package needs an Android entry that exports goslint_android_main (a //go:build android file calling runtime.LockOSThread() then slint.Compile/ Create/Run) — see cmd/examples/interop for a cross-platform example.

Build a signed APK with the goslint tool (it downloads the prebuilt libgoslint.so for each ABI, cross-builds your package, and packages + signs):

goslint android build -o myapp.apk ./path/to/pkg   # arm64-v8a + x86_64
adb install -r myapp.apk

Prereqs: the Android NDK and SDK build-tools + a platform (point ANDROID_HOME/ANDROID_NDK_HOME at them); a JDK for signing. Flags let you set -package, -label, -abi, -min-sdk, a custom -manifest, -keystore, etc. (a debug keystore is created automatically). Verified rendering on an x86_64 emulator and on arm64 phones.

Contributors building the bundled demo from source can instead use make android (it cargo-builds the shim rather than downloading it).

How it works

Three layers (details in CLAUDE.md):

  • rust/goslint-sys — a Rust shim exposing a flat C ABI over Slint's slint-interpreter. Built to lib/<os>_<arch>/libgoslint.{so,a} by make lib.
  • slintsys — the low-level cgo package (1:1 with the C ABI).
  • slint — the idiomatic Go API (this module's root package).

The interpreter approach (like Slint's Node and Python bindings) loads .slint at runtime; embed it with go:embed.

Building from source (contributors)

Working on go-slint (rather than using it) builds the native shim yourself, which needs the Rust toolchain and a Slint checkout in ./slint (the version pinned in .slint-version). This path stages the lib locally and skips goslint setup.

make lib          # cargo-build the shim + stage libs (do this before go build/test)
make test         # lib, then `go test`
make conformance  # run Slint's full .slint test corpus through the bindings

The conformance suite runs Slint's own 600+ .slint cases through the Go API (mirroring Slint's interpreter test driver): currently 0 failures.

Staying in sync with Slint

The bindings sit on slint-interpreter's stable public Rust API, so tracking upstream is one command — it bumps the pinned checkout, rebuilds, and verifies against the full conformance corpus:

make update-slint                    # track main (latest origin/master)
make update-slint SLINT_REF=v1.18.0  # pin to a release tag (recommended for stability)

If an upstream change actually affects us, make lib fails with a localized Rust compile error (pointing at the exact function) before anything is staged — a broken update can't ship silently. In practice the shim builds unchanged across Slint versions (verified compiling clean and conformance-green on both 1.16.1 and 1.17.0-dev).

License

The go-slint binding code in this repository is licensed under the MIT License.

go-slint links the Slint UI toolkit, which is separately licensed and not covered by this repository's MIT license. Slint is tri-licensed under one of:

  • GPLv3 — free, but your application must be GPL-compatible (open source);
  • a royalty-free license for desktop, mobile, and web applications — free, with an attribution requirement (a "Made with Slint" notice);
  • a commercial license — paid; removes attribution and adds support.

Any application you build with go-slint — and the prebuilt libgoslint binaries published in this project's Releases, which embed Slint — are governed by Slint's license, and you must choose and comply with one of the options above. The MIT grant on this repository applies only to the binding code and does not extend to Slint.

For all of go-slint's supported targets the royalty-free license applies, see LICENSES for more information.

Documentation

Overview

Package slint provides Go bindings for the Slint declarative UI toolkit.

UIs are written in Slint's .slint markup language and driven from Go. This package compiles .slint markup at runtime (via Slint's interpreter), creates component instances, and lets you read and write properties, handle callbacks, back models, load images, and run timers — all from Go.

Threading

Slint is thread-affine. Compile, Create, property/callback access, and Run must all happen on a single OS thread. Lock it at program start:

func init() { runtime.LockOSThread() }

To touch the UI from another goroutine, post work with InvokeFromEventLoop.

A minimal program

package main

import (
	_ "embed"
	"runtime"

	"github.com/rileylov/go-slint"
)

func init() { runtime.LockOSThread() }

//go:embed app.slint
var ui string

func main() {
	app, err := slint.Compile(ui, slint.WithStyle("fluent"))
	if err != nil {
		panic(err)
	}
	defer app.Close()

	win, err := app.Create("AppWindow")
	if err != nil {
		panic(err)
	}
	defer win.Close()

	win.OnCallback("clicked", func([]any) any {
		n, _ := win.Int("counter")
		win.Set("counter", n+1)
		return nil
	})
	win.Run()
}

Value mapping

Properties and callback arguments/returns convert between Slint and Go as:

void                          <-> nil
int / float / length / angle  <-> float64
bool                          <-> bool
string                        <-> string
struct / anonymous object     <-> map[string]any
enum                          <-> [Enum]{Type, Value}
color / solid brush           <-> [Color]
image                         <-> *[Image] (write; load via [LoadImage])
array / model                 <-> []any (read) or [*ModelHandle] (write)

Build a writable model with NewSliceModel (or NewModel for a custom Model) and assign it to an array/model property; mutating it notifies Slint.

See the cmd/examples directory for runnable apps (hello, counter, todo, clock) and PLAN.md for the architecture.

The package overview lives in doc.go.

Index

Constants

View Source
const (
	TimerSingleShot = slintsys.TimerSingleShot
	TimerRepeated   = slintsys.TimerRepeated
)

Timer modes.

Variables

This section is empty.

Functions

func InitHeadless

func InitHeadless() error

InitHeadless installs the headless testing backend (mock time, no real windows). Intended for tests; call once per process on the UI thread.

func InvokeFromEventLoop

func InvokeFromEventLoop(fn func()) error

InvokeFromEventLoop posts fn to run once on the event-loop (UI) thread. Safe to call from any goroutine; it is the only safe way to touch UI state (properties, models, callbacks) from a background goroutine.

func LiveReload

func LiveReload(path, component string, bind func(*Instance) error, opts ...Option) error

LiveReload compiles the .slint file at path, creates component (or the last exported one if component is ""), wires it with bind, shows it, and then hot-reloads whenever any .slint file beside it changes on disk — recompiling and swapping the window in place, with no Go rebuild. It runs the event loop and returns when the window is closed.

It's the engine behind `goslint dev`: because the interpreter loads markup at runtime, editing a .slint and saving updates the UI live. bind is re-invoked on each reload with the fresh instance, so wire your callbacks/properties there (note: property state resets on reload). Call runtime.LockOSThread first, as with any Slint program.

func MockElapsedTime

func MockElapsedTime(ms uint64)

MockElapsedTime advances the headless backend's mock clock by ms milliseconds.

func Quit

func Quit() error

Quit asks the running event loop to exit.

func Run

func Run() error

Run enters the Slint event loop until the last window closes or Quit is called. It blocks and must run on the UI thread.

func SingleShot

func SingleShot(intervalMs uint64, fn func())

SingleShot fires fn once after the given number of milliseconds.

func Version

func Version() string

Version returns the Slint version these bindings were built against.

Types

type Callback

type Callback = slintsys.CallbackFunc

Callback is a Go handler invoked by Slint. Its args and return use the same Go value representation as properties (float64, bool, string, map[string]any, Enum, nil, ...).

type Color

type Color = slintsys.Color

Color is an RGBA color (a `color` property, or a solid `brush`).

type Compilation

type Compilation struct {
	// contains filtered or unexported fields
}

Compilation is a successfully compiled set of components.

func Compile

func Compile(source string, opts ...Option) (*Compilation, error)

Compile compiles `.slint` source. It returns a *DiagnosticError if the source has errors.

func CompileFile

func CompileFile(path string, opts ...Option) (*Compilation, error)

CompileFile compiles a `.slint` file from disk.

func (*Compilation) Close

func (c *Compilation) Close()

Close releases the compilation's resources.

func (*Compilation) ComponentNames

func (c *Compilation) ComponentNames() []string

ComponentNames lists the exported components.

func (*Compilation) Create

func (c *Compilation) Create(name string) (*Instance, error)

Create instantiates the named component.

type DiagnosticError

type DiagnosticError struct {
	Diagnostics []slintsys.Diagnostic
}

DiagnosticError reports one or more compiler errors.

func (*DiagnosticError) Error

func (e *DiagnosticError) Error() string

type Enum

type Enum = slintsys.Enum

Enum is the Go representation of a Slint enumeration value (e.g. Enum{Type: "TextHorizontalAlignment", Value: "center"}). Structs are represented as map[string]any.

type Image

type Image = slintsys.Image

Image is a loaded image; assign it to an `image` property and Free it when done.

func LoadImage

func LoadImage(path string) (*Image, error)

LoadImage loads an image (PNG/JPEG) from a file path.

type Instance

type Instance struct {
	// contains filtered or unexported fields
}

Instance is a live component instance.

func (*Instance) Bool

func (i *Instance) Bool(name string) (bool, error)

Bool reads a boolean property.

func (*Instance) Close

func (i *Instance) Close()

Close releases the instance.

func (*Instance) Float

func (i *Instance) Float(name string) (float64, error)

Float reads a numeric property.

func (*Instance) Get

func (i *Instance) Get(name string) (any, error)

Get reads a property as a Go value (float64, bool, string, or nil).

func (*Instance) GetGlobal

func (i *Instance) GetGlobal(global, name string) (any, error)

GetGlobal reads a property of an exported global singleton.

func (*Instance) Hide

func (i *Instance) Hide() error

func (*Instance) Int

func (i *Instance) Int(name string) (int, error)

Int reads a numeric property as an int.

func (*Instance) Invoke

func (i *Instance) Invoke(name string, args ...any) (any, error)

Invoke calls a callback or function, returning its result (nil for void).

func (*Instance) InvokeGlobal

func (i *Instance) InvokeGlobal(global, name string, args ...any) (any, error)

InvokeGlobal calls a callback or function on an exported global.

func (*Instance) OnCallback

func (i *Instance) OnCallback(name string, fn Callback) error

OnCallback installs a handler for the named callback.

func (*Instance) OnGlobalCallback

func (i *Instance) OnGlobalCallback(global, name string, fn Callback) error

OnGlobalCallback installs a handler for a callback on an exported global.

func (*Instance) Run

func (i *Instance) Run() error

func (*Instance) Set

func (i *Instance) Set(name string, v any) error

Set writes a property from a Go value.

func (*Instance) SetGlobal

func (i *Instance) SetGlobal(global, name string, v any) error

SetGlobal writes a property of an exported global singleton.

func (*Instance) Show

func (i *Instance) Show() error

Show makes the window visible. Hide hides it. Run shows then runs the loop.

func (*Instance) Str

func (i *Instance) Str(name string) (string, error)

Str reads a string property.

type Model

type Model = slintsys.Model

Model is a data source backing a Slint model property / `for` loop.

type ModelHandle

type ModelHandle = slintsys.ModelHandle

ModelHandle binds a Model for use as a property value; report data changes via its Notify* methods. Obtain one with NewModel or SliceModel.Handle.

func NewModel

func NewModel(m Model) *ModelHandle

NewModel binds a custom Model implementation so it can be assigned to a property.

type Option

type Option func(*slintsys.Compiler)

Option configures a compilation.

func WithIncludePaths

func WithIncludePaths(paths ...string) Option

WithIncludePaths sets the paths used to resolve `.slint` imports.

func WithStyle

func WithStyle(style string) Option

WithStyle selects the widget style (e.g. "fluent", "material", "cupertino"). Required for `.slint` files that import "std-widgets.slint".

type SliceModel

type SliceModel struct {
	// contains filtered or unexported fields
}

SliceModel is a built-in slice-backed Model whose mutators auto-notify Slint.

func NewSliceModel

func NewSliceModel(items ...any) *SliceModel

NewSliceModel creates a slice-backed model from the given items.

func (*SliceModel) Append

func (s *SliceModel) Append(v any)

Append adds an item and notifies Slint.

func (*SliceModel) Close

func (s *SliceModel) Close()

Close releases the model's binding handle.

func (*SliceModel) Handle

func (s *SliceModel) Handle() *ModelHandle

Handle returns the binding handle (also accepted directly by property setters).

func (*SliceModel) Len

func (s *SliceModel) Len() int

Len reports the number of rows.

func (*SliceModel) RemoveAt

func (s *SliceModel) RemoveAt(row int)

RemoveAt removes the item at row and notifies Slint.

func (*SliceModel) RowCount

func (s *SliceModel) RowCount() int

func (*SliceModel) RowData

func (s *SliceModel) RowData(row int) any

func (*SliceModel) SetRowData

func (s *SliceModel) SetRowData(row int, v any)

type Timer

type Timer = slintsys.Timer

Timer fires a Go callback after an interval. Timers fire only while the event loop runs (Run); create and start them after Create.

func NewTimer

func NewTimer() *Timer

NewTimer creates a stopped timer.

Directories

Path Synopsis
cmd
androiddemo command
On non-Android platforms this is a no-op stub so `go build ./...` works; the real entry point is in app_android.go (built only for android).
On non-Android platforms this is a no-op stub so `go build ./...` works; the real entry point is in app_android.go (built only for android).
examples/chartstress command
Shared throughput-harness logic for desktop (main.go) and Android (app_android.go).
Shared throughput-harness logic for desktop (main.go) and Android (app_android.go).
examples/clock command
Command clock is an interactive go-slint example: a repeating Timer driven from Go updates the UI once per second.
Command clock is an interactive go-slint example: a repeating Timer driven from Go updates the UI once per second.
examples/counter command
Command counter is an interactive go-slint example: button clicks are handled in Go, which updates the `value` property the UI binds to.
Command counter is an interactive go-slint example: button clicks are handled in Go, which updates the `value` property the UI binds to.
examples/hello command
Command hello shows a minimal Slint window from Go.
Command hello shows a minimal Slint window from Go.
examples/interop command
Shared Go ⇄ Slint interop logic, used by both the desktop entry (main.go) and the Android entry (app_android.go).
Shared Go ⇄ Slint interop logic, used by both the desktop entry (main.go) and the Android entry (app_android.go).
examples/todo command
Command todo is an interactive go-slint example showing a live model: a Go SliceModel backs the list, and add/delete are handled in Go.
Command todo is an interactive go-slint example showing a live model: a Go SliceModel backs the list, and add/delete are handled in Go.
goslint command
Command goslint provisions the native Slint shim library for the go-slint bindings and wraps `go build`/`go run` with the right linker configuration.
Command goslint provisions the native Slint shim library for the go-slint bindings and wraps `go build`/`go run` with the right linker configuration.
viewer command
Command viewer loads and runs any .slint file through the Go bindings, like Slint's own slint-viewer.
Command viewer loads and runs any .slint file through the Go bindings, like Slint's own slint-viewer.
Package slintsys is the low-level cgo binding to the goslint-sys C ABI (Layer 1).
Package slintsys is the low-level cgo binding to the goslint-sys C ABI (Layer 1).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL