Standard library · Graphics and games

gui.reactive

Reactive properties for user interfaces: lazy bindings with automatic dependencies, trackers, change handlers, two-way links, CSS transitions and list models.

import gui.reactive

gui.reactive is the state runtime of Salam's native user interface, modelled on the property system of Slint. Every value that can change is a property. A property either holds a value or has a binding, a function that computes it from other properties. Reading a property inside a binding records a dependency by itself, so there is nothing to declare.

The work is split in two halves. A Set only compares, stores and marks the dependents dirty, stopping at nodes that are already dirty. Nothing is computed then: a dirty binding runs the next time somebody reads it, and it forgets its old dependencies and records new ones, so a binding with an if depends only on the branch it took.

Things to know:

  • Handles are small values. IntProp, StrProp and the others hold an index into the runtime, so a lambda that captures one by value still sees the live property. A constant made with ConstInt and friends holds its value in the handle and costs nothing in the runtime.
  • Setting a bound property removes the binding, as an assignment does in Slint. Use Link for a value that two places edit.
  • Text has ownership rules. StrProp.Get returns a string owned by the property, valid until the value changes. Bind takes ownership of the new string its function returns; BindCopy copies it, for literals and other properties' text.
  • Change handlers never run inside a set. OnChange queues them, the event loop calls RunPendingHandlers, and many changes give one call.
  • Binding loops are reported with the property names, for example binding loop: 'min width' -> 'preferred width' -> 'min width', instead of overflowing the stack. Name properties with Name to get readable reports.
  • The runtime is for one thread, the user interface thread. A capturing lambda keeps its environment until the program ends, so bindings made per row of a long list should read package-level state rather than capture.

A counter component

This is what a component with state count turns into. The label and the wide flag are bindings on count. A tracker stands for the screen: it runs the paint function, and its callback only notes that a repaint is needed. The loop plays the event loop: it handles a click, runs the change handlers, and repaints when asked. Clicking three times with one jump to 10 gives four paints and three saves.

gui-reactive-guide-counter.salam
import gui.reactive

mut count := reactive.IntProp {}
mut label := reactive.StrProp {}
mut wide := reactive.BoolProp {}
mut needs_paint := false

func paint():
    println "  paint:", label.Get(), wide.Get() ? "(wide)" : ""
end

func click():
    count.Set(count.Peek() + 1)
end

func main:
    count = reactive.NewInt(0).Name("count")
    label = reactive.NewStr("").Name("label")
    label.Bind((): ret "Count: " + count.Get() end)
    wide = reactive.NewBool(false)
    wide.Bind((): ret count.Get() >= 10 end)
    screen := reactive.Track((): paint() end, (): needs_paint = true end)
    saved := reactive.OnChange(count, (): println "  save", count.Get() end)

    repeat 3 in frame:
        click()
        if frame == 2:
            count.Set(10)
        end
        _n := reactive.RunPendingHandlers()
        if needs_paint:
            needs_paint = false
            _r := screen.Refresh()
        end
    end
    saved.Free()
    screen.Free()
end
Output paint: Count: 0 save 1 paint: Count: 1 save 2 paint: Count: 2 save 10 paint: Count: 10 (wide)

A CSS transition on hover

An animated property moves to each new value over time. Here the background is a binding on hovered, animated with ease-in-out over 300 ms and interpolated per colour channel. Each frame sets the clock with SetTime; the animated property depends on the clock only while it moves, and AnimationsRunning tells the loop when it can stop drawing.

gui-reactive-guide-transition.salam
import conv
import gui.reactive

mut hovered := reactive.BoolProp {}
mut background := reactive.IntProp {}
mut dirty := false

func show(ms: i64):
    c := ((background.Get() as i64) & 0xFFFFFFFF) as u64
    println "  t", ms, "background", conv.FormatHex(c)
end

func main:
    reactive.SetTime(0 as i64)
    hovered = reactive.NewBool(false)
    background = reactive.NewInt(0)
    background.Bind((): ret hovered.Get() ? 0xFF3366CC as int : 0xFFFFFFFF as int end)
    background.Animate(reactive.Animation { duration = 300, easing = reactive.ParseEasing("ease-in-out", reactive.Ease()), color = true })
    screen := reactive.Track((): _c := background.Get() end, (): dirty = true end)

    println "pointer enters at 0 ms"
    hovered.Set(true)
    mut now := 0 as i64
    until reactive.AnimationsRunning() or dirty:
        if dirty:
            dirty = false
            _r := screen.Refresh()
            show(now)
        end
        now += 100
        reactive.SetTime(now)
    end
    println "idle at", now, "ms, running", reactive.AnimationsRunning()
    screen.Free()
end
Outputpointer enters at 0 ms t 0 background ffffffff t 100 background ffd0dcf3 t 200 background ff6289d8 t 300 background ff3366cc idle at 400 ms, running false

Types

Tracker

struct Tracker
A binding without a value: it runs a function, records what the function read, and reports once when any of it changes. The window uses one for repaint and one for layout.
NameTypeDescription
nodeint = -1The index of the tracker in the runtime.
gui-reactive-Track.salam
import gui.reactive

