# Button

> 可点击的按钮，显示文本和/或图标，带按下状态和 onAction。

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

# Button

`Button` 是最基础的交互组件：显示文本和/或图标，被点击时触发一个动作，并带有按下的视觉状态（通过 `ButtonStyle`）。当同时存在文本**和**图标时，它们之间的相对位置由枚举 [`TextPos`](/docs/reference) 控制。它也是其他 widget 的基类（例如 `Dropdown extends Button`）。

动作通过 `onAction(Action)`（继承自 `Component`）注册；触发发生在标准点击流程中（`onTouchUp` 时的 `runActions()`），而不是 `onTouchDown`。

> 🟢 · **包：** `dev.crossfly.ui` · **继承：** `Component` · **样式：** `ButtonStyle`

## 何时使用
- 任何可点击的动作：保存、确认、导航、打开对话框。
- 纯图标按钮（工具栏）或纯文本按钮（链接/次要操作）。

## 何时不要使用
- 静态标签 → 使用 [`Label`](/docs/ui/widgets/label)。
- 持久的开/关切换 → 使用 `Switch`/`Option`（见 [`ui/`](/docs/ui)）。

## 外观
文本和/或图标在按钮内居中。两者都存在时，`[图标 + 间隙 + 文本]` 块在两个轴上都居中；`icongap` 定义间距。定位遵循 `TextPos`：

| `TextPos` | 布局 | `isHorizontal()` |
|---|---|---|
| `NONE` | 文本和图标重叠（居中） | false |
| `TEXT_AT_LEFT` | 文本在图标左侧 | true |
| `TEXT_AT_RIGHT` | 文本在图标右侧 | true |
| `TEXT_AT_TOP` | 文本在图标上方 | false |
| `TEXT_AT_BOTTOM` | 文本在图标下方 | false |

如果 `textPos == null`，默认为居中重叠（等同于 `NONE`）。

``

## 完整示例（复制、粘贴并运行）
```java
package com.exemplo;

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

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // 显示按钮被点击了多少次的 Label
      Label saida = new Label("Cliques: 0");
      add(LAY_AUTO, saida);

      // 回调中使用的可变计数器（长度为 1 的数组）
      final int[] cliques = {0};

      // 每次点击时递增计数器并更新 Label 的按钮
      Button botao = new Button("Clique aqui")
         .onAction(c -> saida.setText("Cliques: " + (++cliques[0])));
      add(LAY_AUTO, botao);
   }
}
```

> 下面的代码片段只展示**核心部分**（放进 `onShow()` 内的内容）。要运行它们，请把它们放入上面的 `MainWindow` 骨架中。

## 基本用法
```java
Button b = new Button("Salvar").onAction(c -> salvar());
container.add(LAY_AUTO, b);
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `Button(String text)` | 仅文本的按钮 |
| `Button(Icon icon)` | 仅图标的按钮 |
| `Button(String text, Icon icon, TextPos textPos)` | 文本 + 图标，带相对位置 |

## 属性与自定义
| 属性 / 方法 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `setText(String)` | `void` | — | 更换文本，使布局失效并调用 `repaint()` |
| `icon(Icon, TextPos)` | `Button`（流式） | — | 设置图标及其相对于文本的位置 |
| `text` / `icon` / `textPos` | 公有字段 | — | 当前内容（可读取/赋值） |
| 样式 `.pressable()` | — | — | 启用按下高亮 |
| 样式 `.textAlign(Position, Position)` | `Position` | 主题 | 内容对齐 |
| 样式 `.autoWidth(Relative)` / `.autoHeight(Relative)` | `Relative` | — | 固定首选尺寸 |
| `getAutoWidth()` / `getAutoHeight(int)` | `int` | — | 按内容计算的首选尺寸 |

## 回调与事件
| 回调 | 触发时机 |
|---|---|
| `onAction(Action)` | 完成点击（touch-up）时，通过 `runActions()`。返回 `this`，可以链式调用（`new Button(...).onAction(...).style(...)`）。 |

`Action` 是 `c -> { ... }`，其中 `c` 是来源的 `Component`；可以抛出 `Exception`。

## 状态
- **pressed：** 按下期间，`ButtonStyle` 应用按下样式（带淡入淡出）。需要在样式上启用 `pressable()`。
- **disabled：** `setEnabled(false)` 应用禁用外观并忽略点击。

### 图标在文本右侧的按钮
```java
Button proximo = new Button(
   "Próximo", new MaterialIcon(""), TextPos.TEXT_AT_RIGHT);
container.add(LAY_AUTO, proximo);
```

### 清空字段的“链接”样式按钮
```java
Button clear = new Button("Clear").style(
   Styles.theme().button(ButtonRole.LINK, State.NORMAL)
      .pressable()
      .textAlign(Position.CENTER, Position.CENTER));
clear.onAction(c -> {
   nome.clearText();
   email.clearText();
});
container.add(LAY_AUTO, clear);
```

## 最佳实践
- 当按钮同时有文本**和**图标时，始终传入 `textPos`（否则会重叠）。
- 使用主题角色（`ButtonRole.PRIMARY`/`SECONDARY`/`LINK`）以保持视觉一致性。
- 要为按钮设置间距，使用 `Gap` 或 `ingap`/`outgap`/布局比例——`Spacer` 已被移除（见 [`spacing.md`](/docs/ui/widgets/spacing)）。

## 常见错误
- **`text` + `icon` 却没有 `textPos`：** 结果会重叠（`null` = `NONE` 的行为）。
- **期望在 `onTouchDown` 触发：** 动作在标准点击流程（touch-up）中触发。

## 另见
- 该领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 概念（组件模型 / `onAction`）：[`concepts/`](/docs/concepts)
- 相关组件：[`Label`](/docs/ui/widgets/label) · [`ImageView`](/docs/ui/widgets/imageview)
