# TabBar

> Tab bar with an animated indicator and horizontally scrollable content with per-page snap.

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

# TabBar

`TabBar` is a tab bar: a strip of buttons (tab strip) + a **horizontally scrollable** content panel with **per-page snap** (via `PageScrollEngine`). Tapping a tab scrolls the content to the corresponding page; **dragging** the content switches the active tab. An **animated indicator** follows the active tab.

The aesthetics (indicator, tabs at the top or bottom, colors) come from the active `Skin` (`TabBarStyle`). Each tab is described by a `TabBar.Tab` (text, icon or both).

> 🟡 · **Package:** `dev.crossfly.ui` · **Extends:** `Component` · **Style:** `TabBarStyle` (via `getSkin().tabBar()`)

## When to use
- Navigation between 2–5 sections of a screen (Home / Search / Profile).
- Paginated content with a swipe gesture between pages + synchronized tabs.

## When NOT to use
- Many sections that don't fit (the strip scrolls, but becomes less legible) → consider a different navigation.

## Appearance
Equal-width tab strip (`1/n`); if they don't fit, the strip becomes horizontally scrollable and the tabs switch to automatic width. The indicator slides under (or over, if `tabsAtBottom`) the active tab. The content panel shows one page at a time with snap.

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

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

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // One content view per tab (the count MUST match the number of tabs).
      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);   // the TabBar absorbs the remaining height
   }
}
```

> The snippets below show **only the core** (what goes inside `onShow()`). To run them, place them in the `MainWindow` skeleton above.

## Basic usage
```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);
```

## Constructors
| Constructor | Description |
|---|---|
| `TabBar(Tab... tabs)` | Creates the bar with the tabs (`activeIndex = 0`) |

### `TabBar.Tab` (tab descriptor)
| Constructor | Result |
|---|---|
| `Tab(String text)` | Text only (`TextPos.NONE`) |
| `Tab(Icon icon)` | Icon only (`TextPos.NONE`) |
| `Tab(String text, Icon icon)` | Text + icon (`TextPos.TEXT_AT_BOTTOM`) |
| `Tab(String text, Icon icon, TextPos textPos)` | Text + icon with a custom position |

## Properties and customization
| Property / method | Type | What it does |
|---|---|---|
| `setContent(Component...)` | `TabBar` | Content per tab (the count **must** match the tabs, otherwise `IllegalArgumentException`) |
| `setActiveTab(int)` | `TabBar` | Activates a tab programmatically (with animation/scroll); no-op if invalid or already active |
| `getActiveIndex()` | `int` | Index of the active tab |
| `getTabs()` | `Tab[]` | The tab descriptors |
| `getStrip()` / `getTabButtons()` / `getContentPanel()` | — | Access to internals |
| `getIndicatorX()` / `getIndicatorW()` | `float` | Current position/width of the indicator (for painting in `Design`) |

## Callbacks and events
| Callback | When it fires |
|---|---|
| `onTabChanged(TabChangedListener)` | On tab change, by tap, swipe or `setActiveTab(...)` |

`TabChangedListener` is functional: `void onTabChanged(int index)`.

## States
- **Active** — only one tab at a time; the indicator animates to it (duration `TabBarStyle.switchDuration`).
- **User swipe** — the indicator interpolates continuously between neighboring tabs; when crossing to another page, `activeIndex` switches and `onTabChanged` fires.
- **Programmatic** — `setActiveTab`/tap lock the synchronization until the scroll animation reaches the target.

### Text-only tabs
```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);
```

### Programmatic tab switch
```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);
// ... later:
tabs.setActiveTab(2);  // animates to tab C
```

## Best practices
- The count of `setContent(...)` **must** match the number of tabs.
- For tabs at the bottom (iOS style), use a `Skin` whose `TabBarStyle.tabsAtBottom = true` — the layout and the indicator adjust themselves.
- Don't force a fixed width on the tabs: if they don't fit, the strip scrolls on its own.

## Common mistakes
- **`setContent` with the wrong count** → `IllegalArgumentException`. Pass exactly one view per tab.
- **Expecting `setActiveTab` to fire `onTabChanged` for the already-active index** — it is a no-op when `index == activeIndex`.

## See also
- Area guide: [`ui/`](/docs/ui) · Recipes: [`cookbook/`](/docs/cookbook)
