Files
chinboy 9f6ec36645 docs: rename directory entry files to overview.md
统一文档目录的入口文件名为 `overview.md`,取代原先的 `index.md`。单 Control 目录本来就用
`overview.md`,此前只有目录级入口用 `index.md`,两套命名并存;现在全库一致。

改名覆盖 42 个入口文件:`docs/` 根、一级分类目录、Architecture 分组与跨模块系统目录、Module
目录、Control 平台与分类目录、Guide 与 Reference 子目录。`docs/AI/generated/index.md` 是手工
维护的导航页,一并改名。

生成产物 `docs/AI/generated/llms/**/index-cn.md` 保持原名,它属于 LLMs Generator 的输出契约。

同步更新 28 个正式文档与 `AGENTS.md` 中的入站链接,以及两处目录树示意图。改名后全部相对链接
可解析,LLMs Generator `verify` 通过(78 控件 / 159 文件),单 Control 四件套文件名契约未变。

新增 `docs/engineering/contributing/documentation-structure-guidelines.md` 承载入口命名、文档
拆分标准、事实所有权和链接规范。此前该规则只写在带日期的设计过程记录里,不适合作为长期来源。
`docs/superpowers/specs/2026-08-10-documentation-information-architecture-design.md` 保留原文
并加取代说明,其余结论继续有效。
2026-08-10 14:24:36 +08:00

