# TextInput

> 文本输入的抽象基类 —— 光标、选择、剪贴板、焦点和键盘，为 TextField 和 TextArea 所共有。

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

# TextInput

`TextInput` 是**抽象类**，它集中了 [`TextField`](/docs/ui/widgets/textfield)（单行）和 [`TextArea`](/docs/ui/widgets/textarea)（多行）之间共享的所有编辑逻辑：光标、选择、剪贴板、焦点/闪烁、键盘快捷键、方向键导航、带 `ActionSheet` 的长按，以及与虚拟键盘的集成。子类实现空间映射（1D 与 2D）、滚动、渲染和掩码。

你**不直接实例化** `TextInput` —— 而是实例化 `TextField` 或 `TextArea`。本页是两者所继承内容的总览。它实现 `UIConstants.TextInputComponent`（`setText`/`getText`/`isPassword`/`replaceText`/`keyboardHidden`/`getCursorPos`/`hasFocus`）。

> 🟡 · **包：** `dev.crossfly.ui` · **继承：** `Component` · **实现：** `UIConstants.TextInputComponent` · **抽象**

## 何时使用
- 作为引用任何输入字段的**通用类型**（`TextInput campo = new TextField(...)`）。
- 编写在光标/选择/剪贴板上操作而不关心是 1 行还是 N 行的代码时。

## 何时不使用
- 实例化一个具体字段 → 使用 [`TextField`](/docs/ui/widgets/textfield)（1 行）或 [`TextArea`](/docs/ui/widgets/textarea)（多行）。

## 层级
`Component` → **`TextInput`**（抽象）→ [`TextField`](/docs/ui/widgets/textfield) / [`TextArea`](/docs/ui/widgets/textarea)。构造函数接收缓冲区的初始容量：`TextInput(int initialCapacity)`。它是 `isPressable() → true`。

## 内部状态（关键字段）
| 字段 | 类型 | 含义 |
|---|---|---|
| `chars` | `StringBuffer` | 文本的原始缓冲区 |
| `text` | `String` | 显示的文本（由子类更新；初始为 `""`） |
| `insertPos` | `int` | 光标位置（插入） |
| `startSelectPos` | `int` | 选择的起点，若无选择则为 `-1` |
| `cursorShowing` | `boolean` | 光标闪烁的可见相位 |
| `keyboardCanOpen` | `boolean` | 键盘可在下一次 touch-up 时打开 |
| `hasFocus` | `boolean` | 拥有键盘焦点 |
| `caption` | `String` | 占位符 / 浮动 caption |

## 继承的方法（光标、选择、剪贴板）
| 方法 | 返回 | 作用 |
|---|---|---|
| `setText(String)` | `void`* | 设置文本（由子类实现） |
| `getText()` | `String` | 当前文本（在 `TextField` 中含掩码） |
| `setCursorPos(int start, int end)` | `void` | 设置选择；若 `start==end`，则只移动光标 |
| `setCursorPos(int pos)` | `void` | 无选择的光标 |
| `getCursorPos()` | `int[]` | `[startSelectPos, insertPos]` |
| `getInsertPos()` | `int` | 光标位置 |
| `hasSelection()` | `boolean` | 是否有选中的区间？ |
| `getSelectionStart()` / `getSelectionEnd()` | `int` | 位置的 min / max |
| `getTextBuffer()` / `getLength()` | `StringBuffer` / `int` | 原始缓冲区 / 长度 |
| `clipboardCopy()` / `clipboardCut()` | `void` | 复制 / 剪切选区（通过 `Device.clipboard*`） |
| `clipboardPaste()` / `clearText()` | `void` | **抽象** —— 子类处理掩码/`\n` |
| `mapKeys(String from, String to)` | `void` | 逐字符的重映射（若长度不同则抛出 `IllegalArgumentException`；`null`/`null` 清除） |
| `hasFocus()` | `boolean` | 拥有键盘焦点 |
| `restoreFocusAtEnd()` | `void` | 重新赋予焦点并将光标移到末尾（关闭 picker/keypad 之后） |

*`setText` 在 `TextComponent` 契约中返回 `void`；`TextField.text(...)`/`TextArea.text(...)` 提供返回字段本身的**流式**变体。

## 回调与事件
| 回调 | 何时触发 |
|---|---|
| `onAction(Action)`（继承） | 在 `onTouchUp`（打开键盘之后） |
| `onFocus()` / `onBlur()` | 获得 / 失去焦点（开启/关闭光标和闪烁） |

不存在专门的 `onChange`：要对变化作出反应，通过动作回调观察或查询 `getText()`。

## 状态
- **focused：** 光标开启并闪烁（`cursorShowing`），触摸时打开键盘/`Keypad`；`onFocus()`/`onBlur()` 控制过渡。
- **selection：** 区间 `[startSelectPos, insertPos]`；长按选中全部并打开 `ActionSheet`（Copy/Cut/Paste/Clear）。
- **disabled / read-only：** 样式中的 `setEnabled(false)` 或 `editable=false` 阻止编辑。

## 按键（继承自 `onKeyDown`）
- **Ctrl+C / V / X** → 复制 / 粘贴 / 剪切；**Ctrl+A** → 选中全部。
- **Backspace / Delete** → 删除选区或一个字符。
- **Left / Right / Home / End** → 移动光标；配合 **Shift** 扩展选择。

## 完整示例（复制、粘贴并运行）
```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());

      // 多态引用：TextInput 是 TextField/TextArea 的抽象基类
      TextInput campo = new TextField("valor inicial");
      add(LAY_AUTO, (Component) campo);

      // 输出 Label
      Label saida = new Label("Leia o campo com o botão");
      add(LAY_AUTO, saida);

      // 没有按键事件：在提交时读取 getText()
      Button enviar = new Button("Enviar")
         .onAction(c -> saida.setText("Você digitou: " + campo.getText()));
      add(LAY_AUTO, enviar);
   }
}
```

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

## 示例（使用基类型）
```java
// 对具体字段的多态引用
TextInput campo = new TextField("valor inicial");
container.add(LAY_AUTO, (Component) campo);

// 通过基类进行的通用操作
if (campo.hasSelection())
   campo.clipboardCopy();
int[] sel = campo.getCursorPos();     // [start, insert]
campo.setCursorPos(campo.getLength()); // 光标移到末尾
```

## 最佳实践
- 当代码不依赖于格式（1 行 vs N 行）时，针对 `TextInput`/`UIConstants.TextInputComponent` 编程。
- 关闭 picker/`Keypad` 之后，调用 `restoreFocusAtEnd()` 以正确地归还焦点。

## 常见错误
- **试图实例化 `TextInput`：** 它是抽象的 —— 使用 `TextField`/`TextArea`。
- **期待 `clipboardPaste()`/`clearText()` 在基类中生效：** 它们是抽象的；行为来自子类（`TextField` 中的掩码，`TextArea` 中的 `\n`）。

## 另见
- 领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 概念（焦点、输入事件）：[`concepts/`](/docs/concepts)
- 相关组件：[`TextField`](/docs/ui/widgets/textfield) · [`TextArea`](/docs/ui/widgets/textarea) · [`Keypad`](/docs/ui/widgets/keypad)