mut redraws := 0

func main:
    x := reactive.NewInt(1)
    y := reactive.NewInt(2)
    t := reactive.Track((): println "  paint at", x.Get(), y.Get() end, (): redraws++ end).Name("paint")
    x.Set(10)
    y.Set(20)
    println "dirty", t.IsDirty(), "redraw requests", redraws
    println "refreshed", t.Refresh()
    println "refresh with nothing new", t.Refresh()

    reactive.BeginBatch()
    x.Set(3)
    println "in batch", reactive.InBatch(), "requests", redraws
    y.Set(4)
    reactive.EndBatch()
    println "after the batch", redraws
    _r := t.Refresh()

    reactive.Batch(():
        x.Set(5)
        y.Set(6)
    end)
    println "after Batch", redraws
    _r2 := t.Refresh()
    t.Free()
end
Output paint at 1 2 dirty true redraw requests 1 paint at 10 20 refreshed true refresh with nothing new false in batch true requests 1 after the batch 2 paint at 3 4 after Batch 3 paint at 5 6

IsDirty

func IsDirty(): bool
Reports whether something the tracked function read has changed since it last ran.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Refresh

func Refresh(): bool
Runs the tracked function again when the tracker is dirty, recording a fresh set of dependencies. Returns whether it ran.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Name

func Name(name: str): Tracker
Gives the tracker a name for error messages and returns it.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Free

func Free()
Removes the tracker; its dirty callback is not called again.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Handler

struct Handler
A change handler made by OnChange.
NameTypeDescription
nodeint = -1The index of the handler in the runtime, or -1 for a handler on a constant, which never runs.
gui-reactive-OnChange.salam
import gui.reactive

func main:
    count := reactive.NewInt(0)
    title := reactive.NewStr("")
    title.Bind((): ret "Count: " + count.Get() end)
    h := reactive.OnChange(title, (): println "  title changed to", title.Get() end)
    count.Set(1)
    count.Set(2)
    count.Set(3)
    println "pending", reactive.PendingHandlers()
    println "handlers run", reactive.RunPendingHandlers()
    count.Set(3)
    println "same value", reactive.RunPendingHandlers()
    h.Free()
    count.Set(4)
    println "after Free", reactive.RunPendingHandlers()
end
Outputpending 1 title changed to Count: 3 handlers run 1 same value 0 after Free 0

Free

func Free()
Removes the handler.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

EasingKind

enum EasingKind
The family of an easing function.
NameTypeDescription
LinearProgress is proportional to time.
CubicBezierA CSS cubic-bezier() curve given by x1, y1, x2, y2.
StepsA CSS steps() function with steps and position.
gui-reactive-Easing.salam
import gui.reactive
import math

func r3(v: f64): f64:
    ret math.Round(v * 1000.0) / 1000.0
end

func main:
    each e in [reactive.Linear(), reactive.Ease(), reactive.EaseIn(), reactive.EaseOut(), reactive.EaseInOut()]:
        println r3(e.At(0.25)), r3(e.At(0.5)), r3(e.At(0.75))
    end
    custom := reactive.CubicBezier(0.68, -0.6, 0.32, 1.6)
    println "custom", r3(custom.At(0.25)), r3(custom.At(0.75))
    s := reactive.Steps(3, reactive.StepPosition.JumpStart)
    println "steps", s.At(0.0), r3(s.At(0.5)), s.At(1.0)
    p := reactive.ParseEasing("cubic-bezier(0.1, 0.7, 1.0, 0.1)", reactive.Ease())
    println "parsed", p.kind == reactive.EasingKind.CubicBezier, p.x1, p.y1, p.x2, p.y2
    q := reactive.ParseEasing("steps(5, jump-none)", reactive.Ease())
    println "parsed steps", q.steps, q.position == reactive.StepPosition.JumpNone
    bad := reactive.ParseEasing("bounce", reactive.Linear())
    println "fallback", bad.kind == reactive.EasingKind.Linear
end
Output0.25 0.5 0.75 0.409 0.802 0.96 0.093 0.315 0.622 0.378 0.685 0.907 0.129 0.5 0.871 custom -0.098 1.098 steps 0.3333333333333333 0.667 1 parsed true 0.1 0.7 1 0.1 parsed steps 5 true fallback true

StepPosition

enum StepPosition
Where a steps() easing jumps, as in CSS.
NameTypeDescription
JumpStartThe first jump happens at the start (jump-start, start).
JumpEndThe last jump happens at the end (jump-end, end, the default).
JumpNoneNo jump at either end (jump-none); needs at least two steps.
JumpBothJumps at both ends (jump-both).

The example for this item is shown with the ones above: gui-reactive-Easing.salam

Easing

struct Easing
A CSS easing function. Build one with Linear, Ease, EaseIn, EaseOut, EaseInOut, CubicBezier, Steps or ParseEasing.
NameTypeDescription
kindEasingKindWhich family of function this is.
x1f64 = 0First control point, x. Must be between 0 and 1.
y1f64 = 0First control point, y. May leave 0..1 for an overshoot.
x2f64 = 1Second control point, x. Must be between 0 and 1.
y2f64 = 1Second control point, y.
stepsint = 1The number of steps of a steps() easing.
positionStepPositionWhere a steps() easing jumps.

