Reactive

Signals, derived values and reactive collections.

Contents:

  • k.signal: A value with a timeline.
  • k.Signal: A value that changes over the timeline: .set (instant), .to (animated), .now.
  • k.computed: Derived value with several dependencies: k.computed(lambda: f(a(), b())).
  • k.Expr: A value that may change over time.
  • k.list: List signal.
  • k.ListSignal: Python lists are not tracked; k.list([...]) is.
  • k.python: Marks an opaque Python function (external libraries, math, untraceable logic).
  • k.lerp: Interpolation modes of a signal, passed as k.signal(..., lerp=): linear (default), round (integers), step (switches at the end), step_start (switches at the start) and pointwise (lists of points, point by point).

Methods in this area:

  • x.map: Applies a function to a signal: hour.map(solar_curve).

Back to the reference index.

k.signal (function)

k.signal(
    initial: bool,
    *,
    lerp: LerpMode | None = "linear",
    kind: str | None = None,
) -> Signal[bool]
k.signal(
    initial: float,
    *,
    lerp: LerpMode | None = "linear",
    kind: str | None = None,
) -> Signal[float]
k.signal(
    initial: tuple[float, float],
    *,
    lerp: LerpMode | None = "linear",
    kind: str | None = None,
) -> Signal[Vec]
k.signal(
    initial: T,
    *,
    lerp: LerpMode | None = "linear",
    kind: str | None = None,
) -> Signal[T]

A value with a timeline. x.set(v) changes it at the cursor, s.play(x.to(v)) animates it, x.now reads the value at the cursor (build phase) and x() does a tracked read inside lambdas. Passing the signal to a prop creates a reactive binding. lerp=None switches in a single step. Every object prop is a signal with the same API.

Parameters:

Name Type Default Description
initial bool required
lerp LerpMode | None "linear" lerp=None switches in a single step.
kind str | None None

Example:

@k.scene
def signal_demo(s: k.Scene):
    r = k.signal(0.5)
    c = k.Circle(r=r).place(at="center")
    label = k.Text(lambda: f"r = {r():.2f}").place(at="top", margin=0.8)
    s.add(c, label)
    s.play(r.to(2), duration=2)
    r.set(1)
    s.wait(0.5)

See also: k.computed, x.map, obj.set.

k.Signal (class)

k.Signal(scene: Scene, sid: int, kind: str, lerp_mode: str = "linear")

A value that changes over the timeline: .set (instant), .to (animated), .now.

k.Signal[float] in annotations (scene params, component code).

Documented together with k.signal.

Parameters:

Name Type Default Description
scene Scene required
sid int required
kind str required
lerp_mode str "linear"

Attributes:

Attribute Type Description
x.kind
x.lerp

Members:

  • now: Value at the build cursor.
  • set: Instant change at the cursor.
  • to: Animated change from the value at the cursor to value.
  • unbind: Drop a reactive binding, keeping the current value.

Inherited from k.Expr: map, x, y.

k.Signal.now (property)

x.now: T  # read-only

Value at the build cursor.

k.Signal.set (method)

x.set(value: Val[T])

Instant change at the cursor. Passing a signal or lambda creates a binding.

Parameters:

Name Type Default Description
value Val[T] required

k.Signal.to (method)

x.to(
    value: Val[T],
    *,
    duration: float | None = None,
    ease: EaseLike | None = None,
    delay: float = 0.0,
    blend: Blend = "replace",
) -> Animation

Animated change from the value at the cursor to value.

Parameters:

Name Type Default Description
value Val[T] required Animated change from the value at the cursor to value.
duration float | None None
ease EaseLike | None None
delay float 0.0
blend Blend "replace"

k.Signal.unbind (method)

x.unbind()

Drop a reactive binding, keeping the current value.

k.computed (function)

k.computed(fn: Callable[[], Expr[T]]) -> Expr[T]
k.computed(fn: Callable[[], T]) -> Expr[T]

Derived value with several dependencies: k.computed(lambda: f(a(), b())). The function is traced to native code (error K0310 if it is not traceable); derived values are read-only (animate the sources). For a single signal, use x.map(fn).

Parameters:

Name Type Default Description
fn Callable[[], Expr[T]] required

Example:

@k.scene
def derived(s: k.Scene):
    w = k.signal(2.0)
    h = k.signal(1.0)
    area = k.computed(lambda: w() * h())
    box = k.Rect(w=w, h=h).place(at="center")
    label = k.Text(lambda: f"area = {area():.1f}").place(at="top", margin=0.8)
    s.add(box, label)
    s.play(w.to(4), h.to(2), duration=2)

See also: x.map, k.signal, k.python.

k.Expr (class)

k.Expr()

A value that may change over time. Read it with .now (build) or x() (reactive).

Documented together with x.map.

Members:

  • now: Value at the build cursor (scene body, component build(), event handlers).
  • map: Applies a function to a signal: hour.map(solar_curve).
  • set: Not allowed: derived values are read-only (K0303).
  • to: Not allowed: derived values are read-only (K0303); animate the source instead.
  • x: x component of a vector expression.
  • y: y component of a vector expression.

k.Expr.now (property)

x.now: T_co  # read-only

Value at the build cursor (scene body, component build(), event handlers).

k.Expr.map (method)

x.map(fn: Callable[[T_co], Expr[U]]) -> Expr[U]
x.map(fn: Callable[[T_co], U]) -> Expr[U]

