统一文档目录的入口文件名为 `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` 保留原文 并加取代说明,其余结论继续有效。
28 KiB
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 |
依赖方向:
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 示例:
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:
public sealed record ShowCaseCodeSnippetGroup(
string Title,
IReadOnlyList<ShowCaseCodeSnippet> Snippets);
Title 用于 Drawer 标题。默认使用 ShowCaseItem.Title 的当前文本值;Provider 返回的标题只作为 fallback。
4.3 Snippet
每个标签页对应一个 ShowCaseCodeSnippet:
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 契约:
public interface IShowCaseCodeSnippetProvider
{
bool TryGetSnippetGroup(
ShowCaseCodeSnippetKey key,
out ShowCaseCodeSnippetGroup group);
}
Provider 注册在 GalleryBase 配置中:
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 缓存 |
打开流程:
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
关闭流程:
user closes Drawer
-> Drawer clears current group
-> disposes created GalleryCodeViewer instances
-> releases TextMate installations
-> keeps ShowCaseItem demo content untouched
切换标签流程:
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 覆盖导航区和内容区;不要只挂在右侧内容列上。
视觉结构:
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
- light 使用 TextMate
- 当
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 的封装:
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 带入运行时发布产物。
输入:
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 源文件通常按以下结构组织:
ShowCases/<Category>/<Control>/
Views/<Control>ShowCase.axaml
Views/<Control>ShowCase.axaml.cs
ViewModels/<Control>ViewModel.cs
Generator 按以下顺序定位同组文件:
- code-behind 优先匹配 AXAML 同路径同名
.axaml.cs。 - 如果同路径匹配失败,通过
x:Class的类型名和 namespace 在 AdditionalFiles 中兜底匹配 partial class。 - ViewModel 优先读取 item 或上层
DataTemplate x:DataType。 - 如果
x:DataType缺失,通过 code-behind 基类GalleryReactiveUserControl<TViewModel>推断。 - 如果泛型推断失败,再按同组
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 依赖闭包与降级规则
成员切片采用有界递归:
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 无法可靠判断某个成员是否相关时,按以下顺序降级:
- 保留当前 item 的 AXAML。
- 保留能确定直接相关的 C# 成员。
- 跳过不确定的 C# 标签,不生成空标签。
- 在严格模式下报告诊断;默认模式不因 C# 切片不完整产生 warning 噪声。
严格模式由产品项目 MSBuild 属性控制,例如:
<GallerySourceCodeDisplayStrict>true</GallerySourceCodeDisplayStrict>
默认非严格模式适合日常开发和迁移期;严格模式适合 release 前检查源码展示完整性。
8.5 生成片段顺序
输出:
internal static partial class ShowCaseCodeSnippetCatalog
{
public static bool TryGetSnippetGroup(
string viewTypeName,
string panelKey,
int itemIndex,
string? sourceKey,
out ShowCaseCodeSnippetGroup group)
{
// generated switch
}
}
每个 ShowCaseCodeSnippetGroup.Snippets 使用稳定顺序:
AXAMLCode-behindViewModel
没有内容的 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 编译期诊断规范,诊断 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:
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(...) 配置阶段注册:
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 模块概览
- AtomUI.Toolkits.GalleryBase 设计文档
- GalleryBase ShowCase 控件设计
- Gallery ShowCase Design Pattern
- AtomUI AOT 编程规范
- AtomUI 编译期诊断规范
验证要求:
| 层级 | 验证 |
|---|---|
| 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 模块入口可达 |