mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-29 07:16:33 +08:00
191 lines
11 KiB
Markdown
191 lines
11 KiB
Markdown
# Avatar 桌面版实现原理
|
||
|
||
本文档描述 Avatar 控件家族的源码 ownership、图片加载状态流、模板生命周期和资源释放规则。公共契约见
|
||
[Avatar 控件家族架构设计](overview.md),Token 语义见 [Avatar Token 设计](token.md)。
|
||
|
||
Popup 接入边界:`AvatarGroup` 负责业务状态和内容准备,内部 `FlyoutHost` 仅作为 relay 适配层,fold-count Flyout Popup 负责实际显示。模板重建或宿主切换时必须先释放旧 relay,再绑定新的 Popup;普通外点、Escape、失焦和业务关闭在 pinned 状态下被拦截,detach、窗口销毁、跨 TopLevel 和无效锚点必须走生命周期关闭并释放 Popup host。完整状态机见 [Popup 钉住打开设计](../../other/popup/popup-pinned-open-design.md)。
|
||
|
||
## 1. 实现定位
|
||
|
||
本文覆盖单头像、桌面 AvatarGroup、共享单图 controller、主题接入和图片租约生命周期。具体 StyledProperty 注册、Token 派生值和
|
||
AXAML selector 仍以源码为准;本文只记录跨文件协作和维护时不可破坏的实现边界。
|
||
|
||
## 2. 源码文件结构
|
||
|
||
`AtomUI.Controls` 拥有单头像实现:
|
||
|
||
```text
|
||
src/AtomUI.Controls/Avatar/
|
||
├── AbstractAvatar.cs
|
||
├── Avatar.cs
|
||
├── AvatarPseudoClass.cs
|
||
├── AvatarShape.cs
|
||
├── AvatarToken.cs
|
||
└── Themes/AvatarTheme.axaml
|
||
```
|
||
|
||
`AtomUI.Desktop.Controls` 只拥有桌面组合控件:
|
||
|
||
```text
|
||
src/AtomUI.Desktop.Controls/Avatar/
|
||
├── AvatarGroup.cs
|
||
├── AvatarGroupFoldInfo.cs
|
||
└── Themes/AvatarGroupTheme.axaml
|
||
```
|
||
|
||
`Avatar` 和 `AvatarToken` 不在 Desktop 包中保留副本。Desktop 通过对 Controls 的正常项目引用复用类型和 Token resource。
|
||
|
||
## 3. 核心类职责
|
||
|
||
- `AbstractAvatar`:Avalonia 属性、图片 controller host、内容优先级、尺寸/形状归一、加载状态和模板生命周期。
|
||
- `Avatar`:可实例化的公共控件类型,不建立第二套状态。
|
||
- `ImageLoadController`:与 `AsyncImage` 共用的单图片请求 owner,负责 generation、取消、fallback、Reload、progress 和租约提交。
|
||
- `AvatarToken`:从全局 Token 派生 Avatar 与 AvatarGroup 共用的视觉值。
|
||
- `AvatarGroup`:子 Avatar 排列、重叠宽度、折叠计数和 Flyout 生命周期。
|
||
|
||
## 4. 状态与数据流
|
||
|
||
```text
|
||
Source / FallbackSource / RequestOptions / Bounds / render scaling
|
||
-> ImageLoadController generation
|
||
-> Application.GetImageLoader().LoadAsync(...)
|
||
-> ImageContentValidator / SvgContentValidator (Application SVG conformance mode)
|
||
-> ImageLoadResult lease
|
||
-> UI dispatcher generation check
|
||
-> LoadedImage + LoadState/Error/Progress
|
||
-> ContentType(Image/Text/Icon) + pseudo-classes
|
||
-> AvatarTheme
|
||
```
|
||
|
||
`AbstractAvatar` 实现 `IImageLoadControllerHost`。它只向 controller 暴露当前 Visual、Normal priority、attach 状态、16 px
|
||
量化后的物理解码尺寸和状态提交回调,不直接访问 transport 或 cache。
|
||
|
||
`SvgContentValidator` 位于 Shared loader 管线中,在 codec 和 Avatar 状态提交之前执行。Application 构建时冻结默认
|
||
`Compatible`/可选 `Strict` 模式;Avatar 不把模式写入 `ImageRequestOptions`,也不根据来源类型选择另一条 SVG 校验路径。
|
||
|
||
Source、FallbackSource 或 RequestOptions 变化调用 `SourceConfigurationChanged()`。Arrange 完成后调用 `RefreshSize()`;只有有效
|
||
Bounds 产生新的尺寸 bucket 时才需要新请求。属性快照和状态提交均在 UI 线程边界完成。
|
||
|
||
## 5. 组合结构模型
|
||
|
||
### 控件角色图
|
||
|
||
```text
|
||
Avatar
|
||
-> Panel#RootLayout
|
||
-> PixelAlignedBorder#Frame (internal-observable)
|
||
-> IconPresenter#IconPresenter (internal-observable)
|
||
-> Border
|
||
-> Image#ImagePresenter (internal-observable)
|
||
-> Border
|
||
-> Viewbox (internal-observable)
|
||
-> TextBlock#PART_TextPresenter (template-stable)
|
||
|
||
AvatarGroup
|
||
-> child Avatar collection (public)
|
||
-> fold Avatar + Flyout (internal-observable)
|
||
```
|
||
|
||
### 协作节点
|
||
|
||
| 节点 | 类型 | 来源 | 生命周期 owner | 影响的 public API | 稳定性 | Agent 使用边界 |
|
||
| --- | --- | --- | --- | --- | --- | --- |
|
||
| `Avatar` | public control | `AvatarTheme.axaml` | visual tree | 单头像全部 API | public | 用户可直接使用 |
|
||
| `ImageLoadController` | internal coordinator | C# | `AbstractAvatar` | Source、fallback、状态、Reload | internal-observable | 只用于理解统一状态机 |
|
||
| Text `Viewbox` | layout presenter | `AvatarTheme.axaml` | visual tree | Text、Gap、Size、Font | internal-observable | 负责自然尺寸测量、向下缩放和居中,不作为独立 API 暴露 |
|
||
| `PART_TextPresenter` | `TextBlock` | `AvatarTheme.axaml` | template | Text、Font | template-stable | 提供自然排版尺寸;改名需同步实现、主题和文档 |
|
||
| `AvatarGroup` | public control | `AvatarGroupTheme.axaml` | visual tree | Children、折叠和尺寸投影 | public | 用户可直接使用 |
|
||
| fold Avatar/Flyout | internal visual | C# + Theme | `AvatarGroup` | MaxDisplayCount、trigger、fold colors | internal-observable | 不作为独立 API 暴露 |
|
||
|
||
内容选择只有三个结果:
|
||
|
||
1. `LoadedImage != null` 时选择 Image。
|
||
2. 否则 `Text != null` 时选择 Text。
|
||
3. 否则选择 Icon。
|
||
|
||
因此 Loading、取消或主来源失败不会主动清空 Text/Icon。Controller 在主来源终态失败后至多请求一次 FallbackSource;主来源
|
||
和 fallback 身份相同则不重复请求。fallback 成功时设置 `:loaded` 与 `:fallback`,最终失败时设置 `:failed`。
|
||
|
||
事件与状态提交顺序必须一致:先保存当前结果和状态,再触发 `ImageOpened`;最终失败先提交错误状态,再触发 `ImageFailed`。
|
||
取消、detach 和 generation 过期不属于图片失败事件。
|
||
|
||
## 6. 生命周期与模板接入
|
||
|
||
成功的 `ImageLoadResult` 是图片使用租约。Controller 持有当前租约,来源变化、detach、最终失败或 controller dispose 时释放。
|
||
替换图片时先接管新租约,再释放旧租约,避免 decoded cache entry 在视觉切换前被销毁。
|
||
|
||
`Reload()` 不创建新的公开来源状态;它复制当前 options,并只把本次请求的
|
||
`CacheRead` 覆盖为 `ImageCacheReadPolicy.RefreshSource`:
|
||
|
||
- HTTP 允许使用已有 metadata 做条件重验证。
|
||
- File、Asset、StorageFile、Bytes 和 Stream 强制重新读取来源。
|
||
- Reload 期间可保留旧图片;新结果只有通过 generation 校验后才能替换。
|
||
|
||
`new BorrowedImageSource(image)` 是 borrowed source。其结果租约只约束消费期,不能 dispose 调用方的 `IImage`。
|
||
|
||
| 获取/建立 | 对应释放 |
|
||
| --- | --- |
|
||
| controller attach 与 loader waiter | visual detach 时 controller detach/cancel |
|
||
| 当前 `ImageLoadResult` lease | Source 清空、替换终态、detach 或 dispose |
|
||
| AvatarGroup motion binding | group detach 时 dispose |
|
||
| AvatarGroup fold Flyout/临时视觉 | rebuild、detach 或 owner 释放时清理 |
|
||
|
||
Reattach 时 controller 使用当前 Source 和当前尺寸重新请求。文字布局不订阅 template part 事件;模板重套用后由 Border、Viewbox
|
||
和 TextBlock 的标准 measure/arrange 流程重新建立可用宽度、自然尺寸与缩放关系。AvatarGroup 只管理组合视觉,不取消或复用子
|
||
Avatar 的图片请求。
|
||
|
||
## 7. 交互与事件处理
|
||
|
||
Avatar 没有独立的 pointer 或 keyboard 状态机;基础交互由 Avalonia 控件和 Theme 处理。图片回调先在 UI dispatcher 校验
|
||
generation 和 attach 状态,再提交结果。成功时先保存 lease、状态和内容,再触发 `ImageOpened`;最终失败时先提交 error 状态,
|
||
再触发 `ImageFailed`。取消、detach 和过期 generation 不触发失败事件。`Reload()` 是唯一命令式图片入口。
|
||
|
||
## 8. 内部算法与关键流程
|
||
|
||
`AvatarTheme.axaml` 通过 internal `ContentType` 选择 Image、Text 或 Icon presenter。唯一稳定 template part 是
|
||
`PART_TextPresenter: Avalonia.Controls.TextBlock`。
|
||
|
||
`AbstractAvatar` 只把 `Gap` 投影为模板内部的水平 Text padding。Border 用该 padding 定义文字可用区域,Viewbox 以
|
||
`PART_TextPresenter` 的自然排版尺寸为输入并使用 `StretchDirection=DownOnly`:自然宽度不超过可用宽度时保持原尺寸,超过时
|
||
等比缩小到可用区域。缩放和居中由同一个模板布局坐标系负责,不单独调用文字测量工具,也不叠加横向补偿 transform;Text、
|
||
Font、Gap 或控件尺寸变化通过 Avalonia measure/arrange 自动重新计算。
|
||
|
||
`Size` 非 `NaN` 时保存原 SizeType 并切换到 Custom;恢复 `NaN` 后回到原 SizeType。Circle 形状把最终宽度的一半写入模板
|
||
CornerRadius。图片请求尺寸来自 Bounds 与 TopLevel render scaling,而不是 Token 名称或逻辑像素直接值。
|
||
|
||
## 9. 资源、性能与 AOT 边界
|
||
|
||
- 不进行同步网络或文件 I/O。
|
||
- 不使用固定延迟等待布局;Arrange 和尺寸 bucket 是唯一尺寸就绪信号。
|
||
- 文字适配使用无状态的 Border/Viewbox 布局,不重复测量字形,不持有 template part 事件订阅。
|
||
- 不创建控件私有 `HttpClient`、cache 或 scheduler。
|
||
- Theme、codec 和 loader 通过静态注册保留,不使用反射扫描。
|
||
- Browser 使用统一 loader 的 Browser 能力矩阵;Avatar 不增加平台分支 API。
|
||
- SVG 一致性模式是封闭的应用级 enum;Avatar 不持有 validator delegate,不增加反射或 NativeAOT 动态发现边界。
|
||
- 快速 Source 切换只允许当前 generation 回写,旧结果必须及时释放。
|
||
|
||
## 10. 维护不变量
|
||
|
||
- `ImageLoadController` 是 Avatar 与 `AsyncImage` 的单图状态机 owner,不在 Avatar 内复制请求协调逻辑。
|
||
- 内容优先级始终为 Image、Text、Icon;Loading 和取消不清空可用降级内容。
|
||
- 每个已提交 cache image 必须由一个有效 lease 支撑;borrowed image 永不由控件销毁。
|
||
- Source、options、尺寸或 attach generation 变化后,旧结果只能释放,不能回写状态。
|
||
- `Compatible`/`Strict` 差异只能来自 Shared `SvgContentValidator`;Avatar 不按 Asset/File/HTTP 或单次请求改写该结论。
|
||
- Template reapply、detach、group rebuild 和 Application dispose 都有明确的取消、解绑和释放路径。
|
||
- 文字的自然尺寸、可用宽度、缩放和居中必须由同一个模板布局路径完成;不得恢复独立文字测量或补偿平移。
|
||
- 单头像和 Token 只存在于 `AtomUI.Controls`,Desktop 包只拥有 AvatarGroup 组合能力。
|
||
|
||
## 11. 测试与验证
|
||
|
||
维护 Avatar 时至少覆盖:
|
||
|
||
- Source、FallbackSource、Text、Icon 优先级与状态伪类。
|
||
- A -> B 快速切换、Reload、detach/reattach、尺寸 bucket 和 render scaling。
|
||
- 主失败/fallback 成功、最终失败、事件顺序和 progress generation。
|
||
- owned cache image 与 borrowed image 的释放责任。
|
||
- AvatarTheme 独立于 Desktop 类型,AvatarGroup 仍能消费同一 Token。
|
||
- 短文本不放大;临界宽度与长文本缩小后保持水平居中,宽度不超过 `Width - 2 * Gap`;运行时 Gap 变化重新布局。
|
||
- Gallery 示例只使用 `Source`/`ImageSource`。
|
||
- 默认 Application `Compatible` 模式加载含重复 id 的本地 SVG;`Strict` 下同一来源报告 `InvalidImageData`,并继续验证
|
||
FallbackSource、`ImageFailed`、伪类和 generation 语义。两种模式对不安全或超预算 SVG 的结果完全相同。
|