Choices: components 031-040 (wip)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 12:26:17 -04:00
1 parent cfc7ab023d
commit d67eb60bf9
54 files changed
+4253 -1

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# 031 Switch · 开关
## 用途
表示立即生效的开/关设置,如“自动备份”。组件只翻转值并回传结果,不调用任何业务接口;调用方在 `Changed()` 时执行操作,可用 `Loading` 表示“正在应用”。
## 最小示例
```go
wifi := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Switch(c, &wifi, "Wi-Fi", mujica.SwitchOptions{}).Changed() {
log.Println("Wi-Fi:", wifi)
}
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func Switch(c *ui.Context, on *bool, label string, opts SwitchOptions) *ui.Element`。`label` 为空时请在返回值上调用 `Label` 命名。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Disabled` | `bool` | false | 禁用:不响应点击与键盘,退出 Tab 序列,由 MyGo 置灰。 |
| `Loading` | `bool` | false | 调用方正在应用结果:滑块内显示旋转标记,保留尺寸与标签,暂不接受输入。 |
| `Description` | `string` | "" | 辅助说明,屏幕阅读器在标签后读出。 |
## 状态
关(象牙底、褐色方块滑块)、开(酒红轨道、象牙滑块,并在左侧显示竖线标记,不只依赖颜色)、悬停(边框与底色加深)、聚焦(轨道外 2 DIP 焦点环)、禁用、加载。切换动画 120 ms,减少动画模式下直接切换。
## 事件
`Changed()` 在用户切换的那一帧返回 true,`*on` 已是新值。若业务失败,调用方自行把值改回并提示错误。
## 键盘操作
- Tab / Shift+Tab:聚焦或离开。
- Space:切换。
## 限制
- 基于 `ui.SwitchBase`,无障碍角色为 Switch,开关状态由 MyGo 报告。
- 组件不保存“请求中”状态;`Loading` 由调用方根据自己的异步任务设置。
+43
View File
@@ -0,0 +1,43 @@
# 032 Toggle · 切换按钮
## 用途
保持按下状态的按钮,例如编辑器工具栏中的“加粗”。可显示文字、图标或二者兼有。
## 最小示例
```go
bold := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Toggle(c, &bold, "", mujica.ToggleOptions{Icon: icons.Must("bold"), Label: "Bold"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func Toggle(c *ui.Context, on *bool, label string, opts ToggleOptions) *ui.Element`。`label` 与 `opts.Label` 至少提供一个,否则 panic。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Icon` | `*ui.SVG` | nil | 显示在文字前的图标。 |
| `Disabled` | `bool` | false | 禁用:不响应输入,退出 Tab 序列。 |
| `Label` | `string` | "" | 无文字按钮的无障碍名称。 |
## 状态
未按下(象牙底、描边)、按下(玫瑰底、酒红边框与文字,底部 2 DIP 横条标记——选中不只依赖颜色)、悬停、按住、聚焦(2 DIP 焦点环)、禁用。
## 事件
`Changed()` 在用户切换的那一帧返回 true。
## 键盘操作
- Tab:聚焦。
- Space:按下或松开。
## 限制
- 基于 `ui.ToggleBase`,无障碍角色为 ToggleButton,按下状态由 MyGo 报告。
+63
View File
@@ -0,0 +1,63 @@
# 033 ToggleGroup · 切换按钮组
## 用途
一排相连的切换按钮,单选(如对齐方式)或多选(如粗体、斜体、下划线)。可设置是否允许全部松开。
## 最小示例
```go
align := []string{"left"}
items := []mujica.ToggleItem[string]{
{Value: "left", Label: "Left"},
{Value: "center", Label: "Center"},
{Value: "right", Label: "Right"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ToggleGroup(c, &align, items, mujica.ToggleGroupOptions{Label: "Align"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func ToggleGroup[T comparable](c *ui.Context, value *[]T, items []ToggleItem[T], opts ToggleGroupOptions) ChoiceResult`。单选模式下 `*value` 最多一个元素,否则 panic。
`ToggleItem[T]`:
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Value` | `T` | — | 稳定的选项值。 |
| `Label` | `string` | "" | 文字。 |
| `Icon` | `*ui.SVG` | nil | 图标。 |
| `Name` | `string` | "" | 仅图标时的无障碍名称。 |
| `Disabled` | `bool` | false | 单项禁用,键盘导航跳过它。 |
`ToggleGroupOptions`:
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Multiple` | `bool` | false | 允许同时按下多项。 |
| `AllowEmpty` | `bool` | false | 允许松开最后一个已按下项。 |
| `Vertical` | `bool` | false | 纵向排列,↑/↓ 导航。 |
| `Disabled` | `bool` | false | 禁用整组。 |
| `Label` | `string` | "" | 组的无障碍名称。 |
## 状态
未按下、按下(玫瑰底 + 酒红文字 + 底部横条)、悬停、按住、聚焦、单项禁用、整组禁用。
## 事件
返回的 `ChoiceResult.Changed()` 在值变化的那一帧返回 true。不允许空选时点击唯一按下项不产生事件。
## 键盘操作
- Tab:进入组,落在按下项(或上次聚焦项);再按 Tab 离开。
- ←/→(纵向为 ↑/↓)、Home、End:在项之间移动焦点,跳过禁用项,首尾循环。
- Space:按下或松开当前项。
## 限制
- 每项基于 `ui.ToggleBase`,组用 MyGo `FocusGroup` 实现方向键导航;MyGo 的 `Key` 不能用于自带输入处理的控件(ui/element.go:474),所以项以位置区分,`items` 在帧之间增删时焦点可能落到相邻项。
+48
View File
@@ -0,0 +1,48 @@
# 034 Slider · 滑块
## 用途
在连续或分步的数值范围内取值,如音量。滑块为菱形,命中区域是整个矩形控件。
## 最小示例
```go
volume := 40.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Slider(c, &volume, mujica.SliderOptions{Step: 5, ShowValue: true, Label: "Volume"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func Slider(c *ui.Context, value *float64, opts SliderOptions) ChoiceResult`。范围外的值在构建时被夹到范围内并对齐步长(不算一次用户变更)。`Min >= Max` 或 `Step < 0` 时 panic。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Min`, `Max` | `float64` | 0, 0 → 0–100 | 范围;二者都为 0 时取 0–100。 |
| `Step` | `float64` | 0 | 步长;值为 `Min + n·Step`。0 为连续。 |
| `Marks` | `[]SliderMark` | nil | 刻度:`Value` 处画刻线,`Label` 非空时在下方显示文字。 |
| `ShowValue` | `bool` | false | 悬停、拖动或聚焦时在滑块上方显示数值提示(预留高度,不改变布局)。 |
| `Format` | `func(float64) string` | 按步长精度 | 数值文字。 |
| `Disabled` | `bool` | false | 禁用。 |
| `Label` | `string` | "" | 无障碍名称。 |
## 状态
默认、悬停(菱形放大 2 DIP)、拖动、聚焦(菱形外菱形焦点环)、禁用。已选段用 `AccentText`,深色主题对比度满足 3:1。
## 事件
`Changed()` 在值变化的那一帧返回 true。
## 键盘操作
- ←/↓、→/↑:减少或增加一个步长(连续模式为范围的 1%)。
- Home / End:最小值 / 最大值(对齐到步长)。
## 限制
- 基于 `ui.SliderBase`。MyGo 的方向键步长固定为范围的 1%(ui/base.go:116),本组件把每次按键结果换算为一个 `Step`;当 `Step` 小于范围的 1% 且滑块距端点不足 1% 时,方向键会直接到端点。
- `SliderBase` 在构建时就处理指针(ui/base.go:111),禁用时组件改为让它操作副本,保证值不变。
+50
View File
@@ -0,0 +1,50 @@
# 035 RangeSlider · 范围滑块
## 用途
用两个菱形滑块选择一个区间,如价格范围。两端不会交叉(允许相等)。
## 最小示例
```go
low, high := 20.0, 80.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RangeSlider(c, &low, &high, mujica.RangeSliderOptions{Step: 10, Label: "Price"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func RangeSlider(c *ui.Context, low, high *float64, opts RangeSliderOptions) ChoiceResult`。`*low > *high` 时两者交换;超出范围的值被夹回并对齐步长。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Min`, `Max` | `float64` | 0, 0 → 0–100 | 范围。 |
| `Step` | `float64` | 0 | 步长,0 为连续。 |
| `Marks` | `[]SliderMark` | nil | 刻度与文字。 |
| `ShowValue` | `bool` | false | 悬停、拖动或聚焦时显示两端数值;低端提示向左、高端向右,互不遮挡。 |
| `Format` | `func(float64) string` | 按步长精度 | 数值文字。 |
| `Disabled` | `bool` | false | 禁用。 |
| `Label` | `string` | "" | 区间名称;两个滑块分别读作“`Label` 最小值, 20”“`Label` 最大值, 80”。 |
## 状态
默认、悬停、拖动、聚焦(各滑块独立焦点环)、禁用。
## 事件
`Changed()` 在任一端变化的那一帧返回 true。
## 键盘操作
- Tab:依次聚焦低端、高端滑块。
- ←/↓、→/↑:一个步长;不能越过另一端。
- Home / End:到范围端点或另一端所在位置。
- 指针按下轨道任意处:移动较近的一端,并把焦点交给它。
## 限制
- MyGo 自带的 `ui.RangeSlider` 使用玻璃质感滑块(ui/widgets.go:419),不符合本库风格;MyGo 也没有双滑块 Base,故本组件用公开的 `Dragged`、`PointerPosition`、`Shortcut` 组合实现。
- 滑块的无障碍角色为 Slider,但数值范围属性只能由 `SliderBase` 设置(ui/base.go:128),因此当前值写在名称里(如“Price 最小值, 20”),屏幕阅读器读名称而非数值属性。
+55
View File
@@ -0,0 +1,55 @@
# 036 Select · 选择器
## 用途
从弹出列表中选择一个值。支持占位、清空、禁用项、空状态与输入首字母定位。
## 最小示例
```go
plan := ""
options := []mujica.SelectOption[string]{
{Value: "free", Label: "Free"},
{Value: "pro", Label: "Pro"},
{Value: "team", Label: "Team", Disabled: true},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Select(c, &plan, options, mujica.SelectOptions{Clearable: true, Label: "Plan"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func Select[T comparable](c *ui.Context, value *T, options []SelectOption[T], opts SelectOptions) ChoiceResult`。`*value` 不等于任何选项时显示占位;清空把值设为 `T` 的零值。
`SelectOption[T]`:`Value T`(稳定值)、`Label string`、`Disabled bool`(显示但不可选,方向键跳过)。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Placeholder` | `string` | 内置“请选择…” | 无值时的提示。 |
| `Clearable` | `bool` | false | 有值时显示清空按钮,并允许 Backspace/Delete 清空。 |
| `Disabled` | `bool` | false | 禁用。 |
| `Empty` | `string` | 内置“暂无选项” | `options` 为空时弹层中的说明。 |
| `Label` | `string` | "" | 无障碍名称。 |
## 状态
默认、悬停(边框加深)、展开(酒红边框、箭头朝上)、聚焦、禁用;弹层中高亮项(浅底)、已选项(勾号 + 酒红文字)、禁用项、空状态。
## 事件
`Changed()` 在值变化(选择或清空)的那一帧返回 true。
## 键盘操作
- Enter / Space / ↑ / ↓:打开弹层。
- 弹层中 ↑/↓、Home、End 移动高亮,Enter 或 Space 选择,Escape 关闭;关闭后焦点回到选择器。
- 弹层关闭时输入字母:选中以该前缀开头的选项(1 秒内连续输入组成前缀,重复同一字母则循环);支持输入法提交的中文。
- Backspace / Delete:`Clearable` 时清空。
## 限制
- 基于 `ui.SelectBase`。其高亮索引没有公开的设置接口(ui/base.go:260),所以弹层打开时输入字母不移动高亮,只在弹层关闭时定位。
- 清空按钮不占 Tab 位(与 MyGo 搜索框一致),键盘用 Backspace/Delete 清空。
+54
View File
@@ -0,0 +1,54 @@
# 037 MultiSelect · 多选选择器
## 用途
从列表中选择多个值,弹层内可搜索、全选当前匹配项,可限制数量;已选值以标签显示,超出部分折叠为“+N”。
## 最小示例
```go
tags := []int{1}
options := []mujica.SelectOption[int]{
{Value: 1, Label: "Rose"},
{Value: 2, Label: "Lily"},
{Value: 3, Label: "Iris"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MultiSelect(c, &tags, options, mujica.MultiSelectOptions{Max: 2, Label: "Flowers"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func MultiSelect[T comparable](c *ui.Context, values *[]T, options []SelectOption[T], opts MultiSelectOptions) ChoiceResult`。`*values` 按选择顺序保存;组件每次修改都会生成新切片。`Max` 或 `MaxTags` 为负数时 panic。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Placeholder` | `string` | 内置“请选择…” | 无选择时的提示。 |
| `Max` | `int` | 0 | 最多选择数,0 不限;达到上限后其余项禁用,并显示“已选 n / Max”。 |
| `MaxTags` | `int` | 0 → 3 | 字段中显示的标签数,其余折叠为“+N”。 |
| `Disabled` | `bool` | false | 禁用(标签不可移除)。 |
| `Label` | `string` | "" | 无障碍名称。 |
## 状态
字段:占位、标签、折叠、展开、悬停、聚焦、禁用。弹层:搜索框、全选(全选/半选/未选)、选中项(酒红方框 + 白勾)、禁用项、达到上限、无结果。
## 事件
`Changed()` 在勾选、取消、全选或移除标签的那一帧返回 true。
## 键盘操作
- Enter / Space / ↓:打开弹层,焦点在搜索框。
- 搜索框中 ↓:进入列表(“全选”);列表内 ↑/↓、Home、End 移动;Space 勾选。
- Escape:关闭,焦点回到字段。
- 字段上 Backspace:移除最后一个值。
## 限制
- 选项行基于 `ui.CheckboxBase` 自绘方框;全选的“半选”只在画面上表示,MyGo 没有为自定义元素设置 mixed 状态的公开接口(ui/element.go:282 的 checked 字段仅由内部设置),屏幕阅读器读作未选中。
- 标签上的移除按钮不占 Tab 位,键盘用 Backspace 移除。
- 列表未虚拟化,适合数百项以内。
+55
View File
@@ -0,0 +1,55 @@
# 038 Combobox · 组合框
## 用途
输入文字筛选选项并选择,覆盖 MyGo 的 Autocomplete(自由输入)与 Combobox(选项选择)。可允许自定义值;选项可由调用方异步提供。
## 最小示例
```go
font := ""
fonts := []string{"Garamond", "Georgia", "Didot"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.Combobox(c, &font, fonts, mujica.ComboboxOptions{AllowCustom: true, Label: "Font"})
if r.QueryChanged() {
log.Println("search:", r.Query())
}
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func Combobox(c *ui.Context, value *string, options []string, opts ComboboxOptions) ComboboxResult`。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Placeholder` | `string` | "" | 空输入时的提示。 |
| `AllowCustom` | `bool` | false | 允许不在选项中的文字成为值:列表首项为“使用“xxx””,Enter 或离开字段时保留输入。 |
| `ExternalFilter` | `bool` | false | 选项已由调用方按 `Query` 过滤,组件不再筛选。 |
| `Loading` | `bool` | false | 调用方正在获取选项:字段内显示旋转标记,无选项时弹层显示“加载中…”。 |
| `Disabled` | `bool` | false | 禁用。 |
| `Label` | `string` | "" | 无障碍名称。 |
`ComboboxResult` 嵌入 `ChoiceResult`,另有 `Query() string`(当前输入)与 `QueryChanged() bool`(用户本帧输入了文字,此时发起请求)。
## 状态
默认、悬停、聚焦(酒红边框 + 光标)、展开、加载、无匹配(“无匹配项”)、禁用;列表高亮项、当前值(勾号)。
## 事件
- `Changed()`:值变化(选择、自定义提交)。
- `QueryChanged()`:输入变化。异步流程:在此发起请求,结果到达后(经 `Window.Update`)通过 `options` 传入并清除 `Loading`。
## 键盘操作
- 输入:筛选(前缀匹配优先)并打开列表,高亮第一项。
- ↓ / ↑:打开或移动高亮;Enter 选择;Escape 关闭。
- Tab 离开:不允许自定义时输入恢复为当前值;允许时提交输入。
## 限制
- 基于 `ui.ComboboxBase`。它在没有选项时隐藏弹层(ui/combobox.go:191),所以“加载中”“无匹配”用一个不接收输入的独立浮层显示。
- 异步结果在打字后才到达时 MyGo 不会自动高亮首项,组件让 Enter 选择首个结果。
+60
View File
@@ -0,0 +1,60 @@
# 039 Cascader · 级联选择
## 用途
逐级选择一条路径,如“欧洲 / 法国 / 巴黎”。字段显示完整路径;子级可由调用方按需加载,并有加载中、失败与重试状态。
## 最小示例
```go
var path []string
level := func(p []string) mujica.CascaderLevel {
if len(p) == 0 {
return mujica.CascaderLevel{Nodes: []mujica.CascaderNode{{Value: "fr", Label: "France"}}}
}
return mujica.CascaderLevel{Nodes: []mujica.CascaderNode{{Value: "paris", Label: "Paris", Leaf: true}}}
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Cascader(c, &path, mujica.CascaderOptions{Level: level, Label: "City"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func Cascader(c *ui.Context, path *[]string, opts CascaderOptions) ChoiceResult`。`*path` 是各级 `Value` 组成的路径。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Level` | `func(path []string) CascaderLevel` | 必填 | 返回某路径下的子级(空路径为根)。为 nil 时 panic。 |
| `Load` | `func(path []string)` | nil | 子级状态为 `CascaderIdle` 时调用一次,点击重试时再次调用。应把该级标为 `CascaderLoading`,结果经 `Window.Update` 写回。有 Idle 级别但 `Load` 为 nil 时 panic。 |
| `AnyLevel` | `bool` | false | 允许选择非叶节点;默认只有叶节点(`Leaf: true`)可成为值。 |
| `Placeholder` | `string` | 内置“请选择…” | 无值时提示。 |
| `Disabled` | `bool` | false | 禁用。 |
| `Label` | `string` | "" | 无障碍名称。 |
`CascaderLevel`:`Nodes []CascaderNode`、`Status CascaderStatus`(零值 `CascaderLoaded`;另有 `CascaderIdle`、`CascaderLoading`、`CascaderFailed`)、`Err error`(失败原因,显示在重试按钮旁)。
`CascaderNode`:`Value`、`Label`、`Leaf`(无子级)、`Disabled`。
## 状态
字段:占位、完整路径、展开、聚焦、禁用。弹层每列:加载中(旋转标记)、失败(错误文字 + 重试按钮)、空(“暂无数据”)、节点列表;展开路径上的节点用玫瑰底,已选路径用酒红文字,分支节点带右箭头。
## 事件
`Changed()` 在路径变化的那一帧返回 true。叶节点选择后弹层关闭。
## 键盘操作
- Enter / Space / ↓:打开弹层,焦点在当前路径或第一项。
- 列内 ↑/↓、Home、End:移动。
- →:展开分支并进入下一列;←:回到上一列的父节点。
- Enter / Space:叶节点选择并关闭;分支展开(`AnyLevel` 时同时选择)。
- Escape:关闭,焦点回到字段。
## 限制
- `Load` 在视图构建中同步调用,不得阻塞;异步任务完成后请用 `Window.Update` 回到界面线程更新数据。
- 列表未虚拟化,每列适合数百项以内。
+59
View File
@@ -0,0 +1,59 @@
# 040 TreeSelect · 树选择
## 用途
在树形数据中选择一个或多个节点,支持搜索、展开折叠、半选显示,父子联动规则可配置。
## 最小示例
```go
var roles []string
nodes := []mujica.TreeSelectNode{
{ID: "court", Label: "Court", Children: []mujica.TreeSelectNode{
{ID: "king", Label: "King"},
{ID: "queen", Label: "Queen"},
}},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TreeSelect(c, &roles, nodes, mujica.TreeSelectOptions{Multiple: true, Label: "Roles"})
}
ui.Render(view, 400, 300, 1)
```
## 参数
`func TreeSelect(c *ui.Context, selected *[]string, nodes []TreeSelectNode, opts TreeSelectOptions) ChoiceResult`。单选模式下 `*selected` 最多一个 ID,否则 panic。节点 ID 在整棵树中必须唯一。
`TreeSelectNode`:`ID`、`Label`、`Children []TreeSelectNode`、`Disabled`。
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `Multiple` | `bool` | false | 多选(复选框);单选时点击节点即选择并关闭。 |
| `Rule` | `TreeSelectRule` | `TreeSelectCascade` | 多选父子规则:`TreeSelectCascade` 勾选父节点即勾选其全部可用叶节点,值只含叶节点 ID,父节点按子节点显示全选/半选;`TreeSelectIndependent` 每个节点独立勾选,值可含任意 ID。 |
| `Placeholder` | `string` | 内置“请选择…” | 无值时提示。 |
| `MaxTags` | `int` | 0 → 3 | 多选时字段中显示的标签数,其余折叠为“+N”。 |
| `Disabled` | `bool` | false | 禁用。 |
| `Label` | `string` | "" | 无障碍名称。 |
## 状态
字段:占位、单值文字、多值标签与折叠、展开、聚焦、禁用。弹层:搜索框、展开/折叠箭头、未选/选中/半选方框(单选为玫瑰底高亮)、禁用节点(联动时被跳过)、无结果。搜索时显示匹配节点及其祖先并自动展开。
## 事件
`Changed()` 在选择变化的那一帧返回 true。
## 键盘操作
- Enter / Space / ↓:打开弹层,焦点在搜索框;搜索框中 ↓ 进入树。
- ↑/↓、Home、End:在可见节点间移动。
- →:展开;已展开时进入第一个子节点。←:折叠;已折叠或叶节点时回到父节点。
- Space:勾选(多选);Enter / Space:选择(单选)。
- Escape:关闭,焦点回到字段。
## 限制
- 树行基于 `ui.CheckboxBase` / `ui.ButtonBase` 自绘。MyGo 的 `TreeItem` 只能显示文字标签,无法放入复选框(ui/tree.go:44),故未复用。
- 展开状态与半选状态只在画面上表示:MyGo 不对自定义元素公开 expanded 与 mixed 的设置(ui/base.go:297、ui/element.go:282 仅内部设置),屏幕阅读器读不到这两个状态;展开箭头带“展开/收起 + 节点名”的名称。
- 列表未虚拟化,适合数千节点以内。