Animation
Kraken TUI’s Animation Module provides a powerful system for time-based property transitions. Animations run natively in Rust for smooth, CPU-efficient motion.
Animation Architecture
The Animation Module manages an active animation registry, advancing timed property transitions each render cycle using elapsed time. It applies interpolated values to target widgets and marks them dirty.
How Animations Work
Start Animation
Call widget.animate(options) to register a property transition. Returns an animation handle.
Frame Advancement
Each render() call queries elapsed time and interpolates animation values using the specified easing function.
Property Application
Interpolated values are written to target widgets, which are marked dirty for rendering.
Completion
When duration elapses, animation stops and is removed from the registry (unless looping).
Animations are host-driven . The Native Core queries system time during render() to advance animations. No background threads or timers.
Animatable Properties
The following properties can be animated:
opacity
fgColor
bgColor
borderColor
positionX
positionY
widget . animate ({
property: "opacity" ,
target: 0.5 , // Target value (0.0 - 1.0)
duration: 300 , // Milliseconds
easing: "easeOut"
});
Type: number (0.0 = transparent, 1.0 = opaque)Use cases: Fade in/out, modal overlays, focus indicatorswidget . animate ({
property: "fgColor" ,
target: "#FF0000" , // Hex, named, or 256-color
duration: 500 ,
easing: "linear"
});
Type: Color (hex string, named, or number)Use cases: Status transitions, hover effects, attention grabbingwidget . animate ({
property: "bgColor" ,
target: "#1E1E1E" ,
duration: 400 ,
easing: "easeInOut"
});
Type: Color (hex string, named, or number)Use cases: Selection highlighting, theme transitionswidget . animate ({
property: "borderColor" ,
target: "cyan" ,
duration: 200 ,
easing: "easeOut"
});
Type: Color (hex string, named, or number)Use cases: Focus indicators, active stateswidget . animate ({
property: "positionX" ,
target: 10.0 , // Cells from layout position
duration: 600 ,
easing: "elastic"
});
Type: number (offset in cells)Use cases: Slide-in panels, drag animationswidget . animate ({
property: "positionY" ,
target: - 5.0 , // Cells from layout position
duration: 400 ,
easing: "bounce"
});
Type: number (offset in cells)Use cases: Drop-down menus, notifications
positionX and positionY are render offsets from the widget’s layout position. They don’t affect layout computation — only final render position.
Easing Functions
Easing functions control the interpolation curve:
linear
easeIn
easeOut
easeInOut
cubicIn
cubicOut
elastic
bounce
widget . animate ({
property: "opacity" ,
target: 1.0 ,
duration: 300 ,
easing: "linear" // Constant speed
});
Curve: Constant velocityUse cases: Progress bars, timers, mechanical motionwidget . animate ({
property: "opacity" ,
target: 0.0 ,
duration: 300 ,
easing: "easeIn" // Slow start, fast end
});
Curve: Accelerating (quadratic)Use cases: Fade out, exit animationswidget . animate ({
property: "opacity" ,
target: 1.0 ,
duration: 300 ,
easing: "easeOut" // Fast start, slow end
});
Curve: Decelerating (quadratic)Use cases: Fade in, entrance animations, natural stopswidget . animate ({
property: "positionX" ,
target: 20.0 ,
duration: 500 ,
easing: "easeInOut" // Slow start & end
});
Curve: Accelerate then decelerate (quadratic)Use cases: Smooth transitions, natural motionwidget . animate ({
property: "opacity" ,
target: 0.0 ,
duration: 400 ,
easing: "cubicIn" // Very slow start
});
Curve: Strong acceleration (cubic)Use cases: Dramatic entranceswidget . animate ({
property: "opacity" ,
target: 1.0 ,
duration: 400 ,
easing: "cubicOut" // Very slow end
});
Curve: Strong deceleration (cubic)Use cases: Gentle stops, soft landingswidget . animate ({
property: "positionY" ,
target: 0.0 ,
duration: 600 ,
easing: "elastic" // Overshoot & spring back
});
Curve: Spring-like oscillationUse cases: Playful motion, attention grabbingwidget . animate ({
property: "positionY" ,
target: 10.0 ,
duration: 800 ,
easing: "bounce" // Bouncing ball effect
});
Curve: Multiple bounce impactsUse cases: Falling objects, playful UI
Animation Lifecycle
Starting an Animation
const animHandle = widget . animate ({
property: "opacity" ,
target: 0.5 ,
duration: 300 ,
easing: "easeOut"
});
// animHandle is a u32 for cancellation
Canceling an Animation
widget . cancelAnimation ( animHandle );
// Property retains its current value at cancellation time
Looping Animations
const animHandle = widget . animate ({
property: "opacity" ,
target: 0.3 ,
duration: 1000 ,
easing: "easeInOut" ,
loop: true // Oscillates indefinitely
});
// Cancel when done:
widget . cancelAnimation ( animHandle );
loop: true creates a ping-pong animation: property animates to target, then back to original value, repeating until canceled.
Built-in Animation Helpers
Kraken TUI provides convenience methods for common animation patterns:
Spinner
const text = new Text ({ content: "⠋" });
const animHandle = text . spinner ({ interval: 80 });
// Cycles through braille spinner frames automatically
// Cancel when done:
text . cancelAnimation ( animHandle );
Frames: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏
Progress
const bar = new Box ();
bar . setOpacity ( 0.0 );
const animHandle = bar . progress ({
duration: 2000 ,
easing: "linear"
});
// Animates opacity from 0.0 → 1.0
Pulse
const indicator = new Box ();
const animHandle = indicator . pulse ({
duration: 1000 ,
easing: "easeInOut"
});
// Opacity oscillates indefinitely
// Cancel when done:
indicator . cancelAnimation ( animHandle );
Animation Choreography
Chain multiple animations for complex sequences:
Sequential Animations
async function fadeInThenSlide ( widget : Widget ) {
// Fade in
widget . setOpacity ( 0.0 );
const fadeHandle = widget . animate ({
property: "opacity" ,
target: 1.0 ,
duration: 300 ,
easing: "easeOut"
});
await sleep ( 300 ); // Wait for fade completion
// Then slide
const slideHandle = widget . animate ({
property: "positionX" ,
target: 20.0 ,
duration: 400 ,
easing: "easeInOut"
});
}
Parallel Animations
function colorShift ( widget : Widget ) {
// Animate foreground and background simultaneously
widget . animate ({
property: "fgColor" ,
target: "#FF0000" ,
duration: 500 ,
easing: "linear"
});
widget . animate ({
property: "bgColor" ,
target: "#1E1E1E" ,
duration: 500 ,
easing: "linear"
});
}
Staggered Animations
function staggerFadeIn ( widgets : Widget [], delayMs : number ) {
widgets . forEach (( widget , i ) => {
setTimeout (() => {
widget . setOpacity ( 0.0 );
widget . animate ({
property: "opacity" ,
target: 1.0 ,
duration: 300 ,
easing: "easeOut"
});
}, i * delayMs );
});
}
// Fade in list items one by one
staggerFadeIn ( listItems , 100 );
Animation Examples
Fade-In Modal
const modal = new Box ();
modal . setBorderStyle ( "rounded" );
modal . setOpacity ( 0.0 );
function showModal () {
modal . setVisible ( true );
modal . animate ({
property: "opacity" ,
target: 1.0 ,
duration: 200 ,
easing: "easeOut"
});
}
function hideModal () {
const handle = modal . animate ({
property: "opacity" ,
target: 0.0 ,
duration: 200 ,
easing: "easeIn"
});
setTimeout (() => {
modal . setVisible ( false );
}, 200 );
}
Slide-In Notification
const notification = new Box ();
notification . setWidth ( 30 );
notification . setHeight ( 3 );
notification . setBorderStyle ( "single" );
function showNotification () {
notification . setVisible ( true );
notification . animate ({
property: "positionY" ,
target: 0.0 , // Slide to layout position
duration: 400 ,
easing: "easeOut"
});
// Auto-hide after 3 seconds
setTimeout (() => {
notification . animate ({
property: "positionY" ,
target: - 10.0 , // Slide up off screen
duration: 300 ,
easing: "easeIn"
});
}, 3000 );
}
// Start off-screen
notification . setRenderOffset ( 0 , - 10 );
showNotification ();
Loading Indicator
const loader = new Box ({ direction: "row" , gap: 1 });
const dots = [ "●" , "●" , "●" ]. map ( char => new Text ({ content: char }));
dots . forEach (( dot , i ) => {
dot . setOpacity ( 0.3 );
loader . append ( dot );
// Stagger pulse animations
setTimeout (() => {
dot . pulse ({
duration: 1000 ,
easing: "easeInOut"
});
}, i * 200 );
});
// ● ● ● (pulsing with stagger)
Color Transition on Focus
const input = new Input ();
input . setBorderStyle ( "single" );
input . setBorderColor ( "#444" );
app . on ( "focus" , ( event ) => {
if ( event . toHandle === input . handle ) {
input . animate ({
property: "borderColor" ,
target: "#00FF00" , // Green when focused
duration: 200 ,
easing: "easeOut"
});
}
});
app . on ( "blur" , ( event ) => {
if ( event . fromHandle === input . handle ) {
input . animate ({
property: "borderColor" ,
target: "#444444" , // Gray when unfocused
duration: 200 ,
easing: "easeIn"
});
}
});
Animation Advancement Cost
Each active animation requires:
Time delta computation (system clock query)
Easing function evaluation (native Rust)
Interpolated value application
Dirty flag propagation
Cost: ~1–5μs per animation per frame (negligible for < 100 active animations)
Animations advance during render(). If layout + diff + animation advancement exceeds 16ms (60fps), frames may drop. Mitigation: Animation interpolation skips frames gracefully under pressure. Time-based advancement (not frame-based) ensures animations complete in correct wall-clock time.
Animated widgets are marked dirty every frame. Ensure animated widgets are small (few descendants) to minimize render cost.
Color animations interpolate in RGB space. Truecolor (#RRGGBB) and 256-color modes are supported. Named colors are converted to ANSI indices (no interpolation between named colors).
Animation Best Practices
Use Appropriate Easing
Entrances: easeOut for natural deceleration
Exits: easeIn for smooth acceleration
Transitions: easeInOut for balanced motion
Playful: elastic or bounce for attention
Keep Durations Reasonable
Fast feedback: 100–200ms (button presses, toggles)
Standard transitions: 200–400ms (fades, slides)
Dramatic effects: 400–800ms (modals, page transitions)
Avoid: > 1000ms (feels sluggish)
Cancel Animations on Unmount
const handles : number [] = [];
handles . push ( widget . animate ({ ... }));
handles . push ( widget . animate ({ ... }));
// Cleanup:
handles . forEach ( h => widget . cancelAnimation ( h ));
widget . destroySubtree ();
Minimize Simultaneous Animations
Too many concurrent animations can overwhelm the visual system. Stagger or sequence animations for clarity.
Next Steps
Events Trigger animations from user input
Theming Animate theme transitions