The example for this item is shown with the ones above: gui-reactive-Easing.salam

At

func At(t: f64): f64
Returns the eased progress for a time fraction t, which is clamped to 0..1.

The example for this item is shown with the ones above: gui-reactive-Easing.salam

Animation

struct Animation
How an animated property moves to a new value: like a CSS transition.
NameTypeDescription
durationint = 0The length of the animation in milliseconds. 0 jumps at once.
delayint = 0Milliseconds to wait after the change before moving; the old value is shown meanwhile.
easingEasingThe easing function. The default is linear.
colorbool = falseTreat an int as a 0xAARRGGBB colour and interpolate each channel instead of the whole number.
gui-reactive-Animate.salam
import conv
import gui.reactive

func main:
    reactive.SetTime(0 as i64)
    left := reactive.NewF64(0.0)
    left.Animate(reactive.Animation { duration = 400, easing = reactive.EaseOut() })
    left.Set(200.0)
    println "running", reactive.AnimationsRunning(), reactive.RunningAnimations()
    until reactive.AnimationsRunning():
        println "t", reactive.Now(), "left", left.Get()
        reactive.AdvanceTime(100 as i64)
    end
    println "t", reactive.Now(), "left", left.Get(), "running", reactive.AnimationsRunning()

    reactive.SetTime(1000 as i64)
    tint := reactive.NewInt(0xFF000000 as int)
    tint.Animate(reactive.Animation { duration = 200, delay = 100, color = true })
    tint.Set(0xFFFFFFFF as int)
    repeat 4 in i:
        reactive.SetTime(1000 as i64 + (i * 100) as i64)
        println "tint", conv.FormatHex(((tint.Get() as i64) & 0xFFFFFFFF) as u64)
    end
end
Outputrunning true 1 t 0 left 0 t 100 left 75.6276261652198 t 200 left 136.9286374988214 t 300 left 181.3070698012065 t 400 left 200 running false tint ff000000 tint ff000000 tint ff808080 tint ffffffff

ModelChange

enum ModelChange
The kind of change a ListModel reports.
NameTypeDescription
RowChangedOne row got a new value.
RowsInsertedcount rows were inserted at index.
RowsRemovedcount rows were removed from index.
ResetEverything may have changed; rebuild all rows.
gui-reactive-ListModel.salam
import gui.reactive

struct Row:
    pub name: str = ""
    pub done: bool = false
end

func main:
    rows := reactive.NewListModel() as reactive.ListModel<Row>
    _id := rows.Listen((ev: reactive.ModelEvent): println "  change", ev.kind as int, "index", ev.index, "count", ev.count end)
    status := reactive.NewStr("")
    status.Bind((): ret "" + rows.Len() + " rows" end)
    rows.Push(Row { name = "milk" })
    rows.Push(Row { name = "bread" })
    rows.Insert(0, Row { name = "eggs" })
    println status.Get(), rows.Get(0).name
    rows.Set(1, Row { name = "milk", done = true })
    rows.Remove(0, 1)
    println status.Get(), rows.Get(0).done
    h := rows.OnChange((): println "  rows changed" end)
    rows.Clear()
    println "handlers", reactive.RunPendingHandlers(), status.Get()
    mut more := Vector {} as Vector<Row>
    more.push(Row { name = "tea" })
    rows.Replace(more)
    more.free()
    rows.Unlisten(_id)
    rows.Push(Row { name = "rice" })
    println status.Get(), reactive.ModelChange.Reset as int
    h.Free()
    rows.Free()
end
Output change 1 index 0 count 1 change 1 index 1 count 1 change 1 index 0 count 1 3 rows eggs change 0 index 1 count 1 change 2 index 0 count 1 2 rows true change 3 index 0 count 0 rows changed handlers 1 0 rows change 3 index 0 count 1 2 rows 3

ModelEvent

struct ModelEvent
One change notification of a ListModel.
NameTypeDescription
kindModelChangeWhat happened.
indexint = 0The first row involved.
countint = 0How many rows; for Reset, the new length.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

ListModel

struct ListModel<T>
A list of values or records that bindings can depend on and that tells listeners exactly which rows changed, so a repeated element rebuilds only those rows. Make one with NewListModel; copies of the handle share the list.
NameTypeDescription
statevoid*The heap block holding the rows, the listeners and the two dependency nodes.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Len

func Len(): int
Returns the number of rows; inside a binding it depends on inserts, removals and resets.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Get

func Get(i: int): T
Returns row i; inside a binding it depends on every change to the rows.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Set

func Set(i: int, v: T)
Replaces row i and reports RowChanged.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Push

func Push(v: T)
Appends a row and reports RowsInserted.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Insert

func Insert(i: int, v: T)
Inserts a row before row i and reports RowsInserted.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Remove

func Remove(i: int, count: int)
Removes count rows starting at i (fewer at the end of the list) and reports RowsRemoved.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Clear

