Skip to content

State Management

mike-ward edited this page May 17, 2026 · 1 revision

State Management

Go-gui's state model has two layers: a per-window typed slot for application state, and an internal per-widget state map used by the framework itself. Both live in the Window — there are no globals, no singletons, and no hidden shared mutable state.


Application state — gui.State[T]

Each window holds exactly one typed state slot. Declare the struct that represents your application's state, pass a pointer to it when creating the window, and retrieve it anywhere with a generic accessor:

type App struct {
    Count   int
    Query   string
    Results []string
}

w := gui.NewWindow(gui.WindowCfg{
    Title: "My App",
    State: &App{},
})

Inside any view function or event callback:

a := gui.State[App](w)
a.Count++

gui.State[T](w) is a type-assertion — it panics if the wrong type is passed. This is intentional: it catches mismatches at startup during development, not silently at runtime.

Rules:

  • One type per window. All state for that window goes into that struct.
  • The view function reads from the struct; callbacks write to it; the next frame reflects the new values. No notify/subscribe step is needed.
  • Multiple windows each have their own independent slot — no cross-window shared state unless you manage it yourself (a shared pointer in the structs is fine).

Why no global state?

Each window is self-contained. Tests can create windows with different state without interfering with each other. Multi-window applications are straightforward: each window has its own State[T] slot and the two can communicate only through explicit shared pointers, channels, or callbacks — never through hidden framework machinery.


Framework state — StateMap[K, V]

The framework uses a keyed map per window to store widget-internal state that persists across frames: scroll offsets, input cursor positions, animation progress, and so on.

// Internal framework usage (illustration):
sy := StateMap[uint32, float32](w, nsScrollY, capScroll)
offset, ok := sy.Get(idScroll)
sy.Set(idScroll, newOffset)

StateMap[K, V](w, namespace, capacity) returns a bounded map scoped to a string namespace. The capacity is a hard ceiling — the map evicts the oldest entry when full, preventing unbounded memory growth in long-running applications with dynamically-created widget IDs.

Framework namespaces (nsScrollX, nsScrollY, nsInput, etc.) are internal and unexported. You can use StateMap in your own code with your own namespace strings:

type HoverState struct{ HoverTime float32 }

// Per-row hover tracking in a list:
hm := gui.StateMap[string, HoverState](w, "my-hover", 256)
hs, _ := hm.Get(rowID)

In practice, most application code never touches StateMap directly — gui.State[T] covers nearly all use cases. StateMap is useful when you need per-widget-instance state that is indexed by a dynamic key (row IDs, tab IDs, etc.) rather than a single top-level struct.

Clone this wiki locally