570 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GalleryBase 源码展示模块架构设计
本文档定义 GalleryBase 中 ShowCase 源码展示模块的最新设计状态。该模块用于在 Gallery 示例卡片中打开当前示例的源码视图,让用户在不离开 ShowCase 页面、不打断示例运行状态的前提下查看示例 AXAML 和相关代码片段。
源码展示模块由 GalleryBase 运行时控件、产品侧源码片段 Provider 和 GalleryBase 专用 Source Generator 三部分组成。GalleryBase 只提供产品中立的 UI、交互和运行时契约;具体产品 Gallery 负责提供源码片段数据;Source Generator 负责把产品 ShowCase 源文件转换成 AOT 友好的强类型 catalog。
## 1. 模块定位
源码展示模块是 `AtomUI.Toolkits.GalleryBase` 的 ShowCase 辅助能力,服务于 `ShowCaseItem` 示例浏览场景。
模块职责:
- 在 `ShowCaseItem` 上提供查看源码入口。
- 使用共享 Drawer 展示源码,不把大段代码嵌入示例卡片内部。
- 使用多标签只读编辑器展示 AXAML、code-behind、ViewModel 等源码片段。
- 延迟加载源码片段和编辑器实例,避免增加 ShowCase 首屏创建成本。
- 通过产品侧 Provider 获取源码片段,不在 GalleryBase 中引用具体产品页面。
- 通过 Source Generator 生成产品侧源码片段 catalog,并按 `ShowCaseItem` 做源码切片,避免运行时文件扫描和反射扫描。
模块非职责:
- 不编辑源码,不提供保存、格式化、诊断、跳转定义或智能提示能力。
- 不替代控件文档、API reference 或 Design Token 文档。
- 不展示整个源码文件,除非该文件本身就是当前示例的最小片段。
- 不为了让片段可独立编译而引入整页、整文件或无关基础设施代码;源码片段目标是阅读完整,不是生成可复制即编译的小项目。
- 不从运行时文件系统读取 `ShowCases/**/*.axaml`、`.axaml.cs` 或 ViewModel `.cs`。
- 不在 GalleryBase 内写入 `AtomUIGallery.ShowCases.*` 或任何产品命名空间。
## 2. 设计语言
源码展示是 ShowCase 示例的辅助阅读层,不是主要内容层。示例卡片继续优先展示真实控件运行效果;源码入口作为卡片工具动作出现。
交互语言:
- 入口轻量:`ShowCaseItem` 只显示 icon-only 查看源码按钮。
- 阅读聚焦:源码内容在 Drawer 中展示,避免长代码撑开 Masonry 卡片。
- 上下文保留:打开 Drawer 不销毁当前示例、不切换页面、不重建 ShowCase 内容。
- 延迟成本:只有用户点击源码按钮后才解析片段、创建 Drawer 内容和代码编辑器。
- 多文件表达:同一示例可包含 AXAML、Code-behind、ViewModel 等片段标签;每个标签只展示当前 `ShowCaseItem` 的共性上下文和直接相关代码。
Drawer 而不是卡片内展开,是因为 Gallery 示例代码量差异很大。Drawer 提供稳定宽度、内部滚动、多标签和复制工具条,能承载 DataGrid、Form、TreeView 等复杂示例的代码阅读需求。
## 3. 模块分层
源码展示模块采用五层结构:
| 层级 | 所属项目 | 职责 |
|---|---|---|
| 入口层 | `AtomUI.Toolkits.GalleryBase` | `ShowCaseItem` 展示源码按钮,发出打开源码请求 |
| 宿主层 | `AtomUI.Toolkits.GalleryBase` | `GalleryShowCaseCodeDrawerHost` 承载单例 Drawer,接收打开请求 |
| 查看层 | `AtomUI.Toolkits.GalleryBase` | `GalleryShowCaseCodeDrawer` 和 `GalleryCodeViewer` 展示多标签只读源码 |
| 数据层 | 产品 Gallery 项目 | 实现 `IShowCaseCodeSnippetProvider`,从生成 catalog 查询片段 |
| 生成层 | `AtomUI.Toolkits.GalleryBase.Generator` | 扫描产品 AdditionalFiles,按 `ShowCaseItem` 提取 AXAML 与相关 C# 成员,生成 `ShowCaseCodeSnippetCatalog.g.cs` |
依赖方向:
```text
AtomUIGallery
-> AtomUI.Toolkits.GalleryBase
-> AtomUI.Toolkits.GalleryBase.Generator (Analyzer)
AtomUI.Toolkits.GalleryBase
-> AvaloniaEdit / TextMate integration
-> AtomUI.Desktop.Controls
AtomUI.Toolkits.GalleryBase.Generator
-> Microsoft.CodeAnalysis
```
GalleryBase 运行时不能依赖 generator 项目。Generator 只在编译期作为 Analyzer 被产品 Gallery 引用。
## 4. 运行时契约模型
源码展示运行时契约由 key、snippet、snippet group 和 provider 组成。
### 4.1 Snippet Key
`ShowCaseCodeSnippetKey` 标识一个 ShowCase 示例:
```csharp
public readonly record struct ShowCaseCodeSnippetKey(
string ViewTypeName,
string PanelKey,
int ItemIndex,
string? SourceKey = null);
```
字段语义:
| 字段 | 语义 |
|---|---|
| `ViewTypeName` | 当前 ShowCase 页面类型全名,例如 `AtomUIGallery.ShowCases.Button.ButtonShowCase` |
| `PanelKey` | 当前 `ShowCasePanel` 的稳定 key,使用 `Name`,例如 `ExamplesContent` |
| `ItemIndex` | 当前 panel 中 `ShowCaseItem` 的声明顺序索引 |
| `SourceKey` | 可选手工 key,用于复杂页面显式绑定特定源码片段 |
默认 key 使用 `ViewTypeName + PanelKey + ItemIndex`。`SourceKey` 只作为特殊页面的稳定覆盖项,不要求每个 `ShowCaseItem` 手写。参与默认源码匹配的 `ShowCasePanel` 必须有稳定 `Name`;没有 `Name` 的 panel 只能通过 `SourceKey` 显式绑定源码片段。
### 4.2 Snippet Group
一个示例返回一个 `ShowCaseCodeSnippetGroup`:
```csharp
public sealed record ShowCaseCodeSnippetGroup(
string Title,
IReadOnlyList<ShowCaseCodeSnippet> Snippets);
```
`Title` 用于 Drawer 标题。默认使用 `ShowCaseItem.Title` 的当前文本值;Provider 返回的标题只作为 fallback。
### 4.3 Snippet
每个标签页对应一个 `ShowCaseCodeSnippet`:
```csharp
public sealed record ShowCaseCodeSnippet(
string TabTitle,
string Language,
string Text,
string? SourceFilePath,
int StartLine,
int EndLine);
```
字段语义:
| 字段 | 语义 |
|---|---|
| `TabTitle` | 标签标题,例如 `AXAML`、`Code-behind`、`ViewModel` |
| `Language` | 语法高亮语言,例如 `axaml`、`xml`、`csharp` |
| `Text` | 展示的源码片段 |
| `SourceFilePath` | repo 相对路径,用于调试和测试 |
| `StartLine` / `EndLine` | 源文件行号范围 |
Generator 必须默认生成 `AXAML` 标签。`Code-behind` 和 `ViewModel` 标签由 item 级静态分析生成:只有当当前 `ShowCaseItem` 的 AXAML 直接引用事件处理器、Binding、`x:DataType` 或相关命名对象,并且 generator 能从同组 `.axaml.cs` / ViewModel `.cs` 中切出相关成员时才生成。没有相关内容时不生成空标签。Generator 不能自动展示整个 `.axaml.cs` 或 ViewModel 文件。
### 4.4 Provider
GalleryBase 定义产品侧 provider 契约:
```csharp
public interface IShowCaseCodeSnippetProvider
{
bool TryGetSnippetGroup(
ShowCaseCodeSnippetKey key,
out ShowCaseCodeSnippetGroup group);
}
```
Provider 注册在 GalleryBase 配置中:
```csharp
public sealed class GallerySourceCodeDisplayOptions
{
public bool IsEnabled { get; set; }
public IShowCaseCodeSnippetProvider? SnippetProvider { get; set; }
}
```
该 options 作为 `GalleryBaseOptions` 的子配置存在。未设置 Provider 或 `IsEnabled=false` 时,`ShowCaseItem` 不显示源码按钮。
## 5. 交互与状态模型
源码展示的核心状态属于 Drawer Host,不属于每个 `ShowCaseItem`。
状态:
| 状态 | Owner | 说明 |
|---|---|---|
| `IsCodeActionVisible` | `ShowCaseItem` | 当前 item 是否显示查看源码按钮 |
| `CurrentKey` | Drawer Host | 当前打开的源码 key |
| `IsOpen` | Drawer Host | Drawer 是否打开 |
| `IsLoading` | Drawer | 是否正在解析片段 |
| `SelectedSnippetIndex` | Drawer | 当前标签页 |
| `LoadedEditors` | Drawer | 已创建的 editor 缓存 |
打开流程:
```text
user clicks ShowCaseItem code button
-> ShowCaseItem creates ShowCaseCodeSnippetKey
-> request bubbles to nearest GalleryShowCaseCodeDrawerHost
-> host opens Drawer with title and loading state
-> host asks provider for ShowCaseCodeSnippetGroup
-> Drawer renders tabs
-> first tab creates GalleryCodeViewer
```
关闭流程:
```text
user closes Drawer
-> Drawer clears current group
-> disposes created GalleryCodeViewer instances
-> releases TextMate installations
-> keeps ShowCaseItem demo content untouched
```
切换标签流程:
```text
user selects tab
-> Drawer resolves snippet
-> creates GalleryCodeViewer for that tab if needed
-> existing editors stay cached while Drawer remains open
```
错误状态:
- Provider 查不到片段:Drawer 显示空状态,并提供当前 key 的调试信息。
- Snippet group 无标签:Drawer 显示空状态。
- 编辑器初始化失败:Drawer 显示纯文本 fallback,不吞掉错误上下文。
## 6. 视觉与主题模型
源码展示使用 AtomUI Drawer 承载,使用 AvaloniaEdit 作为只读代码查看器。Drawer 位于 Gallery Shell 级浮层,不参与 `ShowCasePanel` Masonry 布局。`GalleryShowCaseCodeDrawerHost` 必须包住 Shell 根布局,让 Drawer mask 覆盖导航区和内容区;不要只挂在右侧内容列上。
视觉结构:
```text
GalleryShowCaseCodeDrawerHost
ContentPresenter(page content)
Drawer
Header
Title
Copy current tab
Copy all
Close
Body
TabStrip
ContentControl
GalleryCodeViewer
```
主题规则:
- Drawer 背景、边框、标题、工具条间距使用 AtomUI SharedToken 和 GalleryBase Token。
- `GalleryCodeViewer` 使用等宽字体,默认字体族为 `Cascadia Code, Consolas, Menlo, Monospace`。
- 代码编辑器 TextMate theme 必须跟随当前主题模式:
- light 使用 TextMate `LightPlus`
- dark 使用 TextMate `DarkPlus`
- 当 `ActualThemeVariant` 是 Avalonia 内置 `ThemeVariant.Light` 或 `ThemeVariant.Dark` 时,优先按该值选择 TextMate theme。
- 当 `ActualThemeVariant` 是 AtomUI 生成的自定义变体,例如 `DaybreakBlue-Dark` 或 `DaybreakBlue-Dark-Compact`,不能用 `ActualThemeVariant == ThemeVariant.Dark` 判断暗色。`GalleryCodeViewer` 必须通过 `Application.IsDarkThemeMode()` 扩展方法读取当前应用暗色模式结果。
- `GalleryCodeViewer` 暴露 `LightSyntaxTheme` 和 `DarkSyntaxTheme`,默认值分别为 `LightPlus` 和 `DarkPlus`。产品 Gallery 可以在不改 GalleryBase 源码的前提下替换为 `VisualStudioLight`、`VisualStudioDark`、`AtomOneDark`、`Dracula` 等 TextMate 内置主题。
- `GalleryCodeViewer` 只安装一次 TextMate。主题变化时调用 `TextMate.Installation.SetTheme(...)` 切换主题,不销毁 `TextEditor`、不重建 document、不中断当前滚动位置。
- `Language` 变化只更新 grammar,`ActualThemeVariant` 或 syntax theme 属性变化只更新 TextMate theme,两条路径不能互相重建。
- 已打开的 Drawer 编辑器必须监听主题变体变化作为同步信号。纯 Avalonia 主题切换可由 `Application.ActualThemeVariantChanged` 覆盖;AtomUI 主题切换必须订阅 `IThemeManager.ThemeVariantProperty`,因为它在激活主题按算法更新后触发。不要直接订阅 `IThemeManager.IsDarkThemeModeProperty`,否则会早于主题激活流程,读到旧的 `ActivatedTheme`。
- 行号默认显示。
- 编辑器只读,允许文本选择和复制。
- 长代码在 Drawer 内部滚动,不让页面滚动条承载代码阅读。
源码展示模块可以新增 GalleryBase 专属 Token,例如:
| Token | 用途 |
|---|---|
| `CodeDrawerWidth` | Drawer 默认宽度 |
| `CodeToolbarPadding` | Drawer 内工具条 padding |
| `CodeViewerMinHeight` | 代码查看器最小高度 |
| `CodeViewerMaxHeight` | 代码查看器最大高度 |
| `CodeViewerFontSize` | 代码字体大小 |
Token 是否独立成类取决于实现时是否已有合适的 GalleryBase Token 承载。新增 Token 必须进入 GalleryBase theme provider 和生成资源。
## 7. 编辑器模型
源码展示选择 AvaloniaEdit 作为代码查看器,并通过 `AvaloniaEdit.TextMate` 接入 TextMate 语法高亮。
`GalleryCodeViewer` 是 GalleryBase 对 AvaloniaEdit 的封装:
```csharp
public class GalleryCodeViewer : TemplatedControl
{
public string? CodeText { get; set; }
public string Language { get; set; }
public bool ShowLineNumbers { get; set; }
}
```
封装职责:
- 创建和配置 `TextEditor`。
- 安装 TextMate registry。
- 根据 `Language` 设置 grammar。
- 同步 AtomUI 主题到 TextMate theme。
- 在 `Application.ActualThemeVariantChanged` 和 `IThemeManager.ThemeVariantProperty` 变化时重新应用 syntax theme;暗色判断读取当前激活主题的算法结果,避免主题状态来源分叉。
- 设置只读、行号、字体、换行和滚动策略。
- dispose 时释放 TextMate installation,取消事件订阅,清空 document 引用。
`ShowCaseItem` 和 Drawer 不直接操作 AvaloniaEdit API,避免第三方编辑器细节扩散到 ShowCase 控件。
## 8. 编译期生成模型
产品 Gallery 使用 `AtomUI.Toolkits.GalleryBase.Generator` 生成源码片段 catalog。Generator 以 Analyzer 方式被产品 Gallery 引用,不作为运行时依赖。
项目归属:
| 项目 | 归属 | 说明 |
|---|---|---|
| `src/AtomUI.Toolkits.GalleryBase` | 运行时 | 定义源码查看 UI、options、key、snippet、provider 契约 |
| `src/AtomUI.Toolkits.GalleryBase.Generator` | 编译期 | 读取产品 Gallery 的源码镜像 `AdditionalFiles` 并生成 catalog |
| `controlgallery/AtomUIGallery` | 产品侧 | 准备源码镜像输入、实现 provider、注册 options |
| `tests/AtomUI.Toolkits.GalleryBase.Generator.Tests` | 编译期测试 | 覆盖 AXAML 提取、诊断和生成物稳定性 |
Generator 项目使用 `netstandard2.0`,设置 `IsRoslynComponent=true`,并通过 `TreatAsLocalProperty` 隔离 `PublishAot`、`PublishTrimmed`、`RuntimeIdentifier` 等发布属性。产品项目引用 generator 时使用 Analyzer 引用,不把 generator assembly 带入运行时发布产物。
输入:
```text
controlgallery/AtomUIGallery/ShowCases/**/*.axaml
controlgallery/AtomUIGallery/ShowCases/**/*.axaml.cs
controlgallery/AtomUIGallery/ShowCases/**/ViewModels/*.cs
```
产品项目不要把真实 ShowCase AXAML、code-behind 和 ViewModel 源文件直接注册为 `AdditionalFiles`。真实 `.axaml` / `.cs` 文件已经有 Avalonia、Compile 和 IDE 语义角色,再叠加 `AdditionalFiles` 会污染 Rider 等 IDE 的设计时模型。
产品项目应在编译前把这些源文件复制到 `$(IntermediateOutputPath)` 下的源码镜像文件,例如 `.gallerysource`,然后只把镜像文件注册为 `AdditionalFiles`,并通过 `GallerySourceOriginalPath` metadata 传递真实源文件路径。Generator 使用该 metadata 做路径分类、同组文件匹配、诊断位置和 `SourceFilePath` 输出;镜像文件路径只作为 Roslyn `AdditionalText` 的物理载体。
`GallerySourceOriginalPath` 必须通过 `CompilerVisibleItemMetadata` 暴露给 Roslyn analyzer config,并且准备镜像 `AdditionalFiles` 的 MSBuild target 必须早于 `GenerateMSBuildEditorConfigFileShouldRun` 执行。只在 `BeforeTargets="CoreCompile"` 中追加 metadata 不够,因为此时 `GeneratedMSBuildEditorConfig` 可能已经生成,Generator 会读到空 metadata,进而把 `.gallerysource` 镜像路径当成真实路径并忽略 C# 镜像文件。
Generator 只读取 `AdditionalFiles`,不通过运行时文件系统、MSBuild project model 或 assembly metadata 反向扫描源码。
### 8.1 Item 级源码切片规则
Generator 以 `ShowCaseItem` 为切片单位,而不是以整个 ShowCase 页面为单位。每个 item 的源码片段必须由 AXAML 入口和 C# 依赖闭包共同决定:
- 读取 `x:Class` 得到 `ViewTypeName`。
- 读取 `gallery:ShowCasePanel`,使用 `Name` 作为 `PanelKey`。
- 按同一 panel 内声明顺序为 `gallery:ShowCaseItem` 分配 `ItemIndex`。
- 对迁移后的 item,提取 `ShowCaseItem.DeferredContentTemplate > DataTemplate` 内部节点。
- 对未迁移 item,提取 `ShowCaseItem` 的直接内容。
- 保留 AXAML 源文件相对路径和行号范围。
- 默认生成 `Language="axaml"`、`TabTitle="AXAML"`。
- 从 item AXAML 中收集 `x:DataType`、`{Binding ...}` 根路径、事件属性、`Name` / `x:Name`。
- 事件属性只在 code-behind 中存在同名方法时才视为事件处理器;普通字符串属性不进入 C# 依赖集合。
- 事件属性识别必须覆盖 Avalonia 常见事件入口,例如 `Click`、`Loaded`、`Unloaded`、`AttachedToVisualTree`、`DetachedFromVisualTree`、键盘事件、指针事件和 `Changed` / `Opened` / `Closed` 等后缀事件。
- Binding 根路径用于选择 ViewModel 成员,例如 `SelectedColorPreset.Name` 选择 `SelectedColorPreset`,不把 `Name` 当成独立 ViewModel 依赖。
- `Name` / `x:Name` 只作为 code-behind 依赖辅助输入,用于识别事件处理器或 helper 中访问的示例控件,不单独生成片段。
### 8.2 同组文件定位
ShowCase 源文件通常按以下结构组织:
```text
ShowCases/<Category>/<Control>/
Views/<Control>ShowCase.axaml
Views/<Control>ShowCase.axaml.cs
ViewModels/<Control>ViewModel.cs
```
Generator 按以下顺序定位同组文件:
1. code-behind 优先匹配 AXAML 同路径同名 `.axaml.cs`。
2. 如果同路径匹配失败,通过 `x:Class` 的类型名和 namespace 在 AdditionalFiles 中兜底匹配 partial class。
3. ViewModel 优先读取 item 或上层 `DataTemplate x:DataType`。
4. 如果 `x:DataType` 缺失,通过 code-behind 基类 `GalleryReactiveUserControl<TViewModel>` 推断。
5. 如果泛型推断失败,再按同组 `ViewModels/*ViewModel.cs` 约定兜底;兜底只能在唯一匹配时生效。
找不到 code-behind 或 ViewModel 不阻断 AXAML 片段生成。Generator 只跳过对应 C# 标签。
### 8.3 C# 成员切片规则
Generator 使用 Roslyn C# syntax tree 对 code-behind 和 ViewModel 做成员级切片。切片目标是“当前示例阅读完整”,不是“单片段可独立编译”。
Code-behind 片段包括:
- 当前 item AXAML 直接引用的事件处理器方法。
- 事件处理器调用的同类型 helper 方法。
- 这些方法访问的字段、属性和嵌套类型。
- 这些方法创建或引用的同文件局部记录类型、枚举或小型 DTO。
- 片段需要的 `using`、alias using、namespace 和包含类型声明。
ViewModel 片段包括:
- 当前 item Binding 根路径对应的 public / internal 属性。
- 属性使用的 backing field。
- 属性 setter 或 getter 调用的同类型 helper 方法。
- code-behind 事件转发到 ViewModel 的 public / internal 方法。
- 相关属性或方法依赖的同文件 record、enum、小型 DTO 和构造初始化。
- 构造函数中只与已选成员相关的初始化语句;如果无法安全拆分构造函数,保留完整构造函数但不引入只服务旧 API/Token metadata 的无关 lazy data 方法。
- 片段需要的 `using`、namespace 和包含类型声明。
以下内容默认不进入当前 item 的 C# 片段:
- 只服务旧 API/Token metadata 的初始化方法。
- 其他 `ShowCaseItem` 专用事件处理器、Binding 属性和 helper。
- 仅服务页面 Shell、Scenario tabs、路由、导航、语言刷新或 DataGrid lazy loading 的成员,除非当前 item AXAML 直接引用。
- 整个 `.axaml.cs` 或整个 ViewModel 文件。
### 8.4 依赖闭包与降级规则
成员切片采用有界递归:
```text
AXAML references
-> direct C# members
-> same-file member references
-> backing fields / helper methods / small related types
-> stop at framework, external assembly, unrelated ShowCase members
```
递归只跨同一 code-behind 文件和同一 ViewModel 文件,不跨产品项目中任意 C# 文件扩散。跨文件类型只保留引用表达式,不自动展开目标源码;后续如确实需要,可通过显式 SourceKey 或 include 规则扩展。
当 generator 无法可靠判断某个成员是否相关时,按以下顺序降级:
1. 保留当前 item 的 AXAML。
2. 保留能确定直接相关的 C# 成员。
3. 跳过不确定的 C# 标签,不生成空标签。
4. 在严格模式下报告诊断;默认模式不因 C# 切片不完整产生 warning 噪声。
严格模式由产品项目 MSBuild 属性控制,例如:
```xml
<GallerySourceCodeDisplayStrict>true</GallerySourceCodeDisplayStrict>
```
默认非严格模式适合日常开发和迁移期;严格模式适合 release 前检查源码展示完整性。
### 8.5 生成片段顺序
输出:
```csharp
internal static partial class ShowCaseCodeSnippetCatalog
{
public static bool TryGetSnippetGroup(
string viewTypeName,
string panelKey,
int itemIndex,
string? sourceKey,
out ShowCaseCodeSnippetGroup group)
{
// generated switch
}
}
```
每个 `ShowCaseCodeSnippetGroup.Snippets` 使用稳定顺序:
1. `AXAML`
2. `Code-behind`
3. `ViewModel`
没有内容的 C# 标签不生成。`AXAML` 是唯一默认必选标签;如果 item 没有可展示 AXAML 内容,则整个 group 不生成。
生成代码应使用显式 `switch` 或稳定表,不进行运行时文件系统读取、assembly scan 或 XML 解析。
源码片段数据只放在产品 Gallery 编译产物内的 generated C# catalog 中:
- 不生成运行时 JSON。
- 不生成单独源码资源包。
- 不把源码片段嵌入 GalleryBase runtime assembly。
- 不从安装目录、repo 工作区或 `avares://` 资源读取源码文件。
- `Text` 以 C# 字符串字面量或等价静态数据写入 generated code。
- `SourceFilePath` 使用 repo 相对路径,只用于调试、空状态和测试断言。
生成物命名应位于产品 Gallery namespace 下,例如 `AtomUIGallery.Generated.ShowCaseCodeSnippetCatalog`。GalleryBase 只认识 provider 契约,不认识生成物名称。
Generator 诊断遵循 [AtomUI 编译期诊断规范](../../engineering/development/compiler-diagnostics-guidelines.md),诊断 ID 使用 `ATOMUIGEN` 领域前缀并登记到诊断注册表。源码展示 generator 已使用 `ATOMUIGEN101` 覆盖默认源码匹配缺少 panel key 的场景,其他诊断按同一领域前缀扩展。
| 场景 | Severity | 行为 |
|---|---|---|
| AXAML 无法解析 | Warning | 跳过当前文件,提示文件路径和解析错误摘要 |
| 根节点缺少 `x:Class` | Warning | 跳过当前文件,提示补齐页面类型 |
| `ShowCasePanel` 缺少 `Name` 且没有显式 `SourceKey` | Warning | 跳过该 panel 的默认 key 生成 |
| `ShowCaseItem` 无法定位可展示内容 | Warning | 为该 item 不生成 snippet,并保留其他 item |
| 同一 key 产生重复 snippet group | Warning | 后出现的 key 不覆盖先出现的 key,提示重复来源 |
| 事件处理器名称在 code-behind 中不存在 | Hidden / Warning in strict mode | 默认只跳过该 handler;严格模式提示补齐 handler 或修正 AXAML |
| `x:DataType` 或推断 ViewModel 无法解析 | Hidden / Warning in strict mode | 默认只生成 AXAML 和可用 code-behind;严格模式提示补齐 `x:DataType` 或 ViewModel 约定 |
| Binding 根路径在 ViewModel 中找不到成员 | Hidden / Warning in strict mode | 默认跳过该 ViewModel 成员;严格模式提示 Binding 与 VM 不一致 |
| C# syntax tree 无法解析 | Warning | 跳过对应 C# 文件,保留其他文件片段 |
默认诊断策略偏低噪声:AXAML key 和 group 生成问题使用 Warning,因为它们会导致源码入口无法匹配;C# 切片不完整默认不阻断 ShowCase 源码展示,因为 AXAML 片段仍然可用。严格模式只在产品显式开启时提高 C# 关联问题的可见性。
## 9. 产品集成模型
具体产品 Gallery 负责接入生成 catalog:
```csharp
internal sealed class AtomUIGalleryShowCaseCodeSnippetProvider
: IShowCaseCodeSnippetProvider
{
public bool TryGetSnippetGroup(
ShowCaseCodeSnippetKey key,
out ShowCaseCodeSnippetGroup group)
{
return ShowCaseCodeSnippetCatalog.TryGetSnippetGroup(
key.ViewTypeName,
key.PanelKey,
key.ItemIndex,
key.SourceKey,
out group);
}
}
```
产品模块在 `UseGalleryBase(...)` 配置阶段注册:
```csharp
options.SourceCodeDisplay.IsEnabled = true;
options.SourceCodeDisplay.SnippetProvider =
new AtomUIGalleryShowCaseCodeSnippetProvider();
```
GalleryBase 不直接寻找 `ShowCaseCodeSnippetCatalog`,也不约定产品 catalog 的 namespace。产品侧 provider 是唯一桥接点。
## 10. 生命周期与资源边界
源码展示模块必须保持 ShowCase 页面延迟创建语义:
- `ShowCaseItem` 显示源码按钮不能触发 `MaterializeDeferredContent()`。
- Drawer 打开不能创建未 materialize 的示例控件树。
- Snippet 查询只读取生成 catalog,不访问当前 demo visual tree。
- Drawer 关闭时释放 editor、TextMate installation、事件订阅和 clipboard 状态。
- Drawer Host detach 时关闭 Drawer 并释放当前 group。
- Provider 不持有 ShowCase 页面或 `ShowCaseItem` 实例引用。
Clipboard 操作通过当前 `TopLevel` 获取剪贴板服务。没有可用 clipboard 时,复制按钮进入禁用或错误提示状态,不抛出未处理异常。
## 11. AOT、裁剪与 Browser 边界
源码展示模块必须满足 Gallery AOT 和 Browser 发布要求:
- 运行时不使用 `Assembly.GetTypes()`、`Activator.CreateInstance(...)`、反射字段扫描或运行时文件扫描。
- Source Generator 输出的 catalog 是普通 C# 代码,trimmer 可静态分析。
- Generator 项目使用 `netstandard2.0`,并隔离 `PublishAot`、`PublishTrimmed`、`RuntimeIdentifier` 等发布属性。
- Generator 的 C# 分析只在编译期使用 Roslyn syntax tree;运行时不保存 Roslyn 类型、语义模型或源码文件索引。
- TextMate grammar 资源必须通过包资源路径加载,并在 Browser 发布中验证。
- Drawer 和 editor 均按用户操作延迟创建,Browser 首屏不初始化 AvaloniaEdit。
- 生成 catalog 不应无限增长到包含整页或整文件源码;只保存与示例直接相关的 AXAML、code-behind 和 ViewModel 切片。
涉及 NativeAOT 发布时,除普通测试外必须执行 Gallery NativeAOT publish 验证。
## 12. 兼容性不变量
实现源码展示模块时必须保持以下不变量:
- 现有 ShowCase 示例视觉树不因源码功能变化而改变。
- 现有 `ShowCasePanel + ShowCaseItem` 双层延迟创建规则不变。
- `ShowCaseItem` 标题、描述、Badge、占整行和 deferred placeholder 语义不变。
- 未配置 Provider 的产品 Gallery 不显示源码按钮,也不产生运行时错误。
- Provider 查不到片段时不会阻断 ShowCase 页面展示。
- Drawer 始终作为浮层显示,不参与 Masonry 布局测量。
- GalleryBase 不引用产品项目。
- Source Generator 不生成产品运行时依赖之外的反射路径。
- Source Generator 生成的 `Code-behind` / `ViewModel` 标签必须来自 item 级依赖分析,不得退化为整文件展示。
- 当前 item 没有关联 C# 代码时不生成空 tab;Drawer 只显示实际存在的片段。
- Snapshot 测试中的 demo 内容提取规则仍以 demo XAML 为准,不把源码展示 UI 混入示例内容 snapshot。
## 13. 文档导航与验证策略
相关文档:
- [AtomUI.Toolkits.GalleryBase 模块概览](overview.md)
- [AtomUI.Toolkits.GalleryBase 设计文档](architecture.md)
- [GalleryBase ShowCase 控件设计](showcase-controls.md)
- [Gallery ShowCase Design Pattern](../../gallery/authoring/gallery-showcase-design-pattern.md)
- [AtomUI AOT 编程规范](../../engineering/development/aot-programming-guidelines.md)
- [AtomUI 编译期诊断规范](../../engineering/development/compiler-diagnostics-guidelines.md)
验证要求:
| 层级 | 验证 |
|---|---|
| Generator | generator tests 覆盖 AXAML 提取、panel key、item index、line range、空输入、事件 handler 切片、Binding 到 ViewModel 成员切片和无 C# 关联时不生成空 tab |
| Generator strict mode | generator tests 覆盖找不到事件处理器、找不到 ViewModel、Binding 成员缺失时默认低噪声和 strict warning 两种行为 |
| GalleryBase runtime | Drawer 打开/关闭、editor lazy 创建、provider 缺失、clipboard fallback |
| ShowCase 结构 | 全量迁移页面每个 `ShowCaseItem` 可匹配源码片段;典型页面覆盖 `AXAML + Code-behind`、`AXAML + ViewModel`、`AXAML + Code-behind + ViewModel` 三类组合 |
| AOT | Gallery NativeAOT publish 验证生成 catalog 和编辑器依赖 |
| 文档 | `git diff --check`,并确认本文件从 GalleryBase 模块入口可达 |