func Clear()
Removes every row and reports Reset.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Replace

func Replace(rows: Vector<T>)
Copies the rows of the vector into the model, replacing what was there, and reports Reset. The caller still owns the vector.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Listen

func Listen(cb: func(ModelEvent)): int
Registers a listener that is called at once for every change, and returns its id.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Unlisten

func Unlisten(id: int)
Stops the listener with this id.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

OnChange

func OnChange(body: func()): Handler
Registers a deferred change handler that runs from RunPendingHandlers after the rows changed.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

Free

func Free()
Releases the rows, the listeners and the model's nodes. Free the model before Reset.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

IntProp

struct IntProp
A property holding an int. The handle is a small value you can copy freely; the value itself lives in the runtime. A handle with node -1 is a constant.
NameTypeDescription
nodeint = -1The index of the property in the runtime, or -1 for a constant made with Const.
fixedint = 0The value of a constant property. Unused when node is not -1.
gui-reactive-Basics.salam
import gui.reactive

func main:
    width := reactive.NewInt(3).Name("width")
    height := reactive.NewInt(4).Name("height")
    area := reactive.NewInt(0).Name("area")
    area.Bind((): ret width.Get() * height.Get() end)
    println "area", area.Get(), "bound", area.HasBinding()

    width.Set(10)
    println "dirty after a set", area.IsDirty(), "evaluations so far", reactive.Evaluations()
    println "area", area.Get(), "evaluations so far", reactive.Evaluations()
    println "peek reads without tracking", area.Peek()

    marks := reactive.DirtyMarks()
    height.Set(5)
    height.Set(6)
    println "dirty marks for two sets", reactive.DirtyMarks() - marks

    area.Set(1)
    println "a set replaces the binding", area.HasBinding(), area.Get()
    println "nodes", reactive.LiveNodes(), "edges", reactive.LiveEdges()

    width.Free()
    height.Free()
    area.Free()
    reactive.Reset()
    println "nodes after reset", reactive.LiveNodes()
end
Outputarea 12 bound true dirty after a set true evaluations so far 1 area 40 evaluations so far 2 peek reads without tracking 40 dirty marks for two sets 1 a set replaces the binding false 1 nodes 3 edges 0 nodes after reset 0

Get

func Get(): int
Returns the value. Runs the binding first when it is dirty, and records a dependency when called from inside a binding or a tracker.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

Peek

func Peek(): int
Like Get, but records no dependency, so the caller is not updated when this property changes.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

Set

func Set(v: int)
Stores a new value. Removes a plain binding first, the way a value assignment replaces a binding in Slint; on a linked property it sets the shared value and on an animated one it sets the target. Dependents are marked dirty only when the value really changed. Setting a constant is an error.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

Bind

func Bind(calc: func(): int)
Makes the property computed: calc runs lazily, on the first read after something it read has changed. On a linked property the whole group gets the binding; on an animated one the binding gives the target.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

Animate

func Animate(spec: Animation)
Animates every later change of the value: from then on a set or a binding gives the target, and reads return a value moving towards it over spec.duration milliseconds of the animation clock. Calling it again changes the spec.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

Name

func Name(name: str): IntProp
Gives the property a name used in error messages, such as a binding loop report, and returns the handle.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

HasBinding

func HasBinding(): bool
Reports whether the value is computed by a binding or comes from a two-way link.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

IsDirty

func IsDirty(): bool
Reports whether something the binding read has changed since its last run.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

func Unlink()
Leaves a two-way link and keeps the current value as a plain value. Does nothing on a property that is not linked.
gui-reactive-Link.salam
import gui.reactive

func main:
    model := reactive.NewStr("hello")
    field := reactive.NewStr("")
    reactive.Link(model, field)
    println model.Get(), field.Get()
    field.Set("typed in the field")
    println model.Get()
    model.Set("set by the program")
    println field.Get()

    volume := reactive.NewInt(5)
    slider := reactive.NewInt(0)
    knob := reactive.NewInt(0)
    reactive.Link(volume, slider)
    reactive.Link(slider, knob)
    knob.Set(9)
    println volume.Get(), slider.Get(), knob.Get()
    slider.Unlink()
    slider.Set(1)
    println volume.Get(), slider.Get(), knob.Get()
end
Outputhello hello typed in the field set by the program 9 9 9 9 1 9

Free

func Free()
Removes the property from the runtime. Bindings that read it stop being updated by it.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

I64Prop

struct I64Prop
A property holding an i64. The handle is a small value you can copy freely; the value itself lives in the runtime. A handle with node -1 is a constant.
NameTypeDescription
nodeint = -1The index of the property in the runtime, or -1 for a constant made with Const.
fixedi64 = 0The value of a constant property. Unused when node is not -1.
gui-reactive-Types.salam
import gui.reactive
import str

