Effects¶
An effect is a short visual transition over one or more field areas: fades, sweeps, slides, dissolves, paints, and related kinds. Effects are descriptions in Python. The active runtime lowers them onto the native paint path (terminal and web share the same runtime).
Effects do not replace layout, styling, or hooks. You trigger them when something should animate — often from a hook, after a field update, or once when a panel appears. They need an active runtime and painted field geometry.
Playing effects on fields¶
The usual entrypoint is
BaseGrid.grid_effect. Pass a known
kind string (or a built effect instance) and the field names to animate.
Field names match the attributes on the grid; during paint those slots are
tagged so the runtime can target their rects.
from xnano import BaseGrid, Field, Terminal, on_keyboard
class Panel(BaseGrid, direction="vertical"):
title: str = Field(default="Hello", height=1, border="rounded")
body: str = Field(default="Press f to fade this panel.")
@on_keyboard("f")
def fade_body(self) -> None:
self.grid_effect(
"fade", # (1)!
color="violet-400", # (2)!
duration_ms=300,
fields=["body"], # (3)!
)
Terminal().run(Panel())
- Known kinds are listed under each family below (and as
KnownEffectKindin the API). - Effect APIs use
color=/background=for color-driven kinds. That is separate from field styling, which prefersforeground=onField. fieldsis required for work to start. An empty list or omit means no effect runs. Multiple names are allowed.
grid_effect returns True when at least one field area was found and
the runtime accepted the effect. It returns False when there is no active
runtime (for example outside a session) or the fields could not be resolved.
When the first argument is a kind string, keyword arguments mirror
Effect(...): duration_ms, color,
background, direction, gradient_length, randomness, interpolation,
effects, child, times, and key. When the first argument is already an
AbstractEffect instance, those kwargs are ignored — set options on the
instance itself.
Fades¶
Fades interpolate color on the target field area.
| Kind | Class | Role |
|---|---|---|
"fade" |
FadeEffect |
Fade foreground to a target |
"fade_from" |
FadeFromEffect |
Fade foreground from a source |
"fade_to" |
FadeToEffect |
Fade foreground and background to targets |
"fade_from_both" |
FadeFromBothEffect |
Fade foreground and background from sources |
self.grid_effect(
"fade",
color="emerald-400",
duration_ms=250,
fields=["body"],
)
self.grid_effect(
"fade_from",
color="slate-700",
duration_ms=250,
fields=["body"],
)
self.grid_effect(
"fade_to",
color="white",
background="slate-900",
duration_ms=300,
fields=["body"],
)
self.grid_effect(
"fade_from_both",
color="violet-400",
background="black",
duration_ms=300,
fields=["body"],
)
Typed equivalents:
from xnano.effects import (
FadeEffect,
FadeFromBothEffect,
FadeFromEffect,
FadeToEffect,
)
FadeEffect(color="emerald-400", duration_ms=250)
FadeFromEffect(color="slate-700", duration_ms=250)
FadeToEffect(color="white", background="slate-900", duration_ms=300)
FadeFromBothEffect(
color="violet-400",
background="black",
duration_ms=300,
)
Dissolve and typewriter coalesce¶
Cell-level transitions without a motion direction.
| Kind | Class | Role |
|---|---|---|
"dissolve" |
DissolveEffect |
Random pixel dissolve |
"coalesce" |
CoalesceEffect |
Typewriter-style cell assembly |
self.grid_effect("dissolve", duration_ms=400, fields=["body"])
self.grid_effect("coalesce", duration_ms=500, fields=["body"])
from xnano.effects import CoalesceEffect, DissolveEffect
DissolveEffect(duration_ms=400)
CoalesceEffect(duration_ms=500)
Directional sweeps¶
Directional sweeps reveal or hide content with a motion gradient.
| Kind | Class | Role |
|---|---|---|
"sweep_in" |
SweepInEffect |
Directional sweep revealing content |
"sweep_out" |
SweepOutEffect |
Directional sweep hiding content |
direction is one of "left_to_right", "right_to_left", "up_to_down",
"down_to_up". Optional: gradient_length (cells), randomness, and
color (gradient accent).
self.grid_effect(
"sweep_in",
direction="left_to_right",
gradient_length=14,
randomness=2,
color="teal-400",
duration_ms=400,
fields=["body"],
)
self.grid_effect(
"sweep_out",
direction="up_to_down",
duration_ms=350,
fields=["body"],
)
from xnano.effects import SweepInEffect, SweepOutEffect
SweepInEffect(
direction="left_to_right",
gradient_length=14,
randomness=2,
color="teal-400",
duration_ms=400,
)
SweepOutEffect(direction="up_to_down", duration_ms=350)
Directional slides¶
Directional slides, same motion parameters as sweeps.
| Kind | Class | Role |
|---|---|---|
"slide_in" |
SlideInEffect |
Directional slide revealing content |
"slide_out" |
SlideOutEffect |
Directional slide hiding content |
self.grid_effect(
"slide_in",
direction="down_to_up",
duration_ms=350,
fields=["body"],
)
self.grid_effect(
"slide_out",
direction="right_to_left",
color="sky-300",
duration_ms=300,
fields=["body"],
)
from xnano.effects import SlideInEffect, SlideOutEffect
SlideInEffect(direction="down_to_up", duration_ms=350)
SlideOutEffect(
direction="right_to_left",
color="sky-300",
duration_ms=300,
)
Color paints¶
Paints set colors on the target area (as opposed to fading through them).
| Kind | Class | Role |
|---|---|---|
"paint" |
PaintEffect |
Paint foreground and background |
"paint_fg" |
PaintForegroundEffect |
Paint foreground only |
"paint_bg" |
PaintBackgroundEffect |
Paint background only |
self.grid_effect(
"paint",
color="white",
background="slate-800",
duration_ms=200,
fields=["body"],
)
self.grid_effect(
"paint_fg",
color="amber-300",
duration_ms=200,
fields=["title"],
)
self.grid_effect(
"paint_bg",
background="slate-900",
duration_ms=200,
fields=["body"],
)
from xnano.effects import (
PaintBackgroundEffect,
PaintEffect,
PaintForegroundEffect,
)
PaintEffect(color="white", background="slate-800", duration_ms=200)
PaintForegroundEffect(color="amber-300", duration_ms=200)
PaintBackgroundEffect(background="slate-900", duration_ms=200)
Delays and pauses¶
| Kind | Class | Role |
|---|---|---|
"sleep" |
SleepEffect |
Empty delay (useful inside sequences) |
"delay" |
DelayEffect |
Wait, then run a child |
from xnano.effects import DelayEffect, Effect, FadeEffect, SleepEffect
# Standalone pause for sequencing
SleepEffect(duration_ms=150)
# Wait, then fade
DelayEffect(
duration_ms=200,
child=FadeEffect(color="violet-400", duration_ms=250),
)
# Same idea via kind strings
self.grid_effect(
"delay",
duration_ms=200,
child=Effect("fade", color="violet-400", duration_ms=250),
fields=["body"],
)
Combining effects¶
| Kind | Class | Role |
|---|---|---|
"sequence" |
SequenceEffect |
Run children one after another (effects=) |
"parallel" |
ParallelEffect |
Run children together (effects=) |
"repeat" |
RepeatEffect |
Repeat a child times times (None = forever) |
from xnano.effects import Effect, ParallelEffect, RepeatEffect, SequenceEffect
self.grid_effect(
"sequence",
effects=(
Effect("sweep_in", direction="up_to_down", duration_ms=350),
Effect("fade", color="sky-300", duration_ms=200),
),
fields=["body"],
)
self.grid_effect(
"parallel",
effects=(
Effect("fade", color="emerald-400", duration_ms=300),
Effect("paint_bg", background="slate-900", duration_ms=300),
),
fields=["body"],
)
self.grid_effect(
"repeat",
child=Effect("fade", color="violet-400", duration_ms=200),
times=3,
fields=["title"],
)
Typed composition:
intro = SequenceEffect(
effects=(
Effect("sweep_in", direction="left_to_right", duration_ms=400),
Effect("fade", color="emerald-400", duration_ms=250),
),
)
pulse = RepeatEffect(
child=Effect("fade", color="teal-400", duration_ms=180),
times=2,
)
together = ParallelEffect(
effects=(
Effect("fade_to", color="white", background="black", duration_ms=300),
Effect("coalesce", duration_ms=300),
),
)
self.grid_effect(intro, fields=["title", "body"])
Duration, easing, and filters¶
Every effect instance carries these base attributes (from AbstractEffect):
| Attribute | Role |
|---|---|
duration_ms |
Length in milliseconds (default 300) |
interpolation |
Easing curve ("linear", "smooth_step", "sine_in_out", "bounce_out", …) |
cell_filter |
Which cells participate ("all", "text", "non_empty", "background", "background_only") |
key |
Optional id so replaying replaces an in-flight effect on that field |
Directional kinds (sweep_*, slide_*) also take:
| Attribute | Role |
|---|---|
direction |
"left_to_right", "right_to_left", "up_to_down", "down_to_up" |
gradient_length |
Motion gradient length in cells (default 14) |
randomness |
Randomness along the gradient (default 2) |
color |
Accent color of the motion gradient |
Notes:
duration_ms,interpolation,color,background,direction,gradient_length,randomness,effects,child,times, andkeycan be passed as kwargs on kind-string calls togrid_effect/Effect(...).cell_filteris set on a typed instance (for exampleFadeEffect(color="cyan", cell_filter="text")), not as a kind-string kwarg.- When
effectis already an instance, setkeyon that instance; thekey=kwarg ongrid_effectis only used for kind strings.
When effects run¶
Effects need an active runtime and a painted field geometry, so call
grid_effect from hooks, after the grid is attached to a live
Terminal / Web session, or from other runtime-bound code — not at module
import time.
from xnano import BaseGrid, Field, Terminal, on_keyboard, on_tick
class Banner(BaseGrid, direction="vertical"):
label: str = Field(default="ready", height=1, border="rounded")
_intro_done: bool = Field(default=False, state=True)
@on_tick
def play_intro_once(self) -> None:
if self._intro_done:
return
self._intro_done = True
self.grid_effect(
"coalesce",
duration_ms=450,
fields=["label"],
)
@on_keyboard("r")
def replay(self) -> None:
self.grid_effect(
"sweep_in",
direction="left_to_right",
duration_ms=350,
fields=["label"],
key="replay", # (1)!
)
Terminal().run(Banner())
keyde-duplicates: replaying with the same key on the same field replaces the in-flight effect instead of stacking another.
Runtime.play_effect is the
lower-level API. Grids wrap it as grid_effect so field names resolve
against that grid's layout after paint.
Effects are terminal-native in implementation detail (tachyonfx-backed under xnano-core). On hosts without a live effect engine path, starting an effect may no-op; the description API stays the same.
Kind strings and typed builders¶
Two equivalent construction paths:
from xnano.effects import Effect, FadeEffect, SweepInEffect
# Factory: kind string + kwargs
fade = Effect("fade", color="emerald-400", duration_ms=250)
sweep = Effect(
"sweep_in",
direction="left_to_right",
duration_ms=400,
)
# Typed classes: same descriptions, explicit types
fade_too = FadeEffect(color="emerald-400", duration_ms=250)
sweep_too = SweepInEffect(direction="left_to_right", duration_ms=400)
Pass either form into grid_effect:
self.grid_effect(fade, fields=["body"])
self.grid_effect("fade", color="emerald-400", fields=["body"])
Prefer kind strings at the call site for one-off triggers. Prefer typed classes when you store, compose, or type-check effect values.
Next¶
- Grids — fields that effects target
- Events & Hooks — where you usually trigger them
- Device & Cursor — host chrome, not field animation