# 窗口栈

> 导航背后的模型——EventsProcessor 的栈、弹窗与全屏、返回按钮以及过渡动画。

Source: https://crossfly.dev/zh/docs/ui/navigation/the-window-stack — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 窗口栈

理解这个模型可以避免大多数导航相关的 bug。

## 模型（LIFO）
`EventsProcessor` 维护一个 `Window` 的**栈**。栈顶的窗口是**接收事件并绘制在最上层**的那个。下方的窗口仍然存活（不会被销毁），只是被遮盖。

```
┌─────────────┐  ← topo: recebe toque/teclado, é pintada
│ TelaDetalhe │
├─────────────┤
│ TelaLista   │  ← coberta, viva
├─────────────┤
│ TelaHome    │  ← base
└─────────────┘
```

- `show()` → **push**：在 `EventsProcessor` 中注册，调用 `onShow()`。
- `hide()` → **pop**：注销，调用 `onRemove()`；下方的窗口回到栈顶。

## 每个窗口的生命周期
| 方法 | 何时 | 用途 |
|---|---|---|
| `onShow()` | 进入栈时 | 构建 UI（务必调用 `super.onShow()`） |
| `onReposition()` | 调整尺寸/旋转时 | 重新组织/重新计算布局 |
| `onRemove()` | 离开栈时 | 释放计时器、光标、监听器 |

## 全屏与弹窗
**同一个** `Window` 类既可以是全屏也可以是弹窗——取决于 `WindowStyle`
（`widthRatio`/`heightRatio` < 1 ⇒ 弹窗；参见 [对话框与模态框](/docs/ui/navigation/dialogs-and-modals)）。弹窗覆盖在前一个屏幕之上（后者仍然在后方可见）。

## 返回按钮（Android）与点击外部
- **物理返回键**被应用捕获，默认对栈顶窗口执行 `hide()`。
- 在 `canHide = true` 的弹窗中，**点击外部**会关闭弹窗。
- 要**阻止**退出（例如：放弃确认），请在允许 `hide()` 发生之前处理该意图。

## 完整示例（复制、粘贴并运行）
演示栈的 push/pop 以及在被压入窗口的 `WindowStyle` 中配置的进入过渡动画。
```java
package com.exemplo;

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

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

      add(LAY_AUTO, new Label("Base da pilha (MainWindow)"));

      Button empilhar = new Button("Empilhar tela (push)");
      empilhar.onAction(c -> new TelaEmpilhada().show());   // push：进入栈顶
      add(LAY_AUTO, empilhar);
   }
}

/** 带转场进入栈顶、用 hide()（pop）返回的屏幕。 */
class TelaEmpilhada extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      // 此窗口的进入/退出转场（下方那个仍在后面存活）
      getStyle().transition(UIConstants.Transition.SLIDE_LEFT);
      setLayout(new VerticalLayout());

      add(LAY_AUTO, new Label("Topo da pilha — a tela de baixo continua viva"));

      Button voltar = new Button("Voltar (pop)");
      voltar.onAction(c -> hide());   // pop：出栈，下方那个回到栈顶
      add(LAY_AUTO, voltar);
   }
}
```
> 下面的片段只展示**核心部分**（放进 `onShow()` 里的内容）。要运行它们，请将其放入上面的 `MainWindow` 骨架中。

## 过渡动画
进入/退出动画来自 `WindowStyle.transition`（`UIConstants.Transition`）：`NONE`
（默认）、`FADE`、`SLIDE_LEFT/RIGHT/UP/DOWN`、`SCALE`、`SCALE_DOWN`。选择器默认使用 `FADE`。
```java
getStyle().transition = UIConstants.Transition.SLIDE_LEFT;   // 打开时滑入
```

## 最佳实践
- 不要在超出必要的时间内持有下方窗口的引用——让它们在 `hide()` 时被回收。
- 每个 `show()` 都应有对应的 `hide()`（避免堆叠无法返回的屏幕）。

## 常见错误
- 在 `onReposition()` 中重建 UI 却不保留状态（滚动、选中）。
- 假设打开另一个窗口时下方的窗口已被销毁——它仍然存活。

## 另请参阅
- [在屏幕间导航](/docs/ui/navigation/navigate-between-screens) · [对话框与模态框](/docs/ui/navigation/dialogs-and-modals)