func main:
    bytes := reactive.NewI64(3000000000 as i64)
    scale := reactive.NewF64(1.5)
    visible := reactive.NewBool(true)
    name := reactive.NewStr("Sara")
    greeting := reactive.NewStr("")
    greeting.Bind((): ret "Hello, " + name.Get() + "!" end)
    shown := reactive.NewStr("")
    shown.BindCopy((): ret visible.Get() ? name.Get() : "hidden" end)
    println greeting.Get(), shown.Get()
    name.Set("Reza")
    visible.Set(false)
    println greeting.Get(), shown.Get()
    println bytes.Get(), scale.Get() * 2.0

    gap := reactive.ConstInt(8)
    big := reactive.ConstI64(9000000000 as i64)
    ratio := reactive.ConstF64(0.5)
    flag := reactive.ConstBool(true)
    label := reactive.ConstStr("fixed")
    println gap.Get(), big.Get(), ratio.Get(), flag.Get(), label.Get(), "node", gap.node, "fixed", gap.fixed

    kept := str.Clone(greeting.Get())
    name.Set("Mina")
    println "copy kept", kept, "now", greeting.Get()
end
OutputHello, Sara! Sara Hello, Reza! hidden 3000000000 3 8 9000000000 0.5 true fixed node -1 fixed 8 copy kept Hello, Reza! now Hello, Mina!

Get

func Get(): i64
Returns the value. Runs the binding first when it is dirty, and records a dependency when called from inside a binding or a tracker.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Peek

func Peek(): i64
Like Get, but records no dependency, so the caller is not updated when this property changes.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Set

func Set(v: i64)
Stores a new value. Removes a plain binding first, the way a value assignment replaces a binding in Slint; on a linked property it sets the shared value and on an animated one it sets the target. Dependents are marked dirty only when the value really changed. Setting a constant is an error.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Bind

func Bind(calc: func(): i64)
Makes the property computed: calc runs lazily, on the first read after something it read has changed. On a linked property the whole group gets the binding; on an animated one the binding gives the target.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Animate

func Animate(spec: Animation)
Animates every later change of the value: from then on a set or a binding gives the target, and reads return a value moving towards it over spec.duration milliseconds of the animation clock. Calling it again changes the spec.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

Name

func Name(name: str): I64Prop
Gives the property a name used in error messages, such as a binding loop report, and returns the handle.

The example for this item is shown with the ones above: gui-reactive-Types.salam

HasBinding

func HasBinding(): bool
Reports whether the value is computed by a binding or comes from a two-way link.

The example for this item is shown with the ones above: gui-reactive-Types.salam

IsDirty

func IsDirty(): bool
Reports whether something the binding read has changed since its last run.

The example for this item is shown with the ones above: gui-reactive-Types.salam

func Unlink()
Leaves a two-way link and keeps the current value as a plain value. Does nothing on a property that is not linked.

The example for this item is shown with the ones above: gui-reactive-Link.salam

Free

func Free()
Removes the property from the runtime. Bindings that read it stop being updated by it.

The example for this item is shown with the ones above: gui-reactive-Types.salam

F64Prop

struct F64Prop
A property holding an f64. The handle is a small value you can copy freely; the value itself lives in the runtime. A handle with node -1 is a constant.
NameTypeDescription
nodeint = -1The index of the property in the runtime, or -1 for a constant made with Const.
fixedf64 = 0The value of a constant property. Unused when node is not -1.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Get

func Get(): f64
Returns the value. Runs the binding first when it is dirty, and records a dependency when called from inside a binding or a tracker.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Peek

func Peek(): f64
Like Get, but records no dependency, so the caller is not updated when this property changes.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Set

func Set(v: f64)
Stores a new value. Removes a plain binding first, the way a value assignment replaces a binding in Slint; on a linked property it sets the shared value and on an animated one it sets the target. Dependents are marked dirty only when the value really changed. Setting a constant is an error.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Bind

func Bind(calc: func(): f64)
Makes the property computed: calc runs lazily, on the first read after something it read has changed. On a linked property the whole group gets the binding; on an animated one the binding gives the target.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Animate

func Animate(spec: Animation)
Animates every later change of the value: from then on a set or a binding gives the target, and reads return a value moving towards it over spec.duration milliseconds of the animation clock. Calling it again changes the spec.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

Name

func Name(name: str): F64Prop
Gives the property a name used in error messages, such as a binding loop report, and returns the handle.

The example for this item is shown with the ones above: gui-reactive-Types.salam

HasBinding

func HasBinding(): bool
Reports whether the value is computed by a binding or comes from a two-way link.

The example for this item is shown with the ones above: gui-reactive-Types.salam

IsDirty

func IsDirty(): bool
Reports whether something the binding read has changed since its last run.

The example for this item is shown with the ones above: gui-reactive-Types.salam

func Unlink()
Leaves a two-way link and keeps the current value as a plain value. Does nothing on a property that is not linked.

The example for this item is shown with the ones above: gui-reactive-Link.salam

Free

func Free()
Removes the property from the runtime. Bindings that read it stop being updated by it.

The example for this item is shown with the ones above: gui-reactive-Types.salam

BoolProp

struct BoolProp
A property holding a bool. The handle is a small value you can copy freely; the value itself lives in the runtime. A handle with node -1 is a constant.
NameTypeDescription
nodeint = -1The index of the property in the runtime, or -1 for a constant made with Const.
fixedbool = falseThe value of a constant property. Unused when node is not -1.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Get

