Documentation
¶
Overview ¶
Package skin is a declarative theme + layout + interaction-state engine for the go-widgets toolkit — the "Edje analogue" of the fleet, named generically as skin.
An app describes a widget's PARTS (rectangles, text, images), their per-STATE visuals (fill/ink/border/radius/geometry) and the SIGNAL→state PROGRAMS that wire interaction to appearance in a DATA FILE (`.skin.json`). Re-skinning — or building an entirely new control — then needs no Go recompile: the running program loads different bytes.
The three layers of the toolkit compose cleanly:
- the existing Go layouts (VBox/HBox/Grid/Frame) do INTER-widget placement;
- skin does INTRA-widget parts + interaction (an Object is a single toolkit.Widget, so it drops straight into those layouts);
- github.com/go-widgets/toolkit/anim drives the transitions.
Format ¶
The canonical surface is JSON via the standard library's encoding/json, with ZERO third-party dependencies (a sovereignty requirement of the fleet). The parse pipeline is deliberately layered so a friendlier front-end (an HCL surface, say) can be bolted on LATER without touching the runtime:
raw bytes ──json──▶ document (documented Go struct model) ──validate──▶ *Theme
See Load. Only the first arrow is JSON-specific; everything downstream of the document model is format-agnostic. We do NOT depend on hashicorp/hcl (we don't own it); an HCL front-end, if ever added, would emit the same document model and reuse the whole validator + runtime unchanged.
Size- and font-relative geometry ¶
Part geometry scales to ANY object size and font metrics. A rel1/rel2 endpoint is a FRACTIONAL position across its reference box (relative, 0..1) plus a pixel offset and a FONT-RELATIVE offset (offset_em, in em = glyph-height units resolved against the active github.com/go-widgets/toolkit font). A part may additionally hold a width:height ratio inside its allotted rect via aspect/aspect_mode (the Edje aspect idea — none/neither/horizontal/ vertical/both), seated by the part's align. So the shipped switch/check/card collections render correctly at arbitrary bounds and under any SetFont rather than assuming the default 5x7 face.
Runtime ¶
Load parses+validates every collection into a Theme. Theme.New instantiates a live Object for one collection; the Object is a full toolkit.Widget. Drive it with Object.Signal ("mouse,in", "mouse,down", …), advance transitions from the host frame loop with Object.Tick, and paint with Object.Draw. Emitted signals leave via Object.SignalsOut so a host can wire them to an github.com/go-widgets/mvvm.Command.
Example ¶
Example loads a one-collection document, instantiates it, drives its state machine with a signal and inspects the result — the whole pipeline in miniature.
package main
import (
"fmt"
"github.com/go-widgets/skin"
"github.com/go-widgets/toolkit"
)
func main() {
const doc = `{
"collections": {
"pill": {
"min": {"w": 60, "h": 20},
"parts": [
{"name": "bg", "type": "rect",
"rel1": {}, "rel2": {"relative": [1,1]},
"states": {
"default": {"color": "@surface", "radius": "pill"},
"active": {"color": "@accent", "radius": "pill"}
}}
],
"programs": [
{"on": "select", "target": ["bg"], "to": "active", "in": 0, "emit": "selected"}
]
}
}
}`
theme, err := skin.Load([]byte(doc))
if err != nil {
panic(err)
}
fmt.Println("collections:", theme.Collections())
obj, _ := theme.New("pill")
obj.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 60, H: 20})
before, _ := obj.State("bg")
obj.Signal("select") // instant (in:0), also emits "selected"
after, _ := obj.State("bg")
fmt.Printf("bg: %s -> %s\n", before, after)
fmt.Println("emitted:", <-obj.SignalsOut())
r, _ := obj.PartRect("bg")
fmt.Printf("bg rect: %dx%d\n", r.W, r.H)
}
Output: collections: [pill] bg: default -> active emitted: selected bg rect: 60x20
Index ¶
- type Collection
- type Drawable
- type MVVMContext
- type Object
- func (o *Object) Animating() bool
- func (o *Object) Bind(src TextSource)
- func (o *Object) Draw(p painter.Painter, th *toolkit.Theme)
- func (o *Object) OnEvent(ev toolkit.Event)
- func (o *Object) PartRect(name string) (toolkit.Rect, bool)
- func (o *Object) SetImage(name string, d Drawable)
- func (o *Object) SetState(name string)
- func (o *Object) Signal(name string)
- func (o *Object) SignalsOut() <-chan string
- func (o *Object) State(part string) (state string, ok bool)
- func (o *Object) Tick(now time.Time) bool
- type Part
- type PartType
- type Program
- type State
- type TextSource
- type Theme
- type UnknownCollectionError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Collection ¶
type Collection struct {
Name string
MinW, MinH int
Parts []*Part
Programs []*Program
// contains filtered or unexported fields
}
Collection is one named widget/layout template: a minimum size, an ordered list of parts (drawn back-to-front) and the signal→state programs that drive them. Parts resolve their geometry in list order, so a part may only anchor to a sibling declared BEFORE it (no forward references, no cycles).
type Drawable ¶
Drawable is a host-registered painter for a PartImage — the "swallow" seam for content the rect/text vocabulary can't express (a vector icon, a photo). It is handed the part's resolved rect and the part's current ink so the glyph can track state tints (pressed/disabled) exactly like a toolkit widget's host-supplied Icon callback.
type MVVMContext ¶
type MVVMContext struct {
// contains filtered or unexported fields
}
MVVMContext is a TextSource backed by github.com/go-widgets/mvvm observables: it maps a document's text_from paths (e.g. "$.title") to live string [mvvm.Observable]s. Because Object.Draw pulls the current value each frame, an observable's later Set shows on the next paint — the data binding the toolkit's MVVM layer is meant to provide, wired into skin's declarative text parts.
func NewMVVMContext ¶
func NewMVVMContext() *MVVMContext
NewMVVMContext returns an empty context. Register paths with MVVMContext.Set.
func (*MVVMContext) Get ¶
func (c *MVVMContext) Get(path string) (string, bool)
Get resolves path to its observable's current value, reporting ok=false when the path is unbound so the part falls back to its static Text.
func (*MVVMContext) Set ¶
func (c *MVVMContext) Set(path string, o *mvvm.Observable[string]) *MVVMContext
Set binds a text_from path to an observable and returns the context so calls chain: ctx.Set("$.title", title).Set("$.subtitle", subtitle).
type Object ¶
Object is one live instance of a collection: a full toolkit.Widget that owns its parts' current states and any in-flight transitions. Drop it into a VBox/HBox/Grid like any other widget; drive its state machine with Object.Signal and advance transitions with Object.Tick.
func (*Object) Bind ¶
func (o *Object) Bind(src TextSource)
Bind sets the source Object resolves text_from paths against. Binding is a pull model: Object.Draw reads the current value each frame, so a later change to the source shows on the next paint with no re-bind. A nil src clears the binding (parts fall back to their static Text).
func (*Object) Draw ¶
Draw paints every part back-to-front through p using th's palette (pass Theme.Palette for the document's own palette). It satisfies toolkit.Widget.
func (*Object) OnEvent ¶
OnEvent translates toolkit input into skin signals so an Object behaves like any interactive widget inside a container: hover in/out from EventMouseMove, press from EventClick (also raising the activation signal "mouse,click"), and release from EventMouseUp. A collection wires these to states via programs; events with no matching program are ignored.
func (*Object) PartRect ¶
PartRect returns a part's resolved rectangle in its current settled state, against the object's current bounds — the frame it would paint into this tick. ok is false for an unknown part name. Useful for a host that hit-tests or overlays a specific sub-part (a chip's close slot, a card's header).
func (*Object) SetImage ¶
SetImage registers a Drawable under name so a PartImage referencing that name paints through it. Registering nil removes the drawable.
func (*Object) SetState ¶
SetState instantly puts every part into the named state (no animation), cancelling any transition in flight. It models app-owned state — a control the app marks "disabled", a tab it marks "selected" — that is set directly rather than derived from pointer signals. Parts that don't describe name fall back to their "default" description at draw time.
func (*Object) Signal ¶
Signal drives the state machine: every program whose `on` equals name fires, transitioning its target parts and raising its `emit` (if any) outward. Unmatched signals are a harmless no-op.
func (*Object) SignalsOut ¶
SignalsOut returns the channel emitted signals are delivered on. Wire it to an github.com/go-widgets/mvvm.Command (or any handler) to react to a program's `emit`. The channel is buffered; drain it from the host loop.
type Part ¶
type Part struct {
Name string
Type PartType
TextFrom string // mvvm-style binding path ($.title); "" = none
Text string // static text fallback when TextFrom is empty/unbound
Image string // drawable name for PartImage; "" = none
// contains filtered or unexported fields
}
Part is one element of a collection: a primitive with default geometry, an optional text/image source, and a map of per-state descriptions. Every part MUST define a "default" state; other states are optional and fall back to "default" when the object is in a state the part doesn't describe.
type PartType ¶
type PartType int
PartType is the kind of primitive a part paints.
const ( // PartRect is a (optionally rounded, optionally bordered) filled rectangle. PartRect PartType = iota // PartText draws a string (from a binding or a static literal) in the // toolkit's active font, positioned by the part's align within its box. PartText // PartImage delegates to a host-registered drawable (see // [Object.SetImage]) — the "swallow"/external seam for content the // rect/text vocabulary can't express (a real vector icon, a photo …). PartImage )
type Program ¶
type Program struct {
On string
Target []string
To string
In float64
Ease toolkit.Easing
Emit string
}
Program is one signal→state rule: when signal On is received, every part in Target transitions To the named state over In seconds shaped by Ease, and (if Emit is set) a signal Emit is raised outward. A Target may be empty (a pure emitter). In == 0 snaps instantly with no animation.
type State ¶
type State struct {
Name string
// contains filtered or unexported fields
}
State is one part description: the visual it paints while the part is in this state, plus optional geometry/alignment overrides (so a part can MOVE or RESIZE between states — how a switch knob slides). Any unset visual inherits nothing; it simply isn't painted (an unset fill draws no body, an unset border draws no stroke).
type TextSource ¶
TextSource resolves a part's text_from path (e.g. "$.title") to a live string. Get reports ok=false when the path is unbound, so the part falls back to its static Text. See MVVMContext for an github.com/go-widgets/mvvm -backed implementation.
type Theme ¶
type Theme struct {
// contains filtered or unexported fields
}
Theme is a parsed+validated set of skin collections together with the palette its @-tokens resolve against. It is the output of Load and the factory for live [Object]s (see Theme.New). A Theme is immutable after Load except for Theme.SetPalette; it holds no per-instance state, so one Theme backs any number of concurrent Objects.
func Load ¶
Load parses jsonSrc and validates every collection, returning a ready Theme whose palette defaults to toolkit.DefaultLight. Unknown JSON keys are rejected (a typo'd field is an error, not a silent no-op). Every failure is a rich error naming the offending collection/part/state/program.
func (*Theme) Collections ¶
Collections returns the collection names in sorted order — handy for a host that enumerates what a loaded document offers.
func (*Theme) New ¶
New instantiates the named collection as a live Object, sized to the collection's min. It errors if the collection is not in this Theme.
func (*Theme) Palette ¶
Palette returns the toolkit.Theme this skin's @-tokens resolve against. It defaults to toolkit.DefaultLight; swap it (or a toolkit.LoadGTKTheme result, whose Extra map feeds @extra:<name> tokens) via Theme.SetPalette. The returned pointer is the live palette — a caller may pass it straight to Object.Draw.
func (*Theme) SetPalette ¶
SetPalette replaces the palette @-tokens resolve against. Passing the result of toolkit.LoadGTKTheme makes skin a strict superset of that loader: every canonical field plus every @define-color entry (via @extra:<name>) becomes referenceable from a document. A nil p restores toolkit.DefaultLight.
type UnknownCollectionError ¶
type UnknownCollectionError struct{ Name string }
UnknownCollectionError reports that Theme.New was asked for a collection the document doesn't define.
func (*UnknownCollectionError) Error ¶
func (e *UnknownCollectionError) Error() string