# Como criar um pulso contínuo (loop)

> Animar um valor em ciclo infinito com LoopAnimation, útil para indicadores de carregamento e efeitos de brilho.

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

# Como criar um pulso contínuo (loop)

**Propósito.** Fazer algo pulsar/piscar indefinidamente (indicador de "carregando", brilho, respiração de um botão). `LoopAnimation` cicla um valor de 0 → 1 repetidamente, com ou sem easing, **sem** `onComplete` (é infinita — você a para com `stop()`).

## APIs usadas
- `new LoopAnimation(float duration [, float fps, boolean easeInOut])` — cada ciclo dura `duration` segundos e reinicia sozinho. `fps` default 60; `easeInOut` default `false` (linear). Com `easeInOut=true`, aplica ease-in-out cubic por ciclo.
- `LoopAnimation.onUpdate(Runnable)` — callback a cada frame; leia `getValue()` (0–1, progresso do ciclo atual).
- `LoopAnimation.getValue()` — progresso atual do ciclo.
- `LoopAnimation.start()` / `stop()` — inicia / interrompe o loop. **Não há `onComplete` nem `cancel()`** — é infinita por natureza.
- `Component.alpha(float)` — aplica opacidade (para efeito de "respiração").
- `Component.repaint()` — estático; obrigatório no `onUpdate`.

## Exemplo completo (copiar, colar e rodar)
```java
package com.exemplo;

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

/** Tela principal executável: pulsa um "●" em loop; pare o loop ao fechar a tela. */
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);

      // ciclo de 1 s, 60 fps, com easing (pulso suave)
      pulse = new LoopAnimation(1f, 60f, true);
      pulse.onUpdate(() ->
         {
            // valor vai 0 → 1 e reinicia; mapeia para opacidade 0.3 → 1.0
            float v = 0.3f + 0.7f * pulse.getValue();
            dot.alpha(v);
            Component.repaint();
         })
         .start();

      // botão que encerra o pulso (ex.: quando o carregamento terminar)
      add(new Button("Parar").onAction(e -> pulse.stop()));
   }

   @Override
   public void onRemove()
   {
      // Pare ao sair da tela para não deixar um loop órfão animando fora da tela.
      if (pulse != null) pulse.stop();
      super.onRemove();
   }
}
```

> O trecho abaixo mostra **só o miolo**. Para rodar, coloque-o no esqueleto `MainWindow` acima.

## Trecho mínimo
```java
// verificado na fonte: LoopAnimation(duration, fps, easeInOut); sem onComplete; pare com stop()
Label dot = new Label("●");
add(dot);

// ciclo de 1 s, 60 fps, com easing (pulso suave)
LoopAnimation pulse = new LoopAnimation(1f, 60f, true);
pulse.onUpdate(() ->
   {
      // valor vai 0 → 1 e reinicia; mapeia para opacidade 0.3 → 1.0
      float v = 0.3f + 0.7f * pulse.getValue();
      dot.alpha(v);
      Component.repaint();
   })
   .start();

// mais tarde, para encerrar o pulso (ex.: quando o carregamento terminar):
pulse.stop();
```

Cuidados importantes:

- **Guarde a referência** (`pulse`) para poder chamar `stop()`. Uma `LoopAnimation` sem `stop()` roda para sempre e consome CPU/GPU continuamente (cada frame chama `repaint()`).
- **Pare ao sair da tela**: chame `pulse.stop()` quando a `Window` for fechada (`onRemove`/`hide`) para não deixar um loop órfão animando fora da tela.
- `getValue()` volta a 0 no início de cada ciclo. Para um pulso "vai-e-volta" (0→1→0), mapeie com uma função triangular ou seno sobre `getValue()`.

## Aprofunde-se
- [Animações de valor](/docs/ui/animations/value-animations) · [Press e feedback](/docs/ui/animations/press-and-feedback)
- Receita irmã: [Fade de um componente](/docs/cookbook/anim/fade) · [Mover um componente](/docs/cookbook/anim/mover-componente)