func Get(): bool
Returns the value. Runs the binding first when it is dirty, and records a dependency when called from inside a binding or a tracker.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Peek

func Peek(): bool
Like Get, but records no dependency, so the caller is not updated when this property changes.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Set

func Set(v: bool)
Stores a new value. Removes a plain binding first, the way a value assignment replaces a binding in Slint; on a linked property it sets the shared value and on an animated one it sets the target. Dependents are marked dirty only when the value really changed. Setting a constant is an error.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Bind

func Bind(calc: func(): bool)
Makes the property computed: calc runs lazily, on the first read after something it read has changed. On a linked property the whole group gets the binding; on an animated one the binding gives the target.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Name

func Name(name: str): BoolProp
Gives the property a name used in error messages, such as a binding loop report, and returns the handle.

The example for this item is shown with the ones above: gui-reactive-Types.salam

HasBinding

func HasBinding(): bool
Reports whether the value is computed by a binding or comes from a two-way link.

The example for this item is shown with the ones above: gui-reactive-Types.salam

IsDirty

func IsDirty(): bool
Reports whether something the binding read has changed since its last run.

The example for this item is shown with the ones above: gui-reactive-Types.salam

func Unlink()
Leaves a two-way link and keeps the current value as a plain value. Does nothing on a property that is not linked.

The example for this item is shown with the ones above: gui-reactive-Link.salam

Free

func Free()
Removes the property from the runtime. Bindings that read it stop being updated by it.

The example for this item is shown with the ones above: gui-reactive-Types.salam

StrProp

struct StrProp
A property holding a str. The handle is a small value you can copy freely; the value itself lives in the runtime. A handle with node -1 is a constant.
NameTypeDescription
nodeint = -1The index of the property in the runtime, or -1 for a constant made with Const.
fixedstr = ""The value of a constant property. Unused when node is not -1.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Get

func Get(): str
Returns the text. The string belongs to the property and stays valid until its value next changes; copy it with str.Clone to keep it longer. Records a dependency like the other Get methods.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Peek

func Peek(): str
Like Get, but records no dependency. The same ownership rule applies.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Set

func Set(v: str)
Stores a copy of the text, so the caller keeps its own string. Otherwise the same as the other Set methods.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Bind

func Bind(calc: func(): str)
Makes the text computed. calc must return a new heap string, such as a concatenation or a str function result; the property owns it and frees it on the next change. For a literal or another property's text use BindCopy.

The example for this item is shown with the ones above: gui-reactive-Types.salam

BindCopy

func BindCopy(calc: func(): str)
Like Bind, but the property stores a copy of what calc returns, so calc may return a literal or another property's text.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Name

func Name(name: str): StrProp
Gives the property a name used in error messages, such as a binding loop report, and returns the handle.

The example for this item is shown with the ones above: gui-reactive-Types.salam

HasBinding

func HasBinding(): bool
Reports whether the value is computed by a binding or comes from a two-way link.

The example for this item is shown with the ones above: gui-reactive-Types.salam

IsDirty

func IsDirty(): bool
Reports whether something the binding read has changed since its last run.

The example for this item is shown with the ones above: gui-reactive-Types.salam

func Unlink()
Leaves a two-way link and keeps the current value as a plain value. Does nothing on a property that is not linked.

The example for this item is shown with the ones above: gui-reactive-Link.salam

Free

func Free()
Removes the property from the runtime. Bindings that read it stop being updated by it.

The example for this item is shown with the ones above: gui-reactive-Types.salam

Prop

struct Prop<T>
A property of any type, for records and other values the typed properties do not cover. Its value lives in a small heap cell. Set always notifies, since T may have no ==.
NameTypeDescription
nodeintThe index of the property in the runtime.
cellvoid*The heap cell holding the value and the binding.
gui-reactive-Prop.salam
import gui.reactive

struct Size:
    pub w: int = 0
    pub h: int = 0
end

func main:
    window := reactive.NewProp(Size { w = 800, h = 600 }).Name("window size")
    pad := reactive.NewInt(16)
    content := reactive.NewProp(Size {})
    content.Bind(():
        s := window.Get()
        ret Size { w = s.w - 2 * pad.Get(), h = s.h - 2 * pad.Get() }
    end)
    println content.Get(), content.HasBinding()
    h := content.OnChange((): println "  content is now", content.Peek() end)
    window.Set(Size { w = 1024, h = 768 })
    println "handlers", reactive.RunPendingHandlers()
    h.Free()
    window.Free()
    content.Free()
end
OutputSize {w = 768, h = 568} true content is now Size {w = 992, h = 736} handlers 1

Get

func Get(): T
Returns the value, running a dirty binding first and recording a dependency inside a binding.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

Peek

func Peek(): T
Returns the value without recording a dependency.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

Set

func Set(v: T)
Removes any binding, stores the value and marks dependents dirty.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

Bind

func Bind(calc: func(): T)
Makes the value computed by calc, run lazily.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

OnChange

func OnChange(body: func()): Handler
Registers a deferred change handler, as OnChange does for the typed properties.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

Name

