Skip to main content

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

1

Start Animation

Call widget.animate(options) to register a property transition. Returns an animation handle.
2

Frame Advancement

Each render() call queries elapsed time and interpolates animation values using the specified easing function.
3

Property Application

Interpolated values are written to target widgets, which are marked dirty for rendering.
4

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:
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 indicators
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:
widget.animate({
  property: "opacity",
  target: 1.0,
  duration: 300,
  easing: "linear"  // Constant speed
});
Curve: Constant velocityUse cases: Progress bars, timers, mechanical motion

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"
    });
  }
});

Performance Considerations

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

1

Use Appropriate Easing

  • Entrances: easeOut for natural deceleration
  • Exits: easeIn for smooth acceleration
  • Transitions: easeInOut for balanced motion
  • Playful: elastic or bounce for attention
2

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)
3

Cancel Animations on Unmount

const handles: number[] = [];

handles.push(widget.animate({...}));
handles.push(widget.animate({...}));

// Cleanup:
handles.forEach(h => widget.cancelAnimation(h));
widget.destroySubtree();
4

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

Build docs developers (and LLMs) love