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 ask.signal(..., lerp=):linear(default),round(integers),step(switches at the end),step_start(switches at the start) andpointwise(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 tovalue.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, componentbuild(), 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: Appenditemat the cursor.insert: Insertitematindex, at the cursor.pop: Remove and return the item atindex, at the cursor.swap: Exchange the items atiandj, 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.