Applies a function to a signal: hour.map(solar_curve). The function is traced to native code, so use k functions (k.sin, k.where...), not math or if; for opaque Python, x.map(k.python(fn)).

Parameters:

Name Type Default Description
fn Callable[[T_co], Expr[U]] required Apply fn to this value.

Example:

def solar_curve(h: float) -> float:
    return k.max(0, 6 * k.sin(k.pi * (h - 6) / 12))

@k.scene
def mapping(s: k.Scene):
    hour = k.time.map(lambda t: t * 4)
    radius = hour.map(solar_curve) * 0.2 + 0.1
    sun = k.Circle(r=radius, fill=k.YELLOW, fill_opacity=1).place(at="center")
    s.add(sun)
    s.wait(5)

See also: k.computed, k.python, k.time.

k.Expr.set (method)

x.set(value: Any)

Not allowed: derived values are read-only (K0303).

Parameters:

Name Type Default Description
value Any required

k.Expr.to (method)

x.to(value: Any, **kwargs: Any) -> Any

Not allowed: derived values are read-only (K0303); animate the source instead.

Parameters:

Name Type Default Description
value Any required
**kwargs Any variadic

k.Expr.x (property)

x.x: Expr[float]  # read-only

x component of a vector expression.

k.Expr.y (property)

x.y: Expr[float]  # read-only

y component of a vector expression.

k.list (function)

k.list() -> ListSignal[Any]
k.list(items: Iterable[T]) -> ListSignal[T]

List signal. Plain Python lists are not tracked; k.list([...]) records append, insert, pop and swap at the cursor, and lambdas that read it with items() follow the changes.

Parameters:

Name Type Default Description
items Iterable[T] required

Example:

@k.scene
def queue_demo(s: k.Scene):
    queue = k.list(["ann", "bea"])
    label = k.Text(lambda: f"queue: {queue()}").place(at="center")
    s.add(label)
    s.wait(1)
    queue.append("cal")
    s.wait(1)

See also: k.signal.

k.ListSignal (class)

k.ListSignal(scene: Scene, sid: int, kind: str, lerp_mode: str = "linear")

Python lists are not tracked; k.list([...]) is. Changes switch in steps.

Documented together with k.list.

Parameters:

Name Type Default Description
scene Scene required
sid int required
kind str required
lerp_mode str "linear"

Members:

  • append: Append item at the cursor.
  • insert: Insert item at index, at the cursor.
  • pop: Remove and return the item at index, at the cursor.
  • swap: Exchange the items at i and j, at the cursor.

Inherited from k.Signal: now, set, to, unbind. Inherited from k.Expr: map, x, y.

k.ListSignal.append (method)

listsignal.append(item: T)

Append item at the cursor.

Parameters:

Name Type Default Description
item T required Append item at the cursor.

k.ListSignal.insert (method)

listsignal.insert(index: int, item: T)

Insert item at index, at the cursor.

Parameters:

Name Type Default Description
index int required Insert item at index, at the cursor.
item T required Insert item at index, at the cursor.

k.ListSignal.pop (method)

listsignal.pop(index: int = -1) -> T

Remove and return the item at index, at the cursor.

Parameters:

Name Type Default Description
index int -1 Remove and return the item at index, at the cursor.

k.ListSignal.swap (method)

listsignal.swap(i: int, j: int)

Exchange the items at i and j, at the cursor.

Parameters:

Name Type Default Description
i int required Exchange the items at i and j, at the cursor.
j int required Exchange the items at i and j, at the cursor.

k.python (function)

k.python(fn: Callable[[A], R], vectorized: Literal[False] = False) -> PythonFn[A, R]
k.python(fn: Callable[..., object], vectorized: Literal[True]) -> PythonFn[float, float]

Marks an opaque Python function (external libraries, math, untraceable logic). It is precomputed in the resolve phase, one call per frame, and the render only reads the table. vectorized=True receives the whole timeline as a numpy array. Prefer k functions: they run natively at no extra cost.

Parameters:

Name Type Default Description
fn Callable[[A], R] required Mark fn as opaque Python: it is called once per frame during resolve, never at render.
vectorized Literal[False] False vectorized=True receives the whole timeline as a numpy array.

Example:

import math

import kinemo as k

def opaque(t: float) -> float:
    return 1 + 0.5 * math.sin(t) ** 2

@k.scene
def explicit_cost(s: k.Scene):
    c = k.Circle(r=k.time.map(k.python(opaque))).place(at="center")
    s.add(c)
    s.wait(3)

See also: x.map, k.computed.

k.lerp (class)

k.lerp  # namespace of constants

Written as: k.lerp.linear | k.lerp.round | k.lerp.step | k.lerp.step_start | k.lerp.pointwise

Interpolation modes of a signal, passed as k.signal(..., lerp=): linear (default), round (integers), step (switches at the end), step_start (switches at the start) and pointwise (lists of points, point by point).

Values:

Name Value
k.lerp.linear "linear"
k.lerp.round "round"
k.lerp.step "step_end"
k.lerp.step_start "step_start"
k.lerp.pointwise "pointwise"
k.lerp.layout "layout"

Example:

@k.scene
def counter(s: k.Scene):
    n = k.signal(0, lerp=k.lerp.round)
    label = k.Text(lambda: f"{n():.0f} steps", size=0.8).place(at="center")
    s.add(label)
    s.play(n.to(10), duration=2)

See also: k.signal.