Skip to content

Animation

Castella provides a built-in animation system for smooth property animations with easing functions.

Overview

The animation system consists of:

  • AnimationScheduler - Background thread ticker (60 FPS for desktop, 10 FPS for TUI)
  • ValueTween - Animate numeric values over time
  • AnimatedState - State wrapper that automatically animates value changes
  • EasingFunction - Built-in easing functions (linear, ease-out, bounce, etc.)

ValueTween

ValueTween animates a value from one number to another over a specified duration.

from castella.animation import ValueTween, AnimationScheduler, EasingFunction

# Animate from 0 to 100 over 1 second
AnimationScheduler.get().add(
    ValueTween(
        from_value=0,
        to_value=100,
        duration_ms=1000,
        easing=EasingFunction.EASE_OUT_CUBIC,
        on_update=lambda v: print(f"Value: {v}"),
        on_complete=lambda: print("Done!"),
    )
)

Parameters

Parameter Type Description
from_value float Starting value
to_value float Target value
duration_ms int Animation duration in milliseconds
easing EasingFunction Easing function (default: LINEAR)
on_update Callable[[float], None] Callback for each frame
on_complete Callable[[], None] Callback when animation completes

AnimatedState

AnimatedState is a state wrapper that automatically animates when values change.

from castella import AnimatedState, Component, Text, Button, Column

class Counter(Component):
    def __init__(self):
        super().__init__()
        # Values animate automatically when set
        self._value = AnimatedState(0, duration_ms=300)
        self._value.attach(self)

    def view(self):
        return Column(
            Text(f"Value: {self._value()}"),
            Button("+10").on_click(lambda _: self._value.set(self._value() + 10)),
        )

Parameters

Parameter Type Default Description
initial T required Initial value
duration_ms int 300 Animation duration
easing EasingFunction EASE_OUT_CUBIC Easing function

Methods

Method Description
set(value, animate=True) Set value with optional animation
set_immediate(value) Set value without animation
__call__() Get current (animated) value

Easing Functions

Available easing functions in EasingFunction:

Function Description
LINEAR Constant speed
EASE_IN_QUAD Accelerate from zero
EASE_OUT_QUAD Decelerate to zero
EASE_IN_OUT_QUAD Accelerate then decelerate
EASE_IN_CUBIC Cubic acceleration
EASE_OUT_CUBIC Cubic deceleration
EASE_IN_OUT_CUBIC Cubic acceleration/deceleration
BOUNCE Bounce effect

Widget Animation Methods

Widgets have built-in animation methods for position and size:

# Animate to position/size
widget.animate_to(x=200, y=100, duration_ms=400)

# Slide in from off-screen
widget.slide_in("left", distance=200, duration_ms=300)

# Slide out to off-screen
widget.slide_out("right", distance=200, duration_ms=300)

animate_to()

widget.animate_to(
    x=200,           # Target x position
    y=100,           # Target y position
    width=300,       # Target width
    height=150,      # Target height
    duration_ms=400, # Duration
    easing=EasingFunction.EASE_OUT_CUBIC,
    on_complete=lambda: print("Done"),
)

slide_in() / slide_out()

# Directions: "left", "right", "top", "bottom"
widget.slide_in("left", distance=200)
widget.slide_out("right", distance=200, on_complete=lambda: print("Hidden"))

Complete Example

from castella import (
    App, Component, Column, Button, Text,
    ProgressBar, ProgressBarState, AnimatedState,
)
from castella.animation import ValueTween, AnimationScheduler, EasingFunction
from castella.frame import Frame


class AnimationDemo(Component):
    def __init__(self):
        super().__init__()
        # ProgressBar for ValueTween demo
        self._progress = ProgressBarState(0, min_val=0, max_val=100)
        self._progress.attach(self)

        # AnimatedState for automatic animation
        self._counter = AnimatedState(0, duration_ms=300)
        self._counter.attach(self)

    def view(self):
        return Column(
            Text("ValueTween Demo"),
            ProgressBar(self._progress).fixed_height(24),
            Button("Animate").on_click(self._animate),

            Text(f"Counter: {self._counter()}"),
            Button("+10").on_click(lambda _: self._counter.set(self._counter() + 10)),
        )

    def _animate(self, _):
        self._progress.set(0)
        AnimationScheduler.get().add(
            ValueTween(
                from_value=0,
                to_value=100,
                duration_ms=1500,
                easing=EasingFunction.BOUNCE,
                on_update=lambda v: self._progress.set(v),
            )
        )


App(Frame("Animation Demo", 400, 300), AnimationDemo()).run()

TUI Support

In terminal mode, animations run at a lower frame rate (10 FPS) for better performance. The animation system automatically detects terminal mode via the CASTELLA_IS_TERMINAL_MODE environment variable.