func Name(name: str): Prop<T>
Gives the property a name for error messages and returns it.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

HasBinding

func HasBinding(): bool
Reports whether the value is computed by a binding.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

Free

func Free()
Removes the property and frees its cell.

The example for this item is shown with the ones above: gui-reactive-Prop.salam

Functions

Track

func Track(body: func()): Tracker
Runs body now, recording what it reads, and returns a tracker. The form with on_dirty calls it once, after the set that made the tracker dirty, and not again until Refresh. The callback should only schedule work, such as a repaint.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Track

func Track(body: func(), on_dirty: func()): Tracker
Runs body now, recording what it reads, and returns a tracker. The form with on_dirty calls it once, after the set that made the tracker dirty, and not again until Refresh. The callback should only schedule work, such as a repaint.

The example for this item is shown with the ones above: gui-reactive-Track.salam

OnChange

func OnChange(p: IntProp, body: func()): Handler
Registers body to run after the property's value changed. It never runs inside a set: changes are queued and RunPendingHandlers runs each handler once, however many changes there were.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

OnChange

func OnChange(p: I64Prop, body: func()): Handler
Registers body to run after the property's value changed. It never runs inside a set: changes are queued and RunPendingHandlers runs each handler once, however many changes there were.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

OnChange

func OnChange(p: F64Prop, body: func()): Handler
Registers body to run after the property's value changed. It never runs inside a set: changes are queued and RunPendingHandlers runs each handler once, however many changes there were.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

OnChange

func OnChange(p: BoolProp, body: func()): Handler
Registers body to run after the property's value changed. It never runs inside a set: changes are queued and RunPendingHandlers runs each handler once, however many changes there were.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

OnChange

func OnChange(p: StrProp, body: func()): Handler
Registers body to run after the property's value changed. It never runs inside a set: changes are queued and RunPendingHandlers runs each handler once, however many changes there were.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

PendingHandlers

func PendingHandlers(): int
The number of handlers waiting to run.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

RunPendingHandlers

func RunPendingHandlers(): int
Runs the queued change handlers whose property really changed and returns how many ran. Handlers that cause further changes are run in the same call, for up to 100 rounds.

The example for this item is shown with the ones above: gui-reactive-OnChange.salam

LastError

func LastError(): str
Returns the last error the runtime reported, such as a binding loop, or an empty string. The caller frees the result.
gui-reactive-Errors.salam
import gui.reactive

func main:
    reactive.SetErrorPrinting(false)
    a := reactive.NewInt(0).Name("min width")
    b := reactive.NewInt(0).Name("preferred width")
    a.Bind((): ret b.Get() + 1 end)
    b.Bind((): ret a.Get() + 1 end)
    println "value with the loop cut", a.Get()
    println "errors", reactive.ErrorCount()
    println reactive.LastError()
end
Outputvalue with the loop cut 2 errors 1 binding loop: 'min width' -> 'preferred width' -> 'min width'

ErrorCount

func ErrorCount(): int
The number of errors reported since the start or the last Reset.

The example for this item is shown with the ones above: gui-reactive-Errors.salam

SetErrorPrinting

func SetErrorPrinting(enabled: bool)
Turns printing errors to standard error on or off. On by default; the errors are recorded either way.

The example for this item is shown with the ones above: gui-reactive-Errors.salam

Evaluations

func Evaluations(): int
How many bindings, trackers and handlers have run. Useful to check that work is lazy.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

DirtyMarks

func DirtyMarks(): int
How many times a node went from clean to dirty. Propagation stops at nodes that are already dirty, so a second set adds nothing.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

LiveNodes

func LiveNodes(): int
The number of properties, trackers and handlers alive in the runtime.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

LiveEdges

func LiveEdges(): int
The number of recorded dependencies.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

BeginBatch

func BeginBatch()
Starts a batch: tracker callbacks wait until the matching EndBatch, so they see all the changes at once. Batches nest.

The example for this item is shown with the ones above: gui-reactive-Track.salam

EndBatch

func EndBatch()
Ends a batch and runs the tracker callbacks that became due during it.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Batch

func Batch(body: func())
Runs body as one batch.

The example for this item is shown with the ones above: gui-reactive-Track.salam

InBatch

func InBatch(): bool
Reports whether a batch is open.

The example for this item is shown with the ones above: gui-reactive-Track.salam

Reset

func Reset()
Frees every property, tracker, handler and animation and starts over. Handles made before it are no longer valid.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

Linear

func Linear(): Easing
The linear easing.

The example for this item is shown with the ones above: gui-reactive-Easing.salam

CubicBezier

func CubicBezier(x1: f64, y1: f64, x2: f64, y2: f64): Easing
A cubic-bezier(x1, y1, x2, y2) easing.

The example for this item is shown with the ones above: gui-reactive-Easing.salam

Ease

func Ease(): Easing
The CSS ease easing, cubic-bezier(0.25, 0.1, 0.25, 1).

The example for this item is shown with the ones above: gui-reactive-Easing.salam

EaseIn

func EaseIn(): Easing
The CSS ease-in easing, cubic-bezier(0.42, 0, 1, 1).

