# How to create a continuous pulse (loop)

> Animate a value in an infinite cycle with LoopAnimation, useful for loading indicators and glow effects.

Source: https://crossfly.dev/docs/cookbook/anim/pulso-loop — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# How to create a continuous pulse (loop)

**Purpose.** Make something pulse/blink indefinitely (a "loading" indicator, glow, the breathing of a button). `LoopAnimation` cycles a value from 0 → 1 repeatedly, with or without easing, **without** `onComplete` (it is infinite — you stop it with `stop()`).

## APIs used
- `new LoopAnimation(float duration [, float fps, boolean easeInOut])` — each cycle lasts `duration` seconds and restarts on its own. `fps` default 60; `easeInOut` default `false` (linear). With `easeInOut=true`, it applies cubic ease-in-out per cycle.
- `LoopAnimation.onUpdate(Runnable)` — callback on each frame; read `getValue()` (0–1, progress of the current cycle).
- `LoopAnimation.getValue()` — current progress of the cycle.
- `LoopAnimation.start()` / `stop()` — starts / stops the loop. **There is no `onComplete` or `cancel()`** — it is infinite by nature.
- `Component.alpha(float)` — applies opacity (for the "breathing" effect).
- `Component.repaint()` — static; required in `onUpdate`.

## Full example (copy, paste and run)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.animation.*;

/** Runnable main screen: pulses a "●" in a loop; stop the loop when the screen closes. */
public class MainWindow extends Window
{
   private LoopAnimation pulse;

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      Label dot = new Label("●");
      add(dot);

      // 1 s cycle, 60 fps, with easing (smooth pulse)
      pulse = new LoopAnimation(1f, 60f, true);
      pulse.onUpdate(() ->
         {
            // value goes 0 → 1 and restarts; maps to opacity 0.3 → 1.0
            float v = 0.3f + 0.7f * pulse.getValue();
            dot.alpha(v);
            Component.repaint();
         })
         .start();

      // button that ends the pulse (e.g. when loading finishes)
      add(new Button("Parar").onAction(e -> pulse.stop()));
   }

   @Override
   public void onRemove()
   {
      // Stop it when leaving the screen so you don't leave an orphan loop animating off-screen.
      if (pulse != null) pulse.stop();
      super.onRemove();
   }
}
```

> The snippet below shows **only the core**. To run it, place it inside the `MainWindow` skeleton above.

## Minimal snippet
```java
// verified in the source: LoopAnimation(duration, fps, easeInOut); no onComplete; stop with stop()
Label dot = new Label("●");
add(dot);

// 1 s cycle, 60 fps, with easing (smooth pulse)
LoopAnimation pulse = new LoopAnimation(1f, 60f, true);
pulse.onUpdate(() ->
   {
      // value goes 0 → 1 and restarts; maps to opacity 0.3 → 1.0
      float v = 0.3f + 0.7f * pulse.getValue();
      dot.alpha(v);
      Component.repaint();
   })
   .start();

// later, to end the pulse (e.g. when loading finishes):
pulse.stop();
```

Important cautions:

- **Keep the reference** (`pulse`) so you can call `stop()`. A `LoopAnimation` without `stop()` runs forever and continuously consumes CPU/GPU (each frame calls `repaint()`).
- **Stop when leaving the screen**: call `pulse.stop()` when the `Window` is closed (`onRemove`/`hide`) so you don't leave an orphan loop animating off-screen.
- `getValue()` returns to 0 at the start of each cycle. For a "back-and-forth" pulse (0→1→0), map it with a triangular or sine function over `getValue()`.

## Dig deeper
- [Value animations](/docs/ui/animations/value-animations) · [Press and feedback](/docs/ui/animations/press-and-feedback)
- Sibling recipe: [Fade a component](/docs/cookbook/anim/fade) · [Move a component](/docs/cookbook/anim/mover-componente)
