From 54fd8a0cf343ca3d85e9544997c6969df6cdefda Mon Sep 17 00:00:00 2001 From: chinboy Date: Fri, 21 Aug 2026 22:27:26 +0800 Subject: [PATCH] feat(Semantic): add Space semantic parts and align Ant Design demo --- .../SpaceShowCaseLangResourceKind.cs | 13 +- .../Layout/Space/Localization/en-US.xlf | 55 ++++ .../Layout/Space/Localization/pt-BR.xlf | 66 ++++ .../Layout/Space/Localization/zh-CN.xlf | 66 ++++ .../Layout/Space/Localization/zh-TW.xlf | 66 ++++ .../Layout/Space/Views/SpaceShowCase.axaml | 109 +++++- .../generated/llms/controls/space/index-cn.md | 21 +- .../llms/controls/space/semantic-cn.md | 26 +- docs/AI/generated/llms/llms-full-cn.txt | 21 +- docs/AI/generated/llms/llms-semantic-cn.md | 26 +- .../desktop/layout/space/changelog.md | 25 ++ .../desktop/layout/space/implementation.md | 7 +- .../controls/desktop/layout/space/overview.md | 12 +- .../desktop/layout/space/semantic-part.md | 86 +++++ .../Space/Space.SemanticParts.cs | 24 ++ src/AtomUI.Desktop.Controls/Space/Space.cs | 214 +++++++++++- .../Themes/SemanticPartPreviewTheme.axaml | 7 +- .../Space/SpaceRootFrameTests.cs | 113 +++++++ .../Space/SpaceSemanticPartTests.cs | 266 +++++++++++++++ .../Localization/CatalogMemberOrder.baseline | 2 +- .../GalleryCatalogCoverageTests.cs | 2 +- .../ShowCases/SpaceShowCaseExamples.snapshot | 4 +- .../ShowCases/SpaceShowCasePageTests.cs | 309 +++++++++++++++++- 23 files changed, 1480 insertions(+), 60 deletions(-) create mode 100644 docs/controls/desktop/layout/space/semantic-part.md create mode 100644 src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs create mode 100644 tests/AtomUI.Desktop.Controls.Tests/Space/SpaceRootFrameTests.cs create mode 100644 tests/AtomUI.Desktop.Controls.Tests/Space/SpaceSemanticPartTests.cs diff --git a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/SpaceShowCaseLangResourceKind.cs b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/SpaceShowCaseLangResourceKind.cs index 3fbd882f2..8d7a39815 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/SpaceShowCaseLangResourceKind.cs +++ b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/SpaceShowCaseLangResourceKind.cs @@ -110,5 +110,16 @@ public enum SpaceShowCaseLangResourceKind P2ToolTipTipHeart, P2ToolTipTipShare, P2ToolTipTipDownload, - P2ToolTipTipTooltip + P2ToolTipTipTooltip, + P2LargeSpaceButtonN1, + P2LargeSpaceButtonN2, + P2LargeSpaceButtonN3, + P2StyledButtonN1, + P2StyledButtonN2, + P2StyledButtonN3, + SemanticRootDescription, + SemanticItemDescription, + SemanticSeparatorDescription, + SemanticPartStyleTitle, + SemanticPartStyleDescription } diff --git a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/en-US.xlf b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/en-US.xlf index e247b7c89..919d14ef0 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/en-US.xlf +++ b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/en-US.xlf @@ -301,6 +301,21 @@ Zhong Hua Men + + + Large Space Button 1 + + + + + Large Space Button 2 + + + + + Large Space Button 3 + + Ok @@ -361,6 +376,21 @@ End time + + + Styled Button 1 + + + + + Styled Button 2 + + + + + Styled Button 3 + + Block @@ -481,6 +511,31 @@ Size + + + Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment + + + + + Mirrors the Ant Design style-class demo: customize the root frame, item background and separator color through generated Semantic Part styles, and outline the root with a dashed border. + + + + + Custom Semantic Part styling + + + + + Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles + + + + + Separator element with divider styling + + Use size to set the spacing. Three sizes are preset: small, middle, and large. You can also customize the spacing. If size is not set, the spacing is small. diff --git a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/pt-BR.xlf b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/pt-BR.xlf index bda11fafe..98643599c 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/pt-BR.xlf +++ b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/pt-BR.xlf @@ -361,6 +361,24 @@ Zhong Hua Men + + + Large Space Button 1 + Botão com Espaçamento Grande 1 + + + + + Large Space Button 2 + Botão com Espaçamento Grande 2 + + + + + Large Space Button 3 + Botão com Espaçamento Grande 3 + + Ok @@ -433,6 +451,24 @@ Fim hora + + + Styled Button 1 + Botão Estilizado 1 + + + + + Styled Button 2 + Botão Estilizado 2 + + + + + Styled Button 3 + Botão Estilizado 3 + + Block @@ -577,6 +613,36 @@ Tamanho + + + Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment + Elemento de item encapsulado com layout e estilos de espaçamento, fornecendo um invólucro para cada elemento filho para alinhamento em linha + + + + + Mirrors the Ant Design style-class demo: customize the root frame, item background and separator color through generated Semantic Part styles, and outline the root with a dashed border. + Espelha o demo style-class do Ant Design: personalize a moldura do root, o fundo do item e a cor do separador por meio dos estilos de Semantic Part gerados e contorne o root com uma borda tracejada. + + + + + Custom Semantic Part styling + Personalizar estilo de Semantic Part + + + + + Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles + Elemento raiz com layout flex, configurações de espaçamento, alinhamento, quebra e outros estilos básicos do contêiner de espaçamento + + + + + Separator element with divider styling + Elemento separador com estilo de divisor + + Use size to set the spacing. Three sizes are preset: small, middle, and large. You can also customize the spacing. If size is not set, the spacing is small. diff --git a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-CN.xlf b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-CN.xlf index 2b7d926a4..1cf455a7c 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-CN.xlf +++ b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-CN.xlf @@ -361,6 +361,24 @@ 中华门 + + + Large Space Button 1 + 大间距按钮 1 + + + + + Large Space Button 2 + 大间距按钮 2 + + + + + Large Space Button 3 + 大间距按钮 3 + + Ok @@ -433,6 +451,24 @@ 结束时间 + + + Styled Button 1 + 样式化按钮 1 + + + + + Styled Button 2 + 样式化按钮 2 + + + + + Styled Button 3 + 样式化按钮 3 + + Block @@ -577,6 +613,36 @@ 尺寸 + + + Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment + 包裹的子组件,包含间距项的布局和样式,为每个子元素提供包装用于内联对齐 + + + + + Mirrors the Ant Design style-class demo: customize the root frame, item background and separator color through generated Semantic Part styles, and outline the root with a dashed border. + 对齐 Ant Design style-class 示例:通过生成的 Semantic Part 样式自定义 root 边框、item 背景与 separator 颜色,并为 root 添加虚线边框。 + + + + + Custom Semantic Part styling + 自定义 Semantic Part 样式 + + + + + Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles + 根元素,包含 flex 布局、间隙设置、对齐方式、换行等间距容器的基础样式 + + + + + Separator element with divider styling + 分隔符,包含分隔元素的样式 + + Use size to set the spacing. Three sizes are preset: small, middle, and large. You can also customize the spacing. If size is not set, the spacing is small. diff --git a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-TW.xlf b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-TW.xlf index d0c45e89a..0e7f5f15f 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-TW.xlf +++ b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-TW.xlf @@ -361,6 +361,24 @@ 中華門 + + + Large Space Button 1 + 大間距按鈕 1 + + + + + Large Space Button 2 + 大間距按鈕 2 + + + + + Large Space Button 3 + 大間距按鈕 3 + + Ok @@ -433,6 +451,24 @@ 結束時間 + + + Styled Button 1 + 樣式化按鈕 1 + + + + + Styled Button 2 + 樣式化按鈕 2 + + + + + Styled Button 3 + 樣式化按鈕 3 + + Block @@ -577,6 +613,36 @@ 尺寸 + + + Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment + 包裹的子元件,包含間距項的佈局和樣式,為每個子元素提供包裝用於內聯對齊 + + + + + Mirrors the Ant Design style-class demo: customize the root frame, item background and separator color through generated Semantic Part styles, and outline the root with a dashed border. + 對齊 Ant Design style-class 範例:透過生成的 Semantic Part 樣式自訂 root 邊框、item 背景與 separator 顏色,並為 root 加上虛線邊框。 + + + + + Custom Semantic Part styling + 自定義 Semantic Part 樣式 + + + + + Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles + 根元素,包含 flex 佈局、間隙設定、對齊方式、換行等間距容器的基礎樣式 + + + + + Separator element with divider styling + 分隔符,包含分隔元素的樣式 + + Use size to set the spacing. Three sizes are preset: small, middle, and large. You can also customize the spacing. If size is not set, the spacing is small. diff --git a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml index 0836b4de4..366af077d 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml +++ b/controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml @@ -19,11 +19,19 @@ + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/docs/AI/generated/llms/controls/space/index-cn.md b/docs/AI/generated/llms/controls/space/index-cn.md index 508cfa3bd..4cbdf9961 100644 --- a/docs/AI/generated/llms/controls/space/index-cn.md +++ b/docs/AI/generated/llms/controls/space/index-cn.md @@ -8,6 +8,8 @@ Space 是 AtomUI 桌面控件体系中的间距布局控件,用于在一组子 Space 不负责复杂响应式排版、滚动容器或数据虚拟化列表。这些职责应由业务层、组合控件或更专用的 AtomUI 控件承担。 +Space 的公开语义区域使用 `root`、`item` 和 `separator` 三个 Semantic Part;完整契约见 [Space Semantic Part 契约](semantic-part.md)。 + 主要源码入口: - `src/AtomUI.Desktop.Controls/Space` @@ -41,10 +43,10 @@ Space 的公共契约由 public/protected 类型成员、Avalonia 属性、事 | 契约组 | 代表成员 | 维护含义 | | --- | --- | --- | -| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment` | 定义控件展示内容、输入数据、模板或业务对象入口。 | +| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment`、`SplitTemplate` | 定义控件展示内容、输入数据、模板或业务对象入口。 | | 选择与集合 | `PositionIndex` | 维护选择、展开、过滤、分页、分组或集合状态。 | | 交互与状态 | `IsUsedInCompactSpace`、`Status` | 表达用户可观察状态、可用性、清除、加载或反馈语义。 | -| 视觉与布局 | `CompactSpaceOrientation`、`LineSpacing`、`Orientation`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | +| 视觉与布局 | `Background`、`BorderBrush`、`BorderDashArray`、`BorderDashOffset`、`BorderThickness`、`CompactSpaceOrientation`、`CornerRadius`、`LineSpacing`、`Orientation`、`Padding`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | 当前没有抽取到控件专属 public 事件;交互通知主要来自继承事件、命令或 Gallery 可观察状态。 @@ -74,7 +76,7 @@ Space 的公共契约由 public/protected 类型成员、Avalonia 属性、事 ### 垂直间距 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:74` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:119` Gallery key:`ExamplesContent` / item `1` @@ -105,7 +107,7 @@ Gallery key:`ExamplesContent` / item `1` ### 间距尺寸 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:105` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:150` Gallery key:`ExamplesContent` / item `2` @@ -140,7 +142,7 @@ Gallery key:`ExamplesContent` / item `2` ### 对齐 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:140` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:185` Gallery key:`ExamplesContent` / item `3` @@ -180,7 +182,7 @@ Gallery key:`ExamplesContent` / item `3` ### 自动换行 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:180` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:225` Gallery key:`ExamplesContent` / item `4` @@ -234,6 +236,10 @@ Space 的视觉模型由控件模板、ControlTheme、SharedToken 和必要的 Space 使用 `SpaceToken` 作为控件 Token scope。Token 只表达组件视觉语义,不承载 collection/filter、visual option 运行时状态。 +`Space` 本体没有独立的 `SpaceTheme.axaml`:root frame 由控件自身在 `Render` 中绘制(复用 `BorderRenderHelper`), +root 边框、背景、内边距与虚线由 `Space` 自有的 root frame 样式属性表达,定制边界见 [Space Semantic Part 契约](semantic-part.md) +第 3 节。主题文件只覆盖 `CompactSpace` / `CompactSpaceAddOn` 的模板与状态视觉。 + 主题维护规则: - 不删除或重命名已经稳定的 ControlTheme key、template part、伪类和资源 key。 @@ -277,6 +283,7 @@ Space Token 只表达组件级视觉变量,例如尺寸、间距、颜色、 - `src/AtomUI.Desktop.Controls/Space/ICompactSpaceAware.cs` - `src/AtomUI.Desktop.Controls/Space/InvalidSpaceFillerUsageException.cs` - `src/AtomUI.Desktop.Controls/Space/Space.cs` +- `src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs` - `src/AtomUI.Desktop.Controls/Space/SpaceToken.cs` - `src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceAddOnTheme.axaml` - `src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceTheme.axaml` @@ -284,6 +291,7 @@ Space Token 只表达组件级视觉变量,例如尺寸、间距、颜色、 职责边界: - 控件主文件保留 public/protected API、Avalonia 属性注册、事件和主要生命周期入口。 +- 语义声明文件只承载 `Space` 的 runtime semantic contract,不写模板节点或主题样式。 - Theme 文件负责静态视觉结构、template part、selector 和资源绑定。 - Token 文件只提供组件视觉变量,不保存实例状态。 - Gallery 文件只展示用法和示例,不作为运行时逻辑 owner。 @@ -292,6 +300,7 @@ Space Token 只表达组件级视觉变量,例如尺寸、间距、颜色、 - 源设计文档:`docs/controls/desktop/layout/space/overview.md` - 实现文档:`docs/controls/desktop/layout/space/implementation.md` +- Semantic Part 文档:`docs/controls/desktop/layout/space/semantic-part.md` - Token 文档:`docs/controls/desktop/layout/space/token.md` - 变更记录:`docs/controls/desktop/layout/space/changelog.md` - 语义结构:`./semantic-cn.md` diff --git a/docs/AI/generated/llms/controls/space/semantic-cn.md b/docs/AI/generated/llms/controls/space/semantic-cn.md index 76d06a3f4..1c0f5f500 100644 --- a/docs/AI/generated/llms/controls/space/semantic-cn.md +++ b/docs/AI/generated/llms/controls/space/semantic-cn.md @@ -4,12 +4,18 @@ ## Semantic Parts -| Part | AtomUI 节点 | 职责 | 相关 API | 相关 Token | 稳定性 | -| --- | --- | --- | --- | --- | --- | -| `root` | `Space` | 布局控件根语义区域,承载布局 public API、尺寸和主题入口。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | -| `container` | `布局容器` | 组织子元素、间距、断点、对齐或分割状态。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | -| `item` | `布局项` | 承载子内容、占位、跨度、排序或尺寸约束。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | -| `theme` | `主题区域` | 连接 SharedToken、布局主题资源和 Gallery 可观察样式。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | +| Part | AtomUI 节点 | Selector | ContractType | Cardinality | Customization | CrossVisualRoot | RuntimeCreated | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `root` | `Space` owner | owner | `Space` | `Single` | `Root` | `false` | `false` | +| `item` | 直接子项 | `> .semantic-item` | `Control` | `Multiple` | `Selector` | `false` | `true` | +| `separator` | 直接分隔项 | `> .semantic-separator` | `Control` | `Multiple` | `Selector` | `false` | `true` | + +`root` 不声明 `.semantic-root` marker,也不生成独立 Style type。`item` 和 `separator` 都是运行时创建的语义标记: +`item` 挂在 `Space.Children` 的每个直接子项上,`separator` 挂在由 `SplitTemplate` 构建并插入相邻子项之间的 +分隔控件上。`separator` 的数量始终等于 `Children.Count - 1`,当 `SplitTemplate` 为空时不创建任何分隔项。 + +`Space` 没有独立的 `SpaceTheme.axaml`,因此内置主题不依赖静态 `.semantic-*` marker;语义契约完全由 runtime marker +与生成的 `Style` 类型表达。 ## Abstract AXAML Structure @@ -23,10 +29,10 @@ | 契约组 | 代表成员 | 维护含义 | | --- | --- | --- | -| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment` | 定义控件展示内容、输入数据、模板或业务对象入口。 | +| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment`、`SplitTemplate` | 定义控件展示内容、输入数据、模板或业务对象入口。 | | 选择与集合 | `PositionIndex` | 维护选择、展开、过滤、分页、分组或集合状态。 | | 交互与状态 | `IsUsedInCompactSpace`、`Status` | 表达用户可观察状态、可用性、清除、加载或反馈语义。 | -| 视觉与布局 | `CompactSpaceOrientation`、`LineSpacing`、`Orientation`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | +| 视觉与布局 | `Background`、`BorderBrush`、`BorderDashArray`、`BorderDashOffset`、`BorderThickness`、`CompactSpaceOrientation`、`CornerRadius`、`LineSpacing`、`Orientation`、`Padding`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | ## Pseudo Classes @@ -63,6 +69,10 @@ Space 的视觉模型由控件模板、ControlTheme、SharedToken 和必要的 Space 使用 `SpaceToken` 作为控件 Token scope。Token 只表达组件视觉语义,不承载 collection/filter、visual option 运行时状态。 +`Space` 本体没有独立的 `SpaceTheme.axaml`:root frame 由控件自身在 `Render` 中绘制(复用 `BorderRenderHelper`), +root 边框、背景、内边距与虚线由 `Space` 自有的 root frame 样式属性表达,定制边界见 [Space Semantic Part 契约](semantic-part.md) +第 3 节。主题文件只覆盖 `CompactSpace` / `CompactSpaceAddOn` 的模板与状态视觉。 + 主题维护规则: - 不删除或重命名已经稳定的 ControlTheme key、template part、伪类和资源 key。 diff --git a/docs/AI/generated/llms/llms-full-cn.txt b/docs/AI/generated/llms/llms-full-cn.txt index 743633df0..35bc9434c 100644 --- a/docs/AI/generated/llms/llms-full-cn.txt +++ b/docs/AI/generated/llms/llms-full-cn.txt @@ -2220,6 +2220,8 @@ Space 是 AtomUI 桌面控件体系中的间距布局控件,用于在一组子 Space 不负责复杂响应式排版、滚动容器或数据虚拟化列表。这些职责应由业务层、组合控件或更专用的 AtomUI 控件承担。 +Space 的公开语义区域使用 `root`、`item` 和 `separator` 三个 Semantic Part;完整契约见 [Space Semantic Part 契约](semantic-part.md)。 + 主要源码入口: - `src/AtomUI.Desktop.Controls/Space` @@ -2253,10 +2255,10 @@ Space 的公共契约由 public/protected 类型成员、Avalonia 属性、事 | 契约组 | 代表成员 | 维护含义 | | --- | --- | --- | -| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment` | 定义控件展示内容、输入数据、模板或业务对象入口。 | +| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment`、`SplitTemplate` | 定义控件展示内容、输入数据、模板或业务对象入口。 | | 选择与集合 | `PositionIndex` | 维护选择、展开、过滤、分页、分组或集合状态。 | | 交互与状态 | `IsUsedInCompactSpace`、`Status` | 表达用户可观察状态、可用性、清除、加载或反馈语义。 | -| 视觉与布局 | `CompactSpaceOrientation`、`LineSpacing`、`Orientation`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | +| 视觉与布局 | `Background`、`BorderBrush`、`BorderDashArray`、`BorderDashOffset`、`BorderThickness`、`CompactSpaceOrientation`、`CornerRadius`、`LineSpacing`、`Orientation`、`Padding`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | 当前没有抽取到控件专属 public 事件;交互通知主要来自继承事件、命令或 Gallery 可观察状态。 @@ -2286,7 +2288,7 @@ Space 的公共契约由 public/protected 类型成员、Avalonia 属性、事 ### 垂直间距 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:74` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:119` Gallery key:`ExamplesContent` / item `1` @@ -2317,7 +2319,7 @@ Gallery key:`ExamplesContent` / item `1` ### 间距尺寸 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:105` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:150` Gallery key:`ExamplesContent` / item `2` @@ -2352,7 +2354,7 @@ Gallery key:`ExamplesContent` / item `2` ### 对齐 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:140` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:185` Gallery key:`ExamplesContent` / item `3` @@ -2392,7 +2394,7 @@ Gallery key:`ExamplesContent` / item `3` ### 自动换行 -来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:180` +来源:`controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml:225` Gallery key:`ExamplesContent` / item `4` @@ -2446,6 +2448,10 @@ Space 的视觉模型由控件模板、ControlTheme、SharedToken 和必要的 Space 使用 `SpaceToken` 作为控件 Token scope。Token 只表达组件视觉语义,不承载 collection/filter、visual option 运行时状态。 +`Space` 本体没有独立的 `SpaceTheme.axaml`:root frame 由控件自身在 `Render` 中绘制(复用 `BorderRenderHelper`), +root 边框、背景、内边距与虚线由 `Space` 自有的 root frame 样式属性表达,定制边界见 [Space Semantic Part 契约](semantic-part.md) +第 3 节。主题文件只覆盖 `CompactSpace` / `CompactSpaceAddOn` 的模板与状态视觉。 + 主题维护规则: - 不删除或重命名已经稳定的 ControlTheme key、template part、伪类和资源 key。 @@ -2489,6 +2495,7 @@ Space Token 只表达组件级视觉变量,例如尺寸、间距、颜色、 - `src/AtomUI.Desktop.Controls/Space/ICompactSpaceAware.cs` - `src/AtomUI.Desktop.Controls/Space/InvalidSpaceFillerUsageException.cs` - `src/AtomUI.Desktop.Controls/Space/Space.cs` +- `src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs` - `src/AtomUI.Desktop.Controls/Space/SpaceToken.cs` - `src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceAddOnTheme.axaml` - `src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceTheme.axaml` @@ -2496,6 +2503,7 @@ Space Token 只表达组件级视觉变量,例如尺寸、间距、颜色、 职责边界: - 控件主文件保留 public/protected API、Avalonia 属性注册、事件和主要生命周期入口。 +- 语义声明文件只承载 `Space` 的 runtime semantic contract,不写模板节点或主题样式。 - Theme 文件负责静态视觉结构、template part、selector 和资源绑定。 - Token 文件只提供组件视觉变量,不保存实例状态。 - Gallery 文件只展示用法和示例,不作为运行时逻辑 owner。 @@ -2504,6 +2512,7 @@ Space Token 只表达组件级视觉变量,例如尺寸、间距、颜色、 - 源设计文档:`docs/controls/desktop/layout/space/overview.md` - 实现文档:`docs/controls/desktop/layout/space/implementation.md` +- Semantic Part 文档:`docs/controls/desktop/layout/space/semantic-part.md` - Token 文档:`docs/controls/desktop/layout/space/token.md` - 变更记录:`docs/controls/desktop/layout/space/changelog.md` - 语义结构:`./semantic-cn.md` diff --git a/docs/AI/generated/llms/llms-semantic-cn.md b/docs/AI/generated/llms/llms-semantic-cn.md index 93cfc8905..aaf85b5ad 100644 --- a/docs/AI/generated/llms/llms-semantic-cn.md +++ b/docs/AI/generated/llms/llms-semantic-cn.md @@ -1270,12 +1270,18 @@ Source: ./controls/space/semantic-cn.md ## Semantic Parts -| Part | AtomUI 节点 | 职责 | 相关 API | 相关 Token | 稳定性 | -| --- | --- | --- | --- | --- | --- | -| `root` | `Space` | 布局控件根语义区域,承载布局 public API、尺寸和主题入口。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | -| `container` | `布局容器` | 组织子元素、间距、断点、对齐或分割状态。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | -| `item` | `布局项` | 承载子内容、占位、跨度、排序或尺寸约束。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | -| `theme` | `主题区域` | 连接 SharedToken、布局主题资源和 Gallery 可观察样式。 | 见 API 与契约模型 | 见视觉与主题模型 | stable | +| Part | AtomUI 节点 | Selector | ContractType | Cardinality | Customization | CrossVisualRoot | RuntimeCreated | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `root` | `Space` owner | owner | `Space` | `Single` | `Root` | `false` | `false` | +| `item` | 直接子项 | `> .semantic-item` | `Control` | `Multiple` | `Selector` | `false` | `true` | +| `separator` | 直接分隔项 | `> .semantic-separator` | `Control` | `Multiple` | `Selector` | `false` | `true` | + +`root` 不声明 `.semantic-root` marker,也不生成独立 Style type。`item` 和 `separator` 都是运行时创建的语义标记: +`item` 挂在 `Space.Children` 的每个直接子项上,`separator` 挂在由 `SplitTemplate` 构建并插入相邻子项之间的 +分隔控件上。`separator` 的数量始终等于 `Children.Count - 1`,当 `SplitTemplate` 为空时不创建任何分隔项。 + +`Space` 没有独立的 `SpaceTheme.axaml`,因此内置主题不依赖静态 `.semantic-*` marker;语义契约完全由 runtime marker +与生成的 `Style` 类型表达。 ## Abstract AXAML Structure @@ -1289,10 +1295,10 @@ Source: ./controls/space/semantic-cn.md | 契约组 | 代表成员 | 维护含义 | | --- | --- | --- | -| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment` | 定义控件展示内容、输入数据、模板或业务对象入口。 | +| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment`、`SplitTemplate` | 定义控件展示内容、输入数据、模板或业务对象入口。 | | 选择与集合 | `PositionIndex` | 维护选择、展开、过滤、分页、分组或集合状态。 | | 交互与状态 | `IsUsedInCompactSpace`、`Status` | 表达用户可观察状态、可用性、清除、加载或反馈语义。 | -| 视觉与布局 | `CompactSpaceOrientation`、`LineSpacing`、`Orientation`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | +| 视觉与布局 | `Background`、`BorderBrush`、`BorderDashArray`、`BorderDashOffset`、`BorderThickness`、`CompactSpaceOrientation`、`CornerRadius`、`LineSpacing`、`Orientation`、`Padding`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | ## Pseudo Classes @@ -1329,6 +1335,10 @@ Space 的视觉模型由控件模板、ControlTheme、SharedToken 和必要的 Space 使用 `SpaceToken` 作为控件 Token scope。Token 只表达组件视觉语义,不承载 collection/filter、visual option 运行时状态。 +`Space` 本体没有独立的 `SpaceTheme.axaml`:root frame 由控件自身在 `Render` 中绘制(复用 `BorderRenderHelper`), +root 边框、背景、内边距与虚线由 `Space` 自有的 root frame 样式属性表达,定制边界见 [Space Semantic Part 契约](semantic-part.md) +第 3 节。主题文件只覆盖 `CompactSpace` / `CompactSpaceAddOn` 的模板与状态视觉。 + 主题维护规则: - 不删除或重命名已经稳定的 ControlTheme key、template part、伪类和资源 key。 diff --git a/docs/controls/desktop/layout/space/changelog.md b/docs/controls/desktop/layout/space/changelog.md index 61719eb1d..be4fbc6c2 100644 --- a/docs/controls/desktop/layout/space/changelog.md +++ b/docs/controls/desktop/layout/space/changelog.md @@ -2,6 +2,31 @@ 本文档记录 Space 控件级设计、API、主题契约、Token 和实现结构的变化。它不替代仓库根目录 `CHANGELOG.md`,也不作为正式版本发布说明。 +## 2026-08-21 + +- Code + - Add root frame appearance properties (`Background`, `BorderBrush`, `BorderThickness`, `CornerRadius`, `Padding`, `BorderDashArray`, `BorderDashOffset`) to `Space`, rendered through `BorderRenderHelper`, so Ant Design style-class root decoration (dashed border, background and padding) can be expressed directly on the control. + - Make `Padding + BorderThickness` participate in the measure/arrange frame inset so children stay inside the root border. +- Gallery + - Migrate `SpaceShowCase` to `GalleryShowCaseHost` with a Semantic Parts preview for `root` / `item` / `separator`. + - Add a `Custom Semantic Part styling` example aligned with the Ant Design `style-class` demo: a dashed root border with grey item background and red bold separator, plus a `size=large` root with light blue background. + - Add localization units for the semantic part descriptions, the style demo title/description and the demo button labels (en-US / zh-CN / zh-TW / pt-BR). +- Tests + - Add `SpaceRootFrameTests` covering layout inset, legacy default layout and the appearance API surface. + - Extend `SpaceShowCasePageTests` with document layout, semantic preview materialization, official style values and approved localization copy checks. +- Docs + - Document the root customization boundary in the Space Semantic Part contract and update the Space overview and implementation docs. + +## 2026-08-20 + +- Code + - Add `Space` semantic parts for `root` / `item` / `separator` and propagate runtime markers from direct children and split separators. + - Generate `SpaceItemStyle` and `SpaceSeparatorStyle` contract types through the semantic-part generator. +- Tests + - Add regression coverage for the `Space` descriptor, runtime marker counts, separator rebuild behavior and generated style routing. +- Docs + - Add the dedicated `Space Semantic Part` contract document and link it from the Space architecture and implementation docs. + ## 2026-06-26 - Docs diff --git a/docs/controls/desktop/layout/space/implementation.md b/docs/controls/desktop/layout/space/implementation.md index 32c575c0d..40387d71f 100644 --- a/docs/controls/desktop/layout/space/implementation.md +++ b/docs/controls/desktop/layout/space/implementation.md @@ -1,6 +1,6 @@ # Space 桌面版实现原理 -本文档描述 Space 桌面版的内部实现范围、源码职责、状态流、生命周期、资源边界和维护规则。公共设计与 API 契约见 [Space 桌面版架构设计](overview.md),变化记录见 [Space Changelog](changelog.md)。涉及控件 Token 的实现应同时阅读 [Space Token 设计](token.md)。 +本文档描述 Space 桌面版的内部实现范围、源码职责、状态流、生命周期、资源边界和维护规则。公共设计与 API 契约见 [Space 桌面版架构设计](overview.md),Semantic Part 契约见 [Space Semantic Part 契约](semantic-part.md),变化记录见 [Space Changelog](changelog.md)。涉及控件 Token 的实现应同时阅读 [Space Token 设计](token.md)。 ## 1. 实现定位 @@ -18,6 +18,7 @@ - `src/AtomUI.Desktop.Controls/Space/ICompactSpaceAware.cs` - `src/AtomUI.Desktop.Controls/Space/InvalidSpaceFillerUsageException.cs` - `src/AtomUI.Desktop.Controls/Space/Space.cs` +- `src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs` - `src/AtomUI.Desktop.Controls/Space/SpaceToken.cs` - `src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceAddOnTheme.axaml` - `src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceTheme.axaml` @@ -25,6 +26,7 @@ 职责边界: - 控件主文件保留 public/protected API、Avalonia 属性注册、事件和主要生命周期入口。 +- 语义声明文件只承载 `Space` 的 runtime semantic contract,不写模板节点或主题样式。 - Theme 文件负责静态视觉结构、template part、selector 和资源绑定。 - Token 文件只提供组件视觉变量,不保存实例状态。 - Gallery 文件只展示用法和示例,不作为运行时逻辑 owner。 @@ -80,6 +82,7 @@ Public API / ItemsSource / Command / Event - 构造阶段只注册必要状态,不依赖 template part。 - 模板应用时获取 part、建立事件订阅和绑定,并先释放旧 part 订阅。 - 控件卸载、弹层关闭、窗口关闭、集合替换或 container recycle 时释放事件订阅和资源宿主。 +- 运行时 semantic marker(`item` / `separator`)随 attach 与 `SplitTemplate` 变化重建,释放时只清理 `Space` 自己持有的 marker ownership,不触碰应用侧自行添加的 class。 - DynamicResource、TokenResourceBinder 或 C# binding 必须有明确 owner 和释放点。 - Browser 和 Desktop 宿主下的主题加载顺序不得影响 public API 语义。 @@ -107,6 +110,8 @@ Space 的交互事件应从输入源收敛到控件级语义事件: - 主题资源、Token 和 SharedToken 计算后的视觉更新。 - ItemsSource、selection、checked、expanded、filter、paging 或 upload task 的集合同步。 - 状态变化时避免创建不必要的视觉对象、订阅或动画对象。 +- Root frame 布局与绘制:`Padding + BorderThickness` 构成 frame inset,`MeasureOverride` 用 inset 收缩测量约束并外扩 desired size,`ArrangeOverride` 用 inset 收缩排布区域,`Render` 通过 `BorderRenderHelper` 绘制背景、边框、圆角与虚线。 +- Runtime semantic marker:`item` / `separator` 标记由 `Space` 以 ownership set 维护,attach 与 `SplitTemplate` 变化时重建,移除子项时只清理 Space 自己添加的标记。 实现文档不逐行解释私有方法。若某个私有算法成为稳定维护入口,应在本节补充算法不变量,而不是把代码复述为说明书。 diff --git a/docs/controls/desktop/layout/space/overview.md b/docs/controls/desktop/layout/space/overview.md index 710416f82..275c5f995 100644 --- a/docs/controls/desktop/layout/space/overview.md +++ b/docs/controls/desktop/layout/space/overview.md @@ -1,6 +1,6 @@ # Space 桌面版架构设计 -本文档定义 `Space` 桌面版的最新设计定位、公共契约、状态模型、视觉主题关系和兼容边界。通用控件研发约束见 [控件研发标准](../../../../engineering/development/control-development-guidelines.md),内部实现原理见 [Space 桌面版实现原理](implementation.md),Space Token 的专项设计见 [Space Token 设计](token.md),设计和契约变化记录见 [Space Changelog](changelog.md)。 +本文档定义 `Space` 桌面版的最新设计定位、公共契约、状态模型、视觉主题关系和兼容边界。通用控件研发约束见 [控件研发标准](../../../../engineering/development/control-development-guidelines.md),Semantic Part 契约见 [Space Semantic Part 契约](semantic-part.md),内部实现原理见 [Space 桌面版实现原理](implementation.md),Space Token 的专项设计见 [Space Token 设计](token.md),设计和契约变化记录见 [Space Changelog](changelog.md)。 ## 1. 控件定位 @@ -16,6 +16,8 @@ Space 是 AtomUI 桌面控件体系中的间距布局控件,用于在一组子 Space 不负责复杂响应式排版、滚动容器或数据虚拟化列表。这些职责应由业务层、组合控件或更专用的 AtomUI 控件承担。 +Space 的公开语义区域使用 `root`、`item` 和 `separator` 三个 Semantic Part;完整契约见 [Space Semantic Part 契约](semantic-part.md)。 + 主要源码入口: - `src/AtomUI.Desktop.Controls/Space` @@ -39,10 +41,10 @@ Space 的公共契约由 public/protected 类型成员、Avalonia 属性、事 | 契约组 | 代表成员 | 维护含义 | | --- | --- | --- | -| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment` | 定义控件展示内容、输入数据、模板或业务对象入口。 | +| 内容与数据 | `CompactSpaceItemPosition`、`Content`、`ContentTemplate`、`ItemHeight`、`ItemSpacing`、`ItemWidth`、`ItemsAlignment`、`SplitTemplate` | 定义控件展示内容、输入数据、模板或业务对象入口。 | | 选择与集合 | `PositionIndex` | 维护选择、展开、过滤、分页、分组或集合状态。 | | 交互与状态 | `IsUsedInCompactSpace`、`Status` | 表达用户可观察状态、可用性、清除、加载或反馈语义。 | -| 视觉与布局 | `CompactSpaceOrientation`、`LineSpacing`、`Orientation`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | +| 视觉与布局 | `Background`、`BorderBrush`、`BorderDashArray`、`BorderDashOffset`、`BorderThickness`、`CompactSpaceOrientation`、`CornerRadius`、`LineSpacing`、`Orientation`、`Padding`、`SizeType`、`StyleVariant` | 影响尺寸、位置、颜色、形状、密度和模板视觉变量。 | 当前没有抽取到控件专属 public 事件;交互通知主要来自继承事件、命令或 Gallery 可观察状态。 @@ -89,6 +91,10 @@ Space 的视觉模型由控件模板、ControlTheme、SharedToken 和必要的 Space 使用 `SpaceToken` 作为控件 Token scope。Token 只表达组件视觉语义,不承载 collection/filter、visual option 运行时状态。 +`Space` 本体没有独立的 `SpaceTheme.axaml`:root frame 由控件自身在 `Render` 中绘制(复用 `BorderRenderHelper`), +root 边框、背景、内边距与虚线由 `Space` 自有的 root frame 样式属性表达,定制边界见 [Space Semantic Part 契约](semantic-part.md) +第 3 节。主题文件只覆盖 `CompactSpace` / `CompactSpaceAddOn` 的模板与状态视觉。 + 主题维护规则: - 不删除或重命名已经稳定的 ControlTheme key、template part、伪类和资源 key。 diff --git a/docs/controls/desktop/layout/space/semantic-part.md b/docs/controls/desktop/layout/space/semantic-part.md new file mode 100644 index 000000000..aef4bdb83 --- /dev/null +++ b/docs/controls/desktop/layout/space/semantic-part.md @@ -0,0 +1,86 @@ +# Space Semantic Part 契约 + +本文档定义 `Space` 对应用公开的 Semantic Part、Selector、类型约束、数量语义和定制边界。`Space` 的整体设计见 +[Space 桌面版架构设计](overview.md),实现与生命周期见 [Space 桌面版实现原理](implementation.md),系统级规则见 +[AtomUI Semantic Part 系统设计](../../../../architecture/systems/theming/semantic-parts.md)。 + +`Space` 的公开语义结构与 Ant Design 6 的 Semantic DOM 结构对齐:`root`、`item`、`separator`。其中 `root` 是隐式 +owner,`item` 表示每个直接子项,`separator` 表示位于子项之间的分隔元素。 + +## 1. Semantic Parts + +| Part | AtomUI 节点 | Selector | ContractType | Cardinality | Customization | CrossVisualRoot | RuntimeCreated | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `root` | `Space` owner | owner | `Space` | `Single` | `Root` | `false` | `false` | +| `item` | 直接子项 | `> .semantic-item` | `Control` | `Multiple` | `Selector` | `false` | `true` | +| `separator` | 直接分隔项 | `> .semantic-separator` | `Control` | `Multiple` | `Selector` | `false` | `true` | + +`root` 不声明 `.semantic-root` marker,也不生成独立 Style type。`item` 和 `separator` 都是运行时创建的语义标记: +`item` 挂在 `Space.Children` 的每个直接子项上,`separator` 挂在由 `SplitTemplate` 构建并插入相邻子项之间的 +分隔控件上。`separator` 的数量始终等于 `Children.Count - 1`,当 `SplitTemplate` 为空时不创建任何分隔项。 + +`Space` 没有独立的 `SpaceTheme.axaml`,因此内置主题不依赖静态 `.semantic-*` marker;语义契约完全由 runtime marker +与生成的 `Style` 类型表达。 + +## 2. Selector 用法 + +应用侧使用 owner-scoped selector 和生成的 Style 类型,不手写 `/template/` 路径: + +```xml + +``` + +`SpaceItemStyle` 和 `SpaceSeparatorStyle` 位于 `AtomUI.Theme.Styling` 命名空间,由语义生成器根据 `Space.SemanticParts.cs` +生成。`ContractType` 只给出 Setter 所需的最低稳定类型,不参与 selector 匹配;如果分隔符模板是更具体的 control,应用 +可以在自己的样式里把 `x:SetterTargetType` 收窄到更具体的公开类型。 + +## 3. Root 定制边界 + +`root` 的定制直接作用于 `Space` owner 自身。`Space` 作为 `Control` 派生类没有 Avalonia 的 `Background` / `Border*` / +`Padding` 基础属性,因此 `Space` 注册了自身的 root frame 样式属性,用于对齐 Ant Design 对 root 的边框、背景与内边距 +定制(对应 antd `styles.root` 的 `borderWidth`、`borderStyle`、`backgroundColor`、`borderColor`、`padding`): + +| 属性 | 类型 | 作用 | +| --- | --- | --- | +| `Background` | `IBrush?` | root 背景填充。 | +| `BorderBrush` | `IBrush?` | root 边框画笔。 | +| `BorderThickness` | `Thickness` | root 边框厚度。 | +| `CornerRadius` | `CornerRadius` | root 圆角。 | +| `Padding` | `Thickness` | root 内边距。 | +| `BorderDashArray` | `IReadOnlyList?` | root 边框虚线阵列,与 `BorderThickness` 配合表达虚线边框。 | +| `BorderDashOffset` | `double` | root 边框虚线偏移。 | + +边界约定: + +- root frame 由 `Space` 自身在 `Render` 中绘制(复用 `BorderRenderHelper`),`Space` 没有独立的 `SpaceTheme.axaml`, + 因此内置主题不依赖静态模板或静态 `.semantic-*` marker。 +- `Padding + BorderThickness` 构成 frame inset:`MeasureOverride` 先用 inset 收缩测量约束,`ArrangeOverride` 再用 inset + 收缩排布区域,子项永远位于边框与内边距之内;desired size 对 inset 做外扩。 +- 这些属性只影响 root 外观与布局边界,不改变 `item` / `separator` 的语义契约;`item` / `separator` 的样式仍通过 + `SpaceItemStyle` / `SpaceSeparatorStyle` 表达。 + +## 4. Template 与状态边界 + +- `item` marker 位于 `Space.Children` 中每个直接子项上;它与 `Orientation`、`ItemsAlignment`、`ItemWidth`、`ItemHeight`、 + `ItemSpacing`、`LineSpacing`、`SizeType` 这些布局状态解耦。 +- `separator` marker 位于 `SplitTemplate` 生成的分隔控件上,只负责表达“此处是分隔项”这一稳定职责。 +- `SplitTemplate` 只是 separator 的来源,不改变 root/item 语义,也不引入额外包装层。 +- `SplitTemplate` 在 children 已存在后设置时,会在 attach 后重建同一组 item / separator marker。 +- 语义 contract 不要求 `Space` 使用 `ControlTheme` 或静态 template part;它只要求 marker、descriptor 和生成 style 一致。 + +## 5. 验证不变量 + +- `Space` descriptor 公开 `root`、`item`、`separator`,其中 `item` 和 `separator` 都是 `Multiple`。 +- `root` 仍然是隐式 owner,不存在 `.semantic-root` marker。 +- 内置代码和主题文件不使用 `Classes.semantic-*="True"` 静态 marker。 +- 运行时 marker 数量与实际 children / separators 数量一致。 +- `SplitTemplate` 的设置顺序不会改变最终语义:先加 children 再设 `SplitTemplate`,或先设 `SplitTemplate` 再加 children,结果都应稳定。 +- root frame 属性只参与 root 外观与 frame inset 布局,不改变 `item` / `separator` 的语义契约。 +- 回归测试见 `tests/AtomUI.Desktop.Controls.Tests/Space/SpaceSemanticPartTests.cs` 与 `tests/AtomUI.Desktop.Controls.Tests/Space/SpaceRootFrameTests.cs`。 diff --git a/src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs b/src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs new file mode 100644 index 000000000..fc49d6788 --- /dev/null +++ b/src/AtomUI.Desktop.Controls/Space/Space.SemanticParts.cs @@ -0,0 +1,24 @@ +using AtomUI.Theme; +using Avalonia.Controls; + +namespace AtomUI.Desktop.Controls; + +[SemanticPart( + "item", + SelectorClass = "semantic-item", + SelectorRoute = "> .semantic-item", + ContractType = typeof(Control), + Cardinality = SemanticPartCardinality.Multiple, + Since = "6.0", + RuntimeCreated = true)] +[SemanticPart( + "separator", + SelectorClass = "semantic-separator", + SelectorRoute = "> .semantic-separator", + ContractType = typeof(Control), + Cardinality = SemanticPartCardinality.Multiple, + Since = "6.0", + RuntimeCreated = true)] +public partial class Space +{ +} diff --git a/src/AtomUI.Desktop.Controls/Space/Space.cs b/src/AtomUI.Desktop.Controls/Space/Space.cs index 5047ec6e9..64755a4df 100644 --- a/src/AtomUI.Desktop.Controls/Space/Space.cs +++ b/src/AtomUI.Desktop.Controls/Space/Space.cs @@ -3,8 +3,10 @@ using System.ComponentModel; using System.Diagnostics; using System.Reactive.Disposables; using AtomUI.Controls; +using AtomUI.Controls.Utils; using AtomUI.Data; using AtomUI.Desktop.Controls.DesignTokens; +using AtomUI.Generated.AtomUIDesktopControls; using AtomUI.Utils; using Avalonia; using Avalonia.Collections; @@ -12,6 +14,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; +using Avalonia.Media; using Avalonia.Metadata; using Avalonia.VisualTree; @@ -35,10 +38,10 @@ public enum SpaceItemsAlignment End, } -public class Space : Control, - IChildIndexProvider, - ICustomizableSizeTypeAware, - INavigableContainer +public partial class Space : Control, + IChildIndexProvider, + ICustomizableSizeTypeAware, + INavigableContainer { #region 公共属性定义 public static readonly StyledProperty ItemSpacingProperty = @@ -65,6 +68,27 @@ public class Space : Control, public static readonly StyledProperty?> SplitTemplateProperty = AvaloniaProperty.Register?>( nameof(SplitTemplate)); + + public static readonly StyledProperty BackgroundProperty = + AvaloniaProperty.Register(nameof(Background)); + + public static readonly StyledProperty BorderBrushProperty = + AvaloniaProperty.Register(nameof(BorderBrush)); + + public static readonly StyledProperty BorderThicknessProperty = + AvaloniaProperty.Register(nameof(BorderThickness)); + + public static readonly StyledProperty CornerRadiusProperty = + AvaloniaProperty.Register(nameof(CornerRadius)); + + public static readonly StyledProperty PaddingProperty = + AvaloniaProperty.Register(nameof(Padding)); + + public static readonly StyledProperty?> BorderDashArrayProperty = + AvaloniaProperty.Register?>(nameof(BorderDashArray)); + + public static readonly StyledProperty BorderDashOffsetProperty = + AvaloniaProperty.Register(nameof(BorderDashOffset)); public CustomizableSizeType SizeType { @@ -113,6 +137,48 @@ public class Space : Control, get => GetValue(SplitTemplateProperty); set => SetValue(SplitTemplateProperty, value); } + + public IBrush? Background + { + get => GetValue(BackgroundProperty); + set => SetValue(BackgroundProperty, value); + } + + public IBrush? BorderBrush + { + get => GetValue(BorderBrushProperty); + set => SetValue(BorderBrushProperty, value); + } + + public Thickness BorderThickness + { + get => GetValue(BorderThicknessProperty); + set => SetValue(BorderThicknessProperty, value); + } + + public CornerRadius CornerRadius + { + get => GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); + } + + public Thickness Padding + { + get => GetValue(PaddingProperty); + set => SetValue(PaddingProperty, value); + } + + public IReadOnlyList? BorderDashArray + { + get => GetValue(BorderDashArrayProperty); + set => SetValue(BorderDashArrayProperty, value); + } + + public double BorderDashOffset + { + get => GetValue(BorderDashOffsetProperty); + set => SetValue(BorderDashOffsetProperty, value); + } [Content] public AvaloniaList Children { get; } = new(); @@ -123,7 +189,9 @@ public class Space : Control, private EventHandler? _childIndexChanged; private CompositeDisposable? _spacingBindings; - + private readonly HashSet _semanticItemMarkerOwners = new(); + private BorderRenderHelper? _borderRenderHelper; + static Space() { AffectsMeasure(ItemSpacingProperty, @@ -131,8 +199,16 @@ public class Space : Control, OrientationProperty, ItemWidthProperty, ItemHeightProperty, - SizeTypeProperty); + SizeTypeProperty, + BorderThicknessProperty, + PaddingProperty); AffectsArrange(ItemsAlignmentProperty); + AffectsRender(BackgroundProperty, + BorderBrushProperty, + BorderThicknessProperty, + CornerRadiusProperty, + BorderDashArrayProperty, + BorderDashOffsetProperty); CustomizableSizeTypeControlProperty.SizeTypeProperty.OverrideDefaultValue(CustomizableSizeType.Small); } @@ -145,6 +221,11 @@ public class Space : Control, { base.OnAttachedToVisualTree(e); ApplySpacingTokenBinding(); + + if (SplitTemplate != null) + { + HandleSplitTemplateChanged(); + } } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) @@ -172,6 +253,8 @@ public class Space : Control, protected virtual void HandleChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e) { + ConfigureSemanticItemMarkers(e); + if (SplitTemplate == null) { switch (e.Action) @@ -205,6 +288,7 @@ public class Space : Control, { var index = i + e.OldStartingIndex; var child = (Control)e.NewItems![i]!; + EnsureSemanticItemMarker(child); if (!IsItemsHost) { LogicalChildren[index] = child; @@ -230,6 +314,21 @@ public class Space : Control, InvalidateMeasure(); } + public sealed override void Render(DrawingContext context) + { + var borderRenderHelper = _borderRenderHelper ??= new BorderRenderHelper(); + borderRenderHelper.Render( + context, + Bounds.Size, + BorderThickness, + CornerRadius, + BackgroundSizing.InnerBorderEdge, + Background, + BorderBrush, + BorderDashArray, + BorderDashOffset); + } + protected override Size MeasureOverride(Size constraint) { double itemWidth = ItemWidth; @@ -238,17 +337,21 @@ public class Space : Control, double lineSpacing = LineSpacing; var orientation = Orientation; var children = VisualChildren; + var frameInset = Padding + BorderThickness; + var layoutConstraint = new Size( + Math.Max(0, constraint.Width - frameInset.Left - frameInset.Right), + Math.Max(0, constraint.Height - frameInset.Top - frameInset.Bottom)); var curLineSize = new UVSize(orientation); var panelSize = new UVSize(orientation); - var uvConstraint = new UVSize(orientation, constraint.Width, constraint.Height); + var uvConstraint = new UVSize(orientation, layoutConstraint.Width, layoutConstraint.Height); bool itemWidthSet = !double.IsNaN(itemWidth); bool itemHeightSet = !double.IsNaN(itemHeight); bool itemExists = false; bool lineExists = false; var childConstraint = new Size( - itemWidthSet ? itemWidth : constraint.Width, - itemHeightSet ? itemHeight : constraint.Height); + itemWidthSet ? itemWidth : layoutConstraint.Width, + itemHeightSet ? itemHeight : layoutConstraint.Height); for (int i = 0, count = children.Count; i < count; ++i) { @@ -291,7 +394,7 @@ public class Space : Control, panelSize.V += curLineSize.V + (lineExists ? lineSpacing : 0); // Go from UV space to W/H space - return new Size(panelSize.Width, panelSize.Height); + return new Size(panelSize.Width, panelSize.Height).Inflate(frameInset); } protected override Size ArrangeOverride(Size finalSize) @@ -303,12 +406,14 @@ public class Space : Control, var orientation = Orientation; bool isHorizontal = orientation == Orientation.Horizontal; var children = VisualChildren; + var frameInset = Padding + BorderThickness; + var layoutSize = finalSize.Deflate(frameInset); int firstInLine = 0; double accumulatedV = 0; double itemU = isHorizontal ? itemWidth : itemHeight; double itemV = isHorizontal ? itemHeight : itemWidth; var curLineSize = new UVSize(orientation); - var uvFinalSize = new UVSize(orientation, finalSize.Width, finalSize.Height); + var uvFinalSize = new UVSize(orientation, layoutSize.Width, layoutSize.Height); bool itemWidthSet = !double.IsNaN(itemWidth); bool itemHeightSet = !double.IsNaN(itemHeight); bool itemExists = false; @@ -388,7 +493,8 @@ public class Space : Control, _ => throw new ArgumentOutOfRangeException(nameof(ItemsAlignment), ItemsAlignment, null), }; } - newChild.Arrange(isHorizontal ? new(u, v, layoutSlotU, layoutSlotV) : new(v, u, layoutSlotV, layoutSlotU)); + newChild.Arrange(isHorizontal ? new(u + frameInset.Left, v + frameInset.Top, layoutSlotU, layoutSlotV) + : new(v + frameInset.Left, u + frameInset.Top, layoutSlotV, layoutSlotU)); u += layoutSlotU; first = false; } @@ -526,6 +632,7 @@ public class Space : Control, for (var i = 0; i < Children.Count; ++i) { var child = Children[i]; + EnsureSemanticItemMarker(child); if (!IsItemsHost) { LogicalChildren.Add(child); @@ -535,7 +642,7 @@ public class Space : Control, { if (i != Children.Count - 1) { - var split = SplitTemplate.Build(); + var split = CreateSemanticSeparator(); if (!IsItemsHost) { LogicalChildren.Add(split); @@ -546,6 +653,87 @@ public class Space : Control, } } + private void ConfigureSemanticItemMarkers(NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (Control child in e.NewItems!) + { + EnsureSemanticItemMarker(child); + } + break; + + case NotifyCollectionChangedAction.Remove: + foreach (Control child in e.OldItems!) + { + ReleaseSemanticItemMarker(child); + } + break; + + case NotifyCollectionChangedAction.Replace: + foreach (Control child in e.OldItems!) + { + ReleaseSemanticItemMarker(child); + } + + foreach (Control child in e.NewItems!) + { + EnsureSemanticItemMarker(child); + } + break; + + case NotifyCollectionChangedAction.Move: + break; + + case NotifyCollectionChangedAction.Reset: + ReleaseStaleSemanticItemMarkers(); + break; + } + } + + private void EnsureSemanticItemMarker(Control child) + { + if (child.Classes.Contains(SpaceSemanticParts.ItemClass)) + { + return; + } + + child.Classes.Add(SpaceSemanticParts.ItemClass); + _semanticItemMarkerOwners.Add(child); + } + + private void ReleaseSemanticItemMarker(Control child) + { + if (_semanticItemMarkerOwners.Remove(child)) + { + child.Classes.Remove(SpaceSemanticParts.ItemClass); + } + } + + private void ReleaseStaleSemanticItemMarkers() + { + foreach (var child in _semanticItemMarkerOwners.ToArray()) + { + if (!Children.Contains(child)) + { + ReleaseSemanticItemMarker(child); + } + } + } + + private Control CreateSemanticSeparator() + { + Debug.Assert(SplitTemplate != null); + var separator = SplitTemplate.Build(); + if (!separator.Classes.Contains(SpaceSemanticParts.SeparatorClass)) + { + separator.Classes.Add(SpaceSemanticParts.SeparatorClass); + } + + return separator; + } + private void HandleChildrenPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Children.Count) || e.PropertyName is null) diff --git a/src/AtomUI.Toolkits.GalleryBase/Controls/Themes/SemanticPartPreviewTheme.axaml b/src/AtomUI.Toolkits.GalleryBase/Controls/Themes/SemanticPartPreviewTheme.axaml index ac286e9cb..8d0f822ac 100644 --- a/src/AtomUI.Toolkits.GalleryBase/Controls/Themes/SemanticPartPreviewTheme.axaml +++ b/src/AtomUI.Toolkits.GalleryBase/Controls/Themes/SemanticPartPreviewTheme.axaml @@ -23,7 +23,8 @@ - +