# TabBar

> Barra de pestañas con indicador animado y contenido desplazable horizontalmente con snap por página.

Source: https://crossfly.dev/es/docs/ui/widgets/tabbar — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# TabBar

`TabBar` es una barra de pestañas: una tira de botones (tab strip) + un panel de contenido **desplazable horizontalmente** con **snap por página** (vía `PageScrollEngine`). Tocar una pestaña desplaza el contenido hasta la página correspondiente; **arrastrar** el contenido cambia la pestaña activa. Un **indicador animado** acompaña a la pestaña activa.

La estética (indicador, pestañas arriba o abajo, colores) viene del `Skin` activo (`TabBarStyle`). Cada pestaña se describe con un `TabBar.Tab` (texto, ícono o ambos).

> 🟡 · **Paquete:** `dev.crossfly.ui` · **Extiende:** `Component` · **Estilo:** `TabBarStyle` (vía `getSkin().tabBar()`)

## Cuándo usar
- Navegación entre 2–5 secciones de una pantalla (Inicio / Buscar / Perfil).
- Contenido paginado con gesto de swipe entre páginas + pestañas sincronizadas.

## Cuándo NO usar
- Muchas secciones que no caben (la tira se desplaza, pero queda menos legible) → considera otra navegación.

## Apariencia
Tira de pestañas de igual ancho (`1/n`); si no caben, la tira pasa a ser desplazable horizontalmente y las pestañas pasan a ancho automático. El indicador se desliza bajo (o sobre, si `tabsAtBottom`) la pestaña activa. El panel de contenido muestra una página a la vez con snap.

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

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

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // Una view de contenido por pestaña (el conteo TIENE que coincidir con el número de pestañas).
      Label resumo    = new Label("Conteúdo do Resumo");
      Label detalhes  = new Label("Conteúdo dos Detalhes");
      Label historico = new Label("Conteúdo do Histórico");

      TabBar tabs = new TabBar(
         new TabBar.Tab("Resumo"),
         new TabBar.Tab("Detalhes"),
         new TabBar.Tab("Histórico"));
      tabs.setContent(resumo, detalhes, historico);
      tabs.onTabChanged(i -> System.out.println("aba " + i));

      add(LAY_FILL, tabs);   // la TabBar absorbe la altura restante
   }
}
```

> Los fragmentos de abajo muestran **solo el núcleo** (lo que va dentro de `onShow()`). Para ejecutarlos, colócalos en el esqueleto `MainWindow` de arriba.

## Uso básico
```java
TabBar tabs = new TabBar(
   new TabBar.Tab("Home",   new MaterialIcon("")),
   new TabBar.Tab("Buscar", new MaterialIcon("")),
   new TabBar.Tab("Perfil", new MaterialIcon("")));
tabs.setContent(homeView, searchView, profileView);
tabs.onTabChanged(i -> System.out.println("aba " + i));
add(LAY_FILL, tabs);
```

## Constructores
| Constructor | Descripción |
|---|---|
| `TabBar(Tab... tabs)` | Crea la barra con las pestañas (`activeIndex = 0`) |

### `TabBar.Tab` (descriptor de pestaña)
| Constructor | Resultado |
|---|---|
| `Tab(String text)` | Solo texto (`TextPos.NONE`) |
| `Tab(Icon icon)` | Solo ícono (`TextPos.NONE`) |
| `Tab(String text, Icon icon)` | Texto + ícono (`TextPos.TEXT_AT_BOTTOM`) |
| `Tab(String text, Icon icon, TextPos textPos)` | Texto + ícono con posición personalizada |

## Propiedades y personalización
| Propiedad / método | Tipo | Qué hace |
|---|---|---|
| `setContent(Component...)` | `TabBar` | Contenido por pestaña (la cantidad **debe** coincidir con las pestañas, si no `IllegalArgumentException`) |
| `setActiveTab(int)` | `TabBar` | Activa una pestaña programáticamente (con animación/scroll); no-op si es inválido o ya está activo |
| `getActiveIndex()` | `int` | Índice de la pestaña activa |
| `getTabs()` | `Tab[]` | Descriptores de las pestañas |
| `getStrip()` / `getTabButtons()` / `getContentPanel()` | — | Acceso a los internos |
| `getIndicatorX()` / `getIndicatorW()` | `float` | Posición/ancho actuales del indicador (para pintura en el `Design`) |

## Callbacks y eventos
| Callback | Cuándo se dispara |
|---|---|
| `onTabChanged(TabChangedListener)` | Al cambiar de pestaña, por toque, swipe o `setActiveTab(...)` |

`TabChangedListener` es funcional: `void onTabChanged(int index)`.

## Estados
- **Activa** — solo una pestaña a la vez; el indicador anima hasta ella (duración `TabBarStyle.switchDuration`).
- **Swipe del usuario** — el indicador interpola continuamente entre pestañas vecinas; al cruzar hacia otra página, `activeIndex` cambia y `onTabChanged` se dispara.
- **Programático** — `setActiveTab`/toque bloquean la sincronización hasta que la animación de scroll llega al objetivo.

### Pestañas solo de texto
```java
TabBar tabs = new TabBar(
   new TabBar.Tab("Resumo"),
   new TabBar.Tab("Detalhes"),
   new TabBar.Tab("Histórico"));
tabs.setContent(resumoView, detalhesView, historicoView);
add(LAY_FILL, tabs);
```

### Cambio programático de pestaña
```java
TabBar tabs = new TabBar(new TabBar.Tab("A"), new TabBar.Tab("B"), new TabBar.Tab("C"));
tabs.setContent(viewA, viewB, viewC);
add(LAY_FILL, tabs);
// ... más tarde:
tabs.setActiveTab(2);  // anima hasta la pestaña C
```

## Buenas prácticas
- La cantidad de `setContent(...)` **tiene** que coincidir con el número de pestañas.
- Para pestañas abajo (estilo iOS), usa un `Skin` cuyo `TabBarStyle.tabsAtBottom = true` — el layout y el indicador se ajustan solos.
- No fuerces ancho fijo en las pestañas: si no caben, la tira se desplaza por su cuenta.

## Errores comunes
- **`setContent` con cantidad incorrecta** → `IllegalArgumentException`. Pasa exactamente una view por pestaña.
- **Esperar que `setActiveTab` dispare `onTabChanged` para el índice ya activo** — es no-op cuando `index == activeIndex`.

## Véase también
- Guía del área: [`ui/`](/docs/ui) · Recetas: [`cookbook/`](/docs/cookbook)