The example for this item is shown with the ones above: gui-reactive-Easing.salam

EaseOut

func EaseOut(): Easing
The CSS ease-out easing, cubic-bezier(0, 0, 0.58, 1).

The example for this item is shown with the ones above: gui-reactive-Easing.salam

EaseInOut

func EaseInOut(): Easing
The CSS ease-in-out easing, cubic-bezier(0.42, 0, 0.58, 1).

The example for this item is shown with the ones above: gui-reactive-Easing.salam

Steps

func Steps(count: int, position: StepPosition): Easing
A steps(count, position) easing.

The example for this item is shown with the ones above: gui-reactive-Easing.salam

ParseEasing

func ParseEasing(text: str, fallback: Easing): Easing
Reads a CSS easing: a keyword (linear, ease, ease-in, ease-out, ease-in-out, step-start, step-end), cubic-bezier(...) or steps(...). Returns fallback for anything it does not accept.

The example for this item is shown with the ones above: gui-reactive-Easing.salam

SetTime

func SetTime(ms: i64)
Sets the animation clock, in milliseconds. The event loop calls it once per frame; animated properties and everything that reads them become dirty, and animations that have ended are finished.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

AdvanceTime

func AdvanceTime(ms: i64)
Moves the animation clock forward by ms.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

Now

func Now(): i64
Returns the animation clock. Inside a binding it makes the binding depend on the clock.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

AnimationsRunning

func AnimationsRunning(): bool
Reports whether any animation is moving or waiting to start, so the event loop knows to keep drawing frames.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

RunningAnimations

func RunningAnimations(): int
The number of animations moving or waiting to start.

The example for this item is shown with the ones above: gui-reactive-Animate.salam

NewListModel

func NewListModel<T>(): ListModel<T>
Makes an empty list model. Give the row type with as reactive.ListModel<T>.

The example for this item is shown with the ones above: gui-reactive-ListModel.salam

NewInt

func NewInt(v: int): IntProp
Makes an int property with a starting value.

The example for this item is shown with the ones above: gui-reactive-Basics.salam

NewI64

func NewI64(v: i64): I64Prop
Makes an i64 property with a starting value.

The example for this item is shown with the ones above: gui-reactive-Types.salam

NewF64

func NewF64(v: f64): F64Prop
Makes an f64 property with a starting value.

The example for this item is shown with the ones above: gui-reactive-Types.salam

NewBool

func NewBool(v: bool): BoolProp
Makes a bool property with a starting value.

The example for this item is shown with the ones above: gui-reactive-Types.salam

NewStr

func NewStr(v: str): StrProp
Makes a text property holding a copy of v.

The example for this item is shown with the ones above: gui-reactive-Types.salam

ConstInt

func ConstInt(v: int): IntProp
Makes a constant int property. It uses no runtime memory and reading it records no dependency.

The example for this item is shown with the ones above: gui-reactive-Types.salam

ConstI64

func ConstI64(v: i64): I64Prop
Makes a constant i64 property.

The example for this item is shown with the ones above: gui-reactive-Types.salam

ConstF64

func ConstF64(v: f64): F64Prop
Makes a constant f64 property.

The example for this item is shown with the ones above: gui-reactive-Types.salam

ConstBool

func ConstBool(v: bool): BoolProp
Makes a constant bool property.

The example for this item is shown with the ones above: gui-reactive-Types.salam

ConstStr

func ConstStr(v: str): StrProp
Makes a constant text property. The string is not copied, so pass a literal or a string that outlives the handle.

The example for this item is shown with the ones above: gui-reactive-Types.salam

func Link(a: IntProp, b: IntProp)
Links two properties of the same type both ways: they share one value, and a set on either changes both. a gives the starting value and keeps its binding for the group. Linking a third property to either one joins the same group.

The example for this item is shown with the ones above: gui-reactive-Link.salam

func Link(a: I64Prop, b: I64Prop)
Links two properties of the same type both ways: they share one value, and a set on either changes both. a gives the starting value and keeps its binding for the group. Linking a third property to either one joins the same group.

The example for this item is shown with the ones above: gui-reactive-Link.salam

func Link(a: F64Prop, b: F64Prop)
Links two properties of the same type both ways: they share one value, and a set on either changes both. a gives the starting value and keeps its binding for the group. Linking a third property to either one joins the same group.

The example for this item is shown with the ones above: gui-reactive-Link.salam

func Link(a: BoolProp, b: BoolProp)
Links two properties of the same type both ways: they share one value, and a set on either changes both. a gives the starting value and keeps its binding for the group. Linking a third property to either one joins the same group.

The example for this item is shown with the ones above: gui-reactive-Link.salam

func Link(a: StrProp, b: StrProp)
Links two properties of the same type both ways: they share one value, and a set on either changes both. a gives the starting value and keeps its binding for the group. Linking a third property to either one joins the same group.

The example for this item is shown with the ones above: gui-reactive-Link.salam

NewProp

func NewProp<T>(v: T): Prop<T>
Makes a property of any type with a starting value.

The example for this item is shown with the ones above: gui-reactive-Prop.salam