mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 23:08:36 +08:00
feat(Semantic): add Space semantic parts and align Ant Design demo
This commit is contained in:
+12
-1
@@ -110,5 +110,16 @@ public enum SpaceShowCaseLangResourceKind
|
||||
P2ToolTipTipHeart,
|
||||
P2ToolTipTipShare,
|
||||
P2ToolTipTipDownload,
|
||||
P2ToolTipTipTooltip
|
||||
P2ToolTipTipTooltip,
|
||||
P2LargeSpaceButtonN1,
|
||||
P2LargeSpaceButtonN2,
|
||||
P2LargeSpaceButtonN3,
|
||||
P2StyledButtonN1,
|
||||
P2StyledButtonN2,
|
||||
P2StyledButtonN3,
|
||||
SemanticRootDescription,
|
||||
SemanticItemDescription,
|
||||
SemanticSeparatorDescription,
|
||||
SemanticPartStyleTitle,
|
||||
SemanticPartStyleDescription
|
||||
}
|
||||
|
||||
@@ -301,6 +301,21 @@
|
||||
<source>Zhong Hua Men</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN1">
|
||||
<segment>
|
||||
<source>Large Space Button 1</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN2">
|
||||
<segment>
|
||||
<source>Large Space Button 2</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN3">
|
||||
<segment>
|
||||
<source>Large Space Button 3</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2OkTextOk">
|
||||
<segment>
|
||||
<source>Ok</source>
|
||||
@@ -361,6 +376,21 @@
|
||||
<source>End time</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN1">
|
||||
<segment>
|
||||
<source>Styled Button 1</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN2">
|
||||
<segment>
|
||||
<source>Styled Button 2</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN3">
|
||||
<segment>
|
||||
<source>Styled Button 3</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2TextBlock">
|
||||
<segment>
|
||||
<source>Block</source>
|
||||
@@ -481,6 +511,31 @@
|
||||
<source>Size</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticItemDescription">
|
||||
<segment>
|
||||
<source>Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleTitle">
|
||||
<segment>
|
||||
<source>Custom Semantic Part styling</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticRootDescription">
|
||||
<segment>
|
||||
<source>Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticSeparatorDescription">
|
||||
<segment>
|
||||
<source>Separator element with divider styling</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SizeDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
|
||||
@@ -361,6 +361,24 @@
|
||||
<target state="translated">Zhong Hua Men</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN1">
|
||||
<segment>
|
||||
<source>Large Space Button 1</source>
|
||||
<target state="translated">Botão com Espaçamento Grande 1</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN2">
|
||||
<segment>
|
||||
<source>Large Space Button 2</source>
|
||||
<target state="translated">Botão com Espaçamento Grande 2</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN3">
|
||||
<segment>
|
||||
<source>Large Space Button 3</source>
|
||||
<target state="translated">Botão com Espaçamento Grande 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2OkTextOk">
|
||||
<segment>
|
||||
<source>Ok</source>
|
||||
@@ -433,6 +451,24 @@
|
||||
<target state="translated">Fim hora</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN1">
|
||||
<segment>
|
||||
<source>Styled Button 1</source>
|
||||
<target state="translated">Botão Estilizado 1</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN2">
|
||||
<segment>
|
||||
<source>Styled Button 2</source>
|
||||
<target state="translated">Botão Estilizado 2</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN3">
|
||||
<segment>
|
||||
<source>Styled Button 3</source>
|
||||
<target state="translated">Botão Estilizado 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2TextBlock">
|
||||
<segment>
|
||||
<source>Block</source>
|
||||
@@ -577,6 +613,36 @@
|
||||
<target state="translated">Tamanho</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticItemDescription">
|
||||
<segment>
|
||||
<source>Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment</source>
|
||||
<target state="translated">Elemento de item encapsulado com layout e estilos de espaçamento, fornecendo um invólucro para cada elemento filho para alinhamento em linha</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
<target state="translated">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.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleTitle">
|
||||
<segment>
|
||||
<source>Custom Semantic Part styling</source>
|
||||
<target state="translated">Personalizar estilo de Semantic Part</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticRootDescription">
|
||||
<segment>
|
||||
<source>Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles</source>
|
||||
<target state="translated">Elemento raiz com layout flex, configurações de espaçamento, alinhamento, quebra e outros estilos básicos do contêiner de espaçamento</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticSeparatorDescription">
|
||||
<segment>
|
||||
<source>Separator element with divider styling</source>
|
||||
<target state="translated">Elemento separador com estilo de divisor</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SizeDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
|
||||
@@ -361,6 +361,24 @@
|
||||
<target state="translated">中华门</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN1">
|
||||
<segment>
|
||||
<source>Large Space Button 1</source>
|
||||
<target state="translated">大间距按钮 1</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN2">
|
||||
<segment>
|
||||
<source>Large Space Button 2</source>
|
||||
<target state="translated">大间距按钮 2</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN3">
|
||||
<segment>
|
||||
<source>Large Space Button 3</source>
|
||||
<target state="translated">大间距按钮 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2OkTextOk">
|
||||
<segment>
|
||||
<source>Ok</source>
|
||||
@@ -433,6 +451,24 @@
|
||||
<target state="translated">结束时间</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN1">
|
||||
<segment>
|
||||
<source>Styled Button 1</source>
|
||||
<target state="translated">样式化按钮 1</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN2">
|
||||
<segment>
|
||||
<source>Styled Button 2</source>
|
||||
<target state="translated">样式化按钮 2</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN3">
|
||||
<segment>
|
||||
<source>Styled Button 3</source>
|
||||
<target state="translated">样式化按钮 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2TextBlock">
|
||||
<segment>
|
||||
<source>Block</source>
|
||||
@@ -577,6 +613,36 @@
|
||||
<target state="translated">尺寸</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticItemDescription">
|
||||
<segment>
|
||||
<source>Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment</source>
|
||||
<target state="translated">包裹的子组件,包含间距项的布局和样式,为每个子元素提供包装用于内联对齐</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
<target state="translated">对齐 Ant Design style-class 示例:通过生成的 Semantic Part 样式自定义 root 边框、item 背景与 separator 颜色,并为 root 添加虚线边框。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleTitle">
|
||||
<segment>
|
||||
<source>Custom Semantic Part styling</source>
|
||||
<target state="translated">自定义 Semantic Part 样式</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticRootDescription">
|
||||
<segment>
|
||||
<source>Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles</source>
|
||||
<target state="translated">根元素,包含 flex 布局、间隙设置、对齐方式、换行等间距容器的基础样式</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticSeparatorDescription">
|
||||
<segment>
|
||||
<source>Separator element with divider styling</source>
|
||||
<target state="translated">分隔符,包含分隔元素的样式</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SizeDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
|
||||
@@ -361,6 +361,24 @@
|
||||
<target state="translated">中華門</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN1">
|
||||
<segment>
|
||||
<source>Large Space Button 1</source>
|
||||
<target state="translated">大間距按鈕 1</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN2">
|
||||
<segment>
|
||||
<source>Large Space Button 2</source>
|
||||
<target state="translated">大間距按鈕 2</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2LargeSpaceButtonN3">
|
||||
<segment>
|
||||
<source>Large Space Button 3</source>
|
||||
<target state="translated">大間距按鈕 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2OkTextOk">
|
||||
<segment>
|
||||
<source>Ok</source>
|
||||
@@ -433,6 +451,24 @@
|
||||
<target state="translated">結束時間</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN1">
|
||||
<segment>
|
||||
<source>Styled Button 1</source>
|
||||
<target state="translated">樣式化按鈕 1</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN2">
|
||||
<segment>
|
||||
<source>Styled Button 2</source>
|
||||
<target state="translated">樣式化按鈕 2</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2StyledButtonN3">
|
||||
<segment>
|
||||
<source>Styled Button 3</source>
|
||||
<target state="translated">樣式化按鈕 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="P2TextBlock">
|
||||
<segment>
|
||||
<source>Block</source>
|
||||
@@ -577,6 +613,36 @@
|
||||
<target state="translated">尺寸</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticItemDescription">
|
||||
<segment>
|
||||
<source>Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment</source>
|
||||
<target state="translated">包裹的子元件,包含間距項的佈局和樣式,為每個子元素提供包裝用於內聯對齊</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
<target state="translated">對齊 Ant Design style-class 範例:透過生成的 Semantic Part 樣式自訂 root 邊框、item 背景與 separator 顏色,並為 root 加上虛線邊框。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticPartStyleTitle">
|
||||
<segment>
|
||||
<source>Custom Semantic Part styling</source>
|
||||
<target state="translated">自定義 Semantic Part 樣式</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticRootDescription">
|
||||
<segment>
|
||||
<source>Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles</source>
|
||||
<target state="translated">根元素,包含 flex 佈局、間隙設定、對齊方式、換行等間距容器的基礎樣式</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SemanticSeparatorDescription">
|
||||
<segment>
|
||||
<source>Separator element with divider styling</source>
|
||||
<target state="translated">分隔符,包含分隔元素的樣式</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="SizeDescription">
|
||||
<segment>
|
||||
<source>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.</source>
|
||||
|
||||
@@ -19,11 +19,19 @@
|
||||
<Setter Property="Height" Value="70" />
|
||||
<Setter Property="Width" Value="50" />
|
||||
</Style>
|
||||
<Style Selector="Border.space-surface">
|
||||
<Setter Property="Background" Value="{atom:SharedTokenResource ColorBgContainer}" />
|
||||
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorBorderSecondary}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="Padding" Value="16" />
|
||||
<Setter Property="BoxShadow" Value="0 0 10 0 #1A000000" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid Background="{atom:SharedTokenResource ColorBgLayout}">
|
||||
<gallery:GalleryStickyTabsHost StickyContentPadding="28,0,28,0">
|
||||
<gallery:GalleryStickyTabsHost.Header>
|
||||
<gallery:GalleryShowCaseHost StickyContentPadding="28,0,28,0">
|
||||
<gallery:GalleryShowCaseHost.Header>
|
||||
<gallery:GalleryShowCaseHeader
|
||||
Title="Space"
|
||||
Category="{gallery:SpaceShowCaseLangResource ComponentCategory}"
|
||||
@@ -34,8 +42,45 @@
|
||||
Package="AtomUI.Desktop.Controls"
|
||||
BaseClass="Control"
|
||||
MetadataValueWidth="220" />
|
||||
</gallery:GalleryStickyTabsHost.Header>
|
||||
</gallery:GalleryShowCaseHost.Header>
|
||||
|
||||
<gallery:GalleryShowCaseHost.SemanticPartsContentTemplate>
|
||||
<DataTemplate x:DataType="viewModels:SpaceViewModel">
|
||||
<StackPanel Spacing="24">
|
||||
<gallery:SemanticPartPreview Name="SpaceSemanticPreview"
|
||||
Classes="preview-content-centered"
|
||||
SemanticOwner="{Binding #SpaceSemanticOwner}"
|
||||
SemanticOwnerType="{x:Type atom:Space}">
|
||||
<gallery:SemanticPartPreview.PreviewContent>
|
||||
<atom:Space Name="SpaceSemanticOwner"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<atom:Space.SplitTemplate>
|
||||
<Template>
|
||||
<atom:Separator Orientation="Vertical" />
|
||||
</Template>
|
||||
</atom:Space.SplitTemplate>
|
||||
<atom:Button ButtonType="Primary" Content="{gallery:SpaceShowCaseLangResource P2ContentPrimary}" />
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2ContentDefault}" />
|
||||
<atom:Button ButtonType="Dashed" Content="{gallery:SpaceShowCaseLangResource P2ContentDashed}" />
|
||||
</atom:Space>
|
||||
</gallery:SemanticPartPreview.PreviewContent>
|
||||
<gallery:SemanticPartPreview.PartDescriptions>
|
||||
<gallery:SemanticPartDescription
|
||||
Path="root"
|
||||
Description="{gallery:SpaceShowCaseLangResource SemanticRootDescription}" />
|
||||
<gallery:SemanticPartDescription
|
||||
Path="item"
|
||||
Description="{gallery:SpaceShowCaseLangResource SemanticItemDescription}" />
|
||||
<gallery:SemanticPartDescription
|
||||
Path="separator"
|
||||
Description="{gallery:SpaceShowCaseLangResource SemanticSeparatorDescription}" />
|
||||
</gallery:SemanticPartPreview.PartDescriptions>
|
||||
</gallery:SemanticPartPreview>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</gallery:GalleryShowCaseHost.SemanticPartsContentTemplate>
|
||||
|
||||
<gallery:ShowCasePanel Name="ExamplesContent"
|
||||
IsScrollEnabled="False"
|
||||
@@ -455,8 +500,64 @@
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</gallery:ShowCaseItem.DeferredContentTemplate>
|
||||
</gallery:ShowCaseItem>
|
||||
<gallery:ShowCaseItem Title="{gallery:SpaceShowCaseLangResource SemanticPartStyleTitle}"
|
||||
Description="{gallery:SpaceShowCaseLangResource SemanticPartStyleDescription}"
|
||||
SourceKey="space-semantic-part"
|
||||
BadgeText="{x:Static gallery:GalleryVersionInfo.DisplayVersion}"
|
||||
Span="Full"
|
||||
IsDeferredContentEnabled="True">
|
||||
<gallery:ShowCaseItem.DeferredContentTemplate>
|
||||
<DataTemplate x:DataType="viewModels:SpaceViewModel">
|
||||
<Border Classes="space-surface">
|
||||
<StackPanel Spacing="10">
|
||||
<StackPanel.Styles>
|
||||
<Style Selector="atom|Space.semantic-style-demo-object">
|
||||
<Setter Property="BorderBrush" Value="#E0000000" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="BorderDashArray" Value="4,2" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<atom:SpaceItemStyle x:SetterTargetType="atom:Button">
|
||||
<Setter Property="Background" Value="#F0F0F0" />
|
||||
<Setter Property="Padding" Value="4" />
|
||||
</atom:SpaceItemStyle>
|
||||
<atom:SpaceSeparatorStyle x:SetterTargetType="atom:TextBlock">
|
||||
<Setter Property="Foreground" Value="#FF0000" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</atom:SpaceSeparatorStyle>
|
||||
</Style>
|
||||
<Style Selector="atom|Space.semantic-style-demo-function">
|
||||
<Setter Property="Background" Value="#E6F7FF" />
|
||||
<Setter Property="BorderBrush" Value="#1890FF" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<atom:Space Classes="semantic-style-demo-object"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<atom:Space.SplitTemplate>
|
||||
<Template>
|
||||
<atom:TextBlock Text="•" />
|
||||
</Template>
|
||||
</atom:Space.SplitTemplate>
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2StyledButtonN1}" />
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2StyledButtonN2}" />
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2StyledButtonN3}" />
|
||||
</atom:Space>
|
||||
<atom:Space Classes="semantic-style-demo-function"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
SizeType="Large">
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2LargeSpaceButtonN1}" />
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2LargeSpaceButtonN2}" />
|
||||
<atom:Button Content="{gallery:SpaceShowCaseLangResource P2LargeSpaceButtonN3}" />
|
||||
</atom:Space>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</gallery:ShowCaseItem.DeferredContentTemplate>
|
||||
</gallery:ShowCaseItem>
|
||||
</gallery:ShowCasePanel>
|
||||
</gallery:GalleryStickyTabsHost>
|
||||
</gallery:GalleryShowCaseHost>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 自己添加的标记。
|
||||
|
||||
实现文档不逐行解释私有方法。若某个私有算法成为稳定维护入口,应在本节补充算法不变量,而不是把代码复述为说明书。
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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
|
||||
<Style Selector="atom|Space.semantic-demo">
|
||||
<atom:SpaceItemStyle x:SetterTargetType="Control">
|
||||
<Setter Property="Opacity" Value="0.85" />
|
||||
</atom:SpaceItemStyle>
|
||||
<atom:SpaceSeparatorStyle x:SetterTargetType="Control">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</atom:SpaceSeparatorStyle>
|
||||
</Style>
|
||||
```
|
||||
|
||||
`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<double>?` | 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`。
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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<double> ItemSpacingProperty =
|
||||
@@ -65,6 +68,27 @@ public class Space : Control,
|
||||
public static readonly StyledProperty<ITemplate<Control>?> SplitTemplateProperty =
|
||||
AvaloniaProperty.Register<Space, ITemplate<Control>?>(
|
||||
nameof(SplitTemplate));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> BackgroundProperty =
|
||||
AvaloniaProperty.Register<Space, IBrush?>(nameof(Background));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> BorderBrushProperty =
|
||||
AvaloniaProperty.Register<Space, IBrush?>(nameof(BorderBrush));
|
||||
|
||||
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
|
||||
AvaloniaProperty.Register<Space, Thickness>(nameof(BorderThickness));
|
||||
|
||||
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
|
||||
AvaloniaProperty.Register<Space, CornerRadius>(nameof(CornerRadius));
|
||||
|
||||
public static readonly StyledProperty<Thickness> PaddingProperty =
|
||||
AvaloniaProperty.Register<Space, Thickness>(nameof(Padding));
|
||||
|
||||
public static readonly StyledProperty<IReadOnlyList<double>?> BorderDashArrayProperty =
|
||||
AvaloniaProperty.Register<Space, IReadOnlyList<double>?>(nameof(BorderDashArray));
|
||||
|
||||
public static readonly StyledProperty<double> BorderDashOffsetProperty =
|
||||
AvaloniaProperty.Register<Space, double>(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<double>? BorderDashArray
|
||||
{
|
||||
get => GetValue(BorderDashArrayProperty);
|
||||
set => SetValue(BorderDashArrayProperty, value);
|
||||
}
|
||||
|
||||
public double BorderDashOffset
|
||||
{
|
||||
get => GetValue(BorderDashOffsetProperty);
|
||||
set => SetValue(BorderDashOffsetProperty, value);
|
||||
}
|
||||
|
||||
[Content]
|
||||
public AvaloniaList<Control> Children { get; } = new();
|
||||
@@ -123,7 +189,9 @@ public class Space : Control,
|
||||
|
||||
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
|
||||
private CompositeDisposable? _spacingBindings;
|
||||
|
||||
private readonly HashSet<Control> _semanticItemMarkerOwners = new();
|
||||
private BorderRenderHelper? _borderRenderHelper;
|
||||
|
||||
static Space()
|
||||
{
|
||||
AffectsMeasure<Space>(ItemSpacingProperty,
|
||||
@@ -131,8 +199,16 @@ public class Space : Control,
|
||||
OrientationProperty,
|
||||
ItemWidthProperty,
|
||||
ItemHeightProperty,
|
||||
SizeTypeProperty);
|
||||
SizeTypeProperty,
|
||||
BorderThicknessProperty,
|
||||
PaddingProperty);
|
||||
AffectsArrange<Space>(ItemsAlignmentProperty);
|
||||
AffectsRender<Space>(BackgroundProperty,
|
||||
BorderBrushProperty,
|
||||
BorderThicknessProperty,
|
||||
CornerRadiusProperty,
|
||||
BorderDashArrayProperty,
|
||||
BorderDashOffsetProperty);
|
||||
CustomizableSizeTypeControlProperty.SizeTypeProperty.OverrideDefaultValue<Space>(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)
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
<Border Name="PART_PreviewStage"
|
||||
MinHeight="360"
|
||||
Padding="32">
|
||||
<ContentPresenter Content="{TemplateBinding PreviewContent}"
|
||||
<ContentPresenter Name="PART_PreviewContentHost"
|
||||
Content="{TemplateBinding PreviewContent}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
@@ -73,6 +74,10 @@
|
||||
<Style Selector="^:compact /template/ Border#PART_InspectionPanel">
|
||||
<Setter Property="Margin" Value="16,16,16,24" />
|
||||
</Style>
|
||||
<Style Selector="^.preview-content-centered /template/ ContentPresenter#PART_PreviewContentHost">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="^:compact /template/ Border#PART_PreviewStage">
|
||||
<Setter Property="MinHeight" Value="280" />
|
||||
<Setter Property="Padding" Value="20" />
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using AtomUISpace = AtomUI.Desktop.Controls.Space;
|
||||
using AvaloniaWindow = Avalonia.Controls.Window;
|
||||
|
||||
namespace AtomUI.Desktop.Controls.Tests.Space;
|
||||
|
||||
public class SpaceRootFrameTests
|
||||
{
|
||||
static SpaceRootFrameTests()
|
||||
{
|
||||
AvaloniaTestApp.EnsureInitialized();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Root_Frame_Inset_Offsets_Children_And_Inflates_Desired_Size()
|
||||
{
|
||||
var space = new AtomUISpace
|
||||
{
|
||||
Padding = new Thickness(8),
|
||||
BorderThickness = new Thickness(2),
|
||||
Children =
|
||||
{
|
||||
new Border
|
||||
{
|
||||
Width = 40,
|
||||
Height = 20
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using var window = Show(space);
|
||||
|
||||
var child = space.Children[0];
|
||||
child.Bounds.X.ShouldBe(10);
|
||||
child.Bounds.Y.ShouldBe(10);
|
||||
space.DesiredSize.Width.ShouldBe(60, 0.01);
|
||||
space.DesiredSize.Height.ShouldBe(40, 0.01);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Default_Frame_Values_Keep_Legacy_Layout_Unchanged()
|
||||
{
|
||||
var space = new AtomUISpace
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Border
|
||||
{
|
||||
Width = 40,
|
||||
Height = 20
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using var window = Show(space);
|
||||
|
||||
var child = space.Children[0];
|
||||
child.Bounds.X.ShouldBe(0);
|
||||
child.Bounds.Y.ShouldBe(0);
|
||||
space.DesiredSize.Width.ShouldBe(40, 0.01);
|
||||
space.DesiredSize.Height.ShouldBe(20, 0.01);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Root_Frame_Appearance_Api_Is_Exposed_For_Ant_Design_Styling_Alignment()
|
||||
{
|
||||
var space = new AtomUISpace
|
||||
{
|
||||
Background = Brushes.White,
|
||||
BorderBrush = Brushes.Black,
|
||||
BorderThickness = new Thickness(2),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Padding = new Thickness(8),
|
||||
BorderDashArray = new double[] { 4, 2 },
|
||||
BorderDashOffset = 1
|
||||
};
|
||||
|
||||
space.Background.ShouldBe(Brushes.White);
|
||||
space.BorderBrush.ShouldBe(Brushes.Black);
|
||||
space.BorderThickness.ShouldBe(new Thickness(2));
|
||||
space.CornerRadius.ShouldBe(new CornerRadius(4));
|
||||
space.Padding.ShouldBe(new Thickness(8));
|
||||
space.BorderDashArray.ShouldBe(new double[] { 4, 2 }, ignoreOrder: false);
|
||||
space.BorderDashOffset.ShouldBe(1);
|
||||
}
|
||||
|
||||
private static IDisposable Show(Control content)
|
||||
{
|
||||
var window = new AvaloniaWindow
|
||||
{
|
||||
Width = 480,
|
||||
Height = 260,
|
||||
Content = content
|
||||
};
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
return new WindowLifetime(window);
|
||||
}
|
||||
|
||||
private sealed class WindowLifetime(AvaloniaWindow window) : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
window.Close();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
using System.Xml.Linq;
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Theme.Schema;
|
||||
using AtomUI.Theme.Styling;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using AtomUIButton = AtomUI.Desktop.Controls.Button;
|
||||
using AtomUICompactSpace = AtomUI.Desktop.Controls.CompactSpace;
|
||||
using AtomUICompactSpaceAddOn = AtomUI.Desktop.Controls.CompactSpaceAddOn;
|
||||
using AtomUISpace = AtomUI.Desktop.Controls.Space;
|
||||
using AvaloniaWindow = Avalonia.Controls.Window;
|
||||
|
||||
namespace AtomUI.Desktop.Controls.Tests.Space;
|
||||
|
||||
public class SpaceSemanticPartTests
|
||||
{
|
||||
private const string ItemClass = "semantic-item";
|
||||
private const string SeparatorClass = "semantic-separator";
|
||||
|
||||
static SpaceSemanticPartTests()
|
||||
{
|
||||
AvaloniaTestApp.EnsureInitialized();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Registered_Descriptor_Exposes_The_Ant_Design_Aligned_Parts()
|
||||
{
|
||||
var registry = Application.Current.ShouldNotBeNull()
|
||||
.GetThemeManager().ShouldNotBeNull()
|
||||
.SemanticParts;
|
||||
|
||||
registry.TryGetControl(typeof(AtomUISpace), out var descriptor).ShouldBeTrue();
|
||||
descriptor.ShouldNotBeNull();
|
||||
descriptor.Parts.Select(static part => part.Name)
|
||||
.ShouldBe(["root", "item", "separator"]);
|
||||
|
||||
AssertRoot(descriptor.Parts.Single(static part => part.Name == "root"));
|
||||
AssertPart(descriptor.Parts.Single(static part => part.Name == "item"),
|
||||
ItemClass,
|
||||
typeof(Control),
|
||||
"> .semantic-item");
|
||||
AssertPart(descriptor.Parts.Single(static part => part.Name == "separator"),
|
||||
SeparatorClass,
|
||||
typeof(Control),
|
||||
"> .semantic-separator");
|
||||
|
||||
registry.TryGetControl(typeof(AtomUICompactSpace), out _).ShouldBeFalse();
|
||||
registry.TryGetControl(typeof(AtomUICompactSpaceAddOn), out _).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceTheme.axaml")]
|
||||
[InlineData("src/AtomUI.Desktop.Controls/Space/Themes/CompactSpaceAddOnTheme.axaml")]
|
||||
public void Built_In_Themes_Declare_No_Static_Semantic_Markers(string relativePath)
|
||||
{
|
||||
AssertThemeMarkers(relativePath, Array.Empty<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Direct_Children_Are_Runtime_Item_Parts_And_Removal_Cleans_Owned_Markers()
|
||||
{
|
||||
var first = new AtomUIButton { Content = "First" };
|
||||
var second = new AtomUIButton { Content = "Second" };
|
||||
var space = new AtomUISpace
|
||||
{
|
||||
Children =
|
||||
{
|
||||
first,
|
||||
second
|
||||
}
|
||||
};
|
||||
|
||||
using var window = Show(space);
|
||||
|
||||
first.Classes.ShouldContain(ItemClass);
|
||||
second.Classes.ShouldContain(ItemClass);
|
||||
space.Classes.ShouldNotContain("semantic-root");
|
||||
|
||||
space.Children.Remove(first);
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
first.Classes.ShouldNotContain(ItemClass);
|
||||
second.Classes.ShouldContain(ItemClass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SplitTemplate_Creates_Runtime_Separator_Parts_Between_Items()
|
||||
{
|
||||
var space = CreateSeparatedSpace();
|
||||
|
||||
using var window = Show(space);
|
||||
|
||||
GetSemanticElements(space, ItemClass).Length.ShouldBe(3);
|
||||
GetSemanticElements(space, SeparatorClass).Length.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generated_Route_Styles_Apply_To_Direct_Items_And_Separators()
|
||||
{
|
||||
var space = CreateSeparatedSpace();
|
||||
space.Classes.Add("semantic-owner");
|
||||
|
||||
var ownerStyle = new Style(selector => selector.OfType<AtomUISpace>().Class("semantic-owner"));
|
||||
ownerStyle.Children.Add(new SpaceItemStyle
|
||||
{
|
||||
Setters = { new Setter(Control.TagProperty, "item") }
|
||||
});
|
||||
ownerStyle.Children.Add(new SpaceSeparatorStyle
|
||||
{
|
||||
Setters = { new Setter(Control.TagProperty, "separator") }
|
||||
});
|
||||
space.Styles.Add(ownerStyle);
|
||||
|
||||
using var window = Show(space);
|
||||
|
||||
GetSemanticElements(space, ItemClass)
|
||||
.OfType<Control>()
|
||||
.ShouldAllBe(static item => Equals(item.Tag, "item"));
|
||||
GetSemanticElements(space, SeparatorClass)
|
||||
.OfType<Control>()
|
||||
.ShouldAllBe(static separator => Equals(separator.Tag, "separator"));
|
||||
}
|
||||
|
||||
private static AtomUISpace CreateSeparatedSpace()
|
||||
{
|
||||
return new AtomUISpace
|
||||
{
|
||||
SplitTemplate = new SeparatorTemplate(),
|
||||
Children =
|
||||
{
|
||||
new AtomUIButton { Content = "Primary" },
|
||||
new AtomUIButton { Content = "Default" },
|
||||
new AtomUIButton { Content = "Dashed" }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void AssertRoot(SemanticPartDescriptor part)
|
||||
{
|
||||
part.Path.ShouldBe("root");
|
||||
part.SelectorClass.ShouldBeNull();
|
||||
part.SelectorRoute.ShouldBeNull();
|
||||
part.ContractType.ShouldBe(typeof(AtomUISpace));
|
||||
part.Cardinality.ShouldBe(SemanticPartCardinality.Single);
|
||||
part.Customization.ShouldBe(SemanticPartCustomization.Root);
|
||||
part.CrossVisualRoot.ShouldBeFalse();
|
||||
part.RuntimeCreated.ShouldBeFalse();
|
||||
part.Since.ShouldBe("6.0");
|
||||
}
|
||||
|
||||
private static void AssertPart(
|
||||
SemanticPartDescriptor part,
|
||||
string selectorClass,
|
||||
Type contractType,
|
||||
string selectorRoute)
|
||||
{
|
||||
part.Path.ShouldBe(part.Name);
|
||||
part.SelectorClass.ShouldBe(selectorClass);
|
||||
part.SelectorRoute.ShouldBe(selectorRoute);
|
||||
part.ContractType.ShouldBe(contractType);
|
||||
part.Cardinality.ShouldBe(SemanticPartCardinality.Multiple);
|
||||
part.Customization.ShouldBe(SemanticPartCustomization.Selector);
|
||||
part.CrossVisualRoot.ShouldBeFalse();
|
||||
part.RuntimeCreated.ShouldBeTrue();
|
||||
part.StyleType.ShouldNotBeNull();
|
||||
part.Since.ShouldBe("6.0");
|
||||
}
|
||||
|
||||
private static Control[] GetSemanticElements(AtomUISpace space, string semanticClass)
|
||||
{
|
||||
return space.GetVisualDescendants()
|
||||
.OfType<Control>()
|
||||
.Where(control => control.Classes.Contains(semanticClass))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static IDisposable Show(Control content)
|
||||
{
|
||||
var window = new AvaloniaWindow
|
||||
{
|
||||
Width = 480,
|
||||
Height = 260,
|
||||
Content = content
|
||||
};
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
return new WindowLifetime(window);
|
||||
}
|
||||
|
||||
private static void AssertThemeMarkers(string relativePath, string[] expectedMarkers)
|
||||
{
|
||||
var document = XDocument.Load(GetRepoFile(relativePath), LoadOptions.SetLineInfo);
|
||||
var literalSemanticMarkers = document.Descendants()
|
||||
.Attributes()
|
||||
.Where(static attribute =>
|
||||
attribute.Name.LocalName == "Classes" &&
|
||||
attribute.Value.Split(
|
||||
(char[]?)null,
|
||||
StringSplitOptions.RemoveEmptyEntries)
|
||||
.Any(static value => value.StartsWith(
|
||||
"semantic-",
|
||||
StringComparison.Ordinal)))
|
||||
.ToArray();
|
||||
var classPropertyMarkers = document.Descendants()
|
||||
.SelectMany(static element => element.Attributes()
|
||||
.Where(static attribute => attribute.Name.LocalName.StartsWith(
|
||||
"Classes.semantic-",
|
||||
StringComparison.Ordinal))
|
||||
.Select(attribute => (Element: element, Attribute: attribute)))
|
||||
.ToArray();
|
||||
|
||||
literalSemanticMarkers.ShouldBeEmpty();
|
||||
classPropertyMarkers.ShouldAllBe(static marker =>
|
||||
string.Equals(marker.Attribute.Value, "true", StringComparison.OrdinalIgnoreCase));
|
||||
classPropertyMarkers.Select(static marker =>
|
||||
$"{marker.Attribute.Name.LocalName["Classes.".Length..]}:{marker.Element.Name.LocalName}")
|
||||
.OrderBy(static value => value, StringComparer.Ordinal)
|
||||
.ShouldBe(expectedMarkers);
|
||||
}
|
||||
|
||||
private static string GetRepoFile(string relativePath)
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
var candidate = Path.Combine(directory.FullName, relativePath);
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Could not locate repository file '{relativePath}'.");
|
||||
}
|
||||
|
||||
private sealed class SeparatorTemplate : ITemplate<Control>
|
||||
{
|
||||
public Control Build()
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Width = 1,
|
||||
Height = 16
|
||||
};
|
||||
}
|
||||
|
||||
object ITemplate.Build() => Build();
|
||||
}
|
||||
|
||||
private sealed class WindowLifetime(AvaloniaWindow window) : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
window.Close();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ AtomUIGallery.Localization.SelectShowCaseLangResourceKind|BasicTitle,BasicDescri
|
||||
AtomUIGallery.Localization.SeparatorShowCaseLangResourceKind|ScenarioExamples,ComponentCategory,ComponentStatusStable,PageSubtitle,PageDescription,HorizontalTitle,HorizontalDescription,DividerWithTitleTitle,DividerWithTitleDescription,PlainTextTitle,PlainTextDescription,SpacingSizeTitle,SpacingSizeDescription,VerticalTitle,VerticalDescription,VariantTitle,VariantDescription,SemanticPartStyleTitle,SemanticPartStyleDescription,SemanticRootDescription,SemanticRailDescription,SemanticContentDescription,SemanticPartAllPartsTitle,SemanticPartTitlePlacementTitle,SemanticPartDashedRailTitle,SemanticPartSmallSizeTitle,P2TitleText,P2TitleLeftText,P2TitleRightText,P2TitleLeftTextWithN0Orientationmargin,P2TitleRightTextWithN50pxOrientationmargin,P2TitleLeftText2,P2TitleRightText2,P2TitleSolid,P2TitleDotted,P2TitleDashed,P2TextLoremIpsumDolorSitAmetConsecteturAdipiscingElit,P2TextItem1,P2TextItem2,P2TextItem3
|
||||
AtomUIGallery.Localization.SkeletonShowCaseLangResourceKind|BasicTitle,BasicDescription,ComplexCombinationTitle,ComplexCombinationDescription,ActiveAnimationTitle,ActiveAnimationDescription,ButtonAvatarInputImageNodeTitle,ButtonAvatarInputImageNodeDescription,ContainsSubComponentTitle,ContainsSubComponentDescription,SemanticPartStyleTitle,SemanticPartStyleDescription,SemanticRootDescription,SemanticHeaderDescription,SemanticSectionDescription,SemanticAvatarDescription,SemanticTitleDescription,SemanticParagraphDescription,SemanticElementRootDescription,SemanticElementContentDescription,PageSubtitle,PageDescription,ComponentCategory,ComponentStatusStable,ScenarioExamples,P2TextActive,P2TextButtonAndInputBlock,P2TextSize,P2ContentDefault,P2ContentLarge,P2ContentSmall,P2TextButtonShape,P2ContentSquare,P2ContentRound,P2ContentCircle,P2TextAvatarShape,P2TextAntDesignADesignLanguage,P2TextWeSupplyASeriesOfDesignPrinciplesPractical,P2ContentShowSkeleton
|
||||
AtomUIGallery.Localization.SliderShowCaseLangResourceKind|ScenarioExamples,ComponentCategory,ComponentStatusStable,PageSubtitle,PageDescription,BasicTitle,BasicDescription,MultiHandleTitle,MultiHandleDescription,DisabledHandleTitle,DisabledHandleDescription,RangeValuesBindingTitle,RangeValuesBindingDescription,CustomizeTooltipTitle,CustomizeTooltipDescription,VerticalTitle,VerticalDescription,GraduatedSliderTitle,GraduatedSliderDescription,P2TextEnabled,P2TextIncludedTrue,P2TextIncludedFalse,P2TextBoundRangeValues,P2ContentSetRange,P2ContentClear,P2ContentDisabledHandle1,P2ContentDisabledHandle2,P2ContentDisabledHandle3,SemanticHandleDescription,SemanticPartStyleTitle,SemanticPartStyleDescription,SemanticRailDescription,SemanticRootDescription,SemanticTrackDescription,SemanticTracksDescription
|
||||
AtomUIGallery.Localization.SpaceShowCaseLangResourceKind|BasicTitle,BasicDescription,VerticalSpaceTitle,VerticalSpaceDescription,SizeTitle,SizeDescription,AlignTitle,AlignDescription,WrapTitle,WrapDescription,SplitTitle,SplitDescription,CompactFormTitle,CompactFormDescription,CompactButtonTitle,CompactButtonDescription,VerticalCompactTitle,VerticalCompactDescription,ScenarioBasic,ScenarioSize,ScenarioAlign,ScenarioCompactForm,ScenarioCompactButton,ScenarioExamples,PageSubtitle,PageDescription,ComponentCategory,ComponentStatusStable,P2ConfirmContentAreYouSureToDeleteThisTask,P2OkTextOk,P2CancelTextCancel,P2TitleDeleteTheTask,P2HeaderCard,P2HeaderReport,P2HeaderMail,P2HeaderMobile,P2HeaderN1stItem,P2HeaderN2ndItem,P2HeaderN3rdItem,P2HeaderZhejiang,P2HeaderJiangsu,P2TextXihuDistrictHangzhou,P2TextN1,P2HeaderOption1,P2HeaderOption2,P2TextInputContent,P2HeaderOption1N1,P2HeaderOption2N1,P2HeaderOption2N2,P2HeaderBetween,P2HeaderExcept,P2PlaceholderTextMinimum,P2PlaceholderTextText,P2PlaceholderTextMaximum,P2HeaderSignUp,P2HeaderSignIn,P2PlaceholderTextEmail,P2HeaderTextN1,P2HeaderTextN2,P2PlaceholderTextSelectTime,P2PlaceholderTextSelectAddress,P2HeaderHangzhou,P2HeaderWestLake,P2HeaderLingyinShi,P2HeaderNanjing,P2HeaderZhongHuaMen,P2PlaceholderTextStartTime,P2SecondaryPlaceholderTextEndTime,P2PlaceholderTextPleaseSelect,P2HeaderParentN1,P2HeaderParentN1N0,P2HeaderLeaf1,P2HeaderLeaf2,P2HeaderParentN1N1,P2HeaderLeaf3,P2PlaceholderTextInputHere,P2PlaceholderTextAnotherInput,P2TextCenter,P2ContentPrimary,P2TextBlock,P2ContentButton,P2ContentLink,P2TextSpace,P2ContentClickToUpload,P2ContentConfirm,P2TextCardContent,P2ContentButtonN1,P2ContentButtonN2,P2ContentButtonN3,P2ContentButtonN4,P2ContentSubmit,P2ContentSearch,P2ContentSmall,P2ContentMiddle,P2ContentLarge,P2ContentCustom,P2ContentDefault,P2ContentDashed,P2ToolTipTipCopyGitUrl,P2ToolTipTipLike,P2ToolTipTipComment,P2ToolTipTipStar,P2ToolTipTipHeart,P2ToolTipTipShare,P2ToolTipTipDownload,P2ToolTipTipTooltip
|
||||
AtomUIGallery.Localization.SpaceShowCaseLangResourceKind|BasicTitle,BasicDescription,VerticalSpaceTitle,VerticalSpaceDescription,SizeTitle,SizeDescription,AlignTitle,AlignDescription,WrapTitle,WrapDescription,SplitTitle,SplitDescription,CompactFormTitle,CompactFormDescription,CompactButtonTitle,CompactButtonDescription,VerticalCompactTitle,VerticalCompactDescription,ScenarioBasic,ScenarioSize,ScenarioAlign,ScenarioCompactForm,ScenarioCompactButton,ScenarioExamples,PageSubtitle,PageDescription,ComponentCategory,ComponentStatusStable,P2ConfirmContentAreYouSureToDeleteThisTask,P2OkTextOk,P2CancelTextCancel,P2TitleDeleteTheTask,P2HeaderCard,P2HeaderReport,P2HeaderMail,P2HeaderMobile,P2HeaderN1stItem,P2HeaderN2ndItem,P2HeaderN3rdItem,P2HeaderZhejiang,P2HeaderJiangsu,P2TextXihuDistrictHangzhou,P2TextN1,P2HeaderOption1,P2HeaderOption2,P2TextInputContent,P2HeaderOption1N1,P2HeaderOption2N1,P2HeaderOption2N2,P2HeaderBetween,P2HeaderExcept,P2PlaceholderTextMinimum,P2PlaceholderTextText,P2PlaceholderTextMaximum,P2HeaderSignUp,P2HeaderSignIn,P2PlaceholderTextEmail,P2HeaderTextN1,P2HeaderTextN2,P2PlaceholderTextSelectTime,P2PlaceholderTextSelectAddress,P2HeaderHangzhou,P2HeaderWestLake,P2HeaderLingyinShi,P2HeaderNanjing,P2HeaderZhongHuaMen,P2PlaceholderTextStartTime,P2SecondaryPlaceholderTextEndTime,P2PlaceholderTextPleaseSelect,P2HeaderParentN1,P2HeaderParentN1N0,P2HeaderLeaf1,P2HeaderLeaf2,P2HeaderParentN1N1,P2HeaderLeaf3,P2PlaceholderTextInputHere,P2PlaceholderTextAnotherInput,P2TextCenter,P2ContentPrimary,P2TextBlock,P2ContentButton,P2ContentLink,P2TextSpace,P2ContentClickToUpload,P2ContentConfirm,P2TextCardContent,P2ContentButtonN1,P2ContentButtonN2,P2ContentButtonN3,P2ContentButtonN4,P2ContentSubmit,P2ContentSearch,P2ContentSmall,P2ContentMiddle,P2ContentLarge,P2ContentCustom,P2ContentDefault,P2ContentDashed,P2ToolTipTipCopyGitUrl,P2ToolTipTipLike,P2ToolTipTipComment,P2ToolTipTipStar,P2ToolTipTipHeart,P2ToolTipTipShare,P2ToolTipTipDownload,P2ToolTipTipTooltip,P2LargeSpaceButtonN1,P2LargeSpaceButtonN2,P2LargeSpaceButtonN3,P2StyledButtonN1,P2StyledButtonN2,P2StyledButtonN3,SemanticRootDescription,SemanticItemDescription,SemanticSeparatorDescription,SemanticPartStyleTitle,SemanticPartStyleDescription
|
||||
AtomUIGallery.Localization.SpinShowCaseLangResourceKind|BasicUsageTitle,BasicUsageDescription,SizeTitle,SizeDescription,CustomIndicatorTitle,CustomIndicatorDescription,CustomizedDescriptionTitle,CustomizedDescriptionDescription,EmbeddedModeTitle,EmbeddedModeDescription,SemanticPartStyleTitle,SemanticPartStyleDescription,SemanticRootDescription,SemanticContainerDescription,SemanticMaskDescription,SemanticSectionDescription,SemanticIndicatorDescription,SemanticDescriptionDescription,SemanticIndicatorRootDescription,SemanticIndicatorContentDescription,PageSubtitle,PageDescription,ComponentCategory,ComponentStatusStable,ScenarioExamples,P2DescriptionFurtherDetailsAboutTheContextOfThisAlert,P2TextLoadingState,P2TipLoading,P2MessageAlertMessageTitle
|
||||
AtomUIGallery.Localization.SplashShowCaseLangResourceKind|ScenarioExamples,ComponentCategory,ComponentStatusPreview,ComponentIntroducedVersion,PageSubtitle,PageDescription,BasicTitle,BasicDescription,DeterminateTitle,DeterminateDescription,StatusTitle,StatusDescription,ComposedTitle,ComposedDescription,WindowServiceTitle,WindowServiceDescription,P2SubtitleDesktopBoot,P2MessagePreparingShell,P2DetailLoadingThemeAndResources,P2MessageLoadingModules,P2DetailProgress,P2MessageReady,P2DetailReady,P2MessageError,P2DetailError,P2FooterStaticPreview,P2ContentModuleCore,P2ContentModuleTheme,P2ContentModuleGallery,P2FooterDesktopOnly,P2ContentShowWindowSplash,P2WindowSplashSubtitle,P2WindowSplashMessageStarting,P2WindowSplashDetailStarting,P2WindowSplashMessageLoadingTheme,P2WindowSplashMessageLoadingControls,P2WindowSplashMessageLoadingRoutes,P2WindowSplashMessageWarmingCache,P2WindowSplashMessageFinalizing,P2WindowSplashDetailProgress,P2WindowSplashMessageComplete,P2WindowSplashDetailComplete,P2WindowSplashFooter,ApiStaticShowAsync,ApiServiceShowAsync,ApiMethodSetProgress,ApiOptionMinimumShowDuration
|
||||
AtomUIGallery.Localization.SplitButtonShowCaseLangResourceKind|BasicTitle,BasicDescription,SizeTitle,SizeDescription,DangerButtonsTitle,DangerButtonsDescription,CustomIconTitle,CustomIconDescription,FlyoutTriggerTypeTitle,FlyoutTriggerTypeDescription,P2HeaderCut,P2HeaderCopy,P2HeaderDelete,P2ContentHoverMe,P2ContentLarge,P2ContentMiddle,P2ContentSmall,P2ContentCustom,P2ContentDefault,P2ContentPrimary,P2ContentClickMe,PageSubtitle,PageDescription,ComponentCategory,ComponentStatusStable,ScenarioExamples
|
||||
|
||||
@@ -120,7 +120,7 @@ public class GalleryCatalogCoverageTests
|
||||
.ToArray();
|
||||
|
||||
sourceFiles.Length.ShouldBe(81);
|
||||
sourceFiles.Sum(CountUnits).ShouldBe(4244);
|
||||
sourceFiles.Sum(CountUnits).ShouldBe(4255);
|
||||
foreach (var sourcePath in sourceFiles)
|
||||
{
|
||||
var targetPath = Path.Combine(
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
count: 9
|
||||
sha256: 5ebdbdabb01cc71c23baba0b5824a62e4a3527487732f8d8140bf82d73200336
|
||||
count: 10
|
||||
sha256: a27382e8b0a83b2b5a9156fd526aa76684ff1af9cbfd937feba0ab5d4bf7e945
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AtomUI.Toolkits.GalleryBase.Controls;
|
||||
using AtomUIGallery.ShowCases.Space;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using ReactiveUI;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using AvaloniaTextBlock = Avalonia.Controls.TextBlock;
|
||||
using AvaloniaWindow = Avalonia.Controls.Window;
|
||||
using AtomUIButton = AtomUI.Desktop.Controls.Button;
|
||||
using AtomUISpace = AtomUI.Desktop.Controls.Space;
|
||||
|
||||
namespace AtomUIGallery.Tests.ShowCases;
|
||||
|
||||
@@ -26,7 +41,9 @@ public class SpaceShowCasePageTests
|
||||
source.ShouldNotContain("Tag=\"Examples\"");
|
||||
source.ShouldNotContain("Tag=\"Api\"");
|
||||
source.ShouldNotContain("Tag=\"DesignToken\"");
|
||||
source.ShouldContain("<gallery:GalleryStickyTabsHost");
|
||||
source.ShouldContain("<gallery:GalleryShowCaseHost");
|
||||
source.ShouldNotContain("<gallery:GalleryStickyTabsHost");
|
||||
source.ShouldContain("<gallery:GalleryShowCaseHost.SemanticPartsContentTemplate>");
|
||||
source.ShouldContain("StickyContentPadding=\"28,0,28,0\"");
|
||||
source.ShouldNotContain("<atom:TabStrip Name=\"ScenarioTabs\"");
|
||||
source.ShouldNotContain("<ContentControl Name=\"ScenarioContentHost\">");
|
||||
@@ -36,16 +53,28 @@ public class SpaceShowCasePageTests
|
||||
source.ShouldContain("InitialDeferredLoadItemCount=\"4\"");
|
||||
source.ShouldContain("DeferredLoadBatchSize=\"2\"");
|
||||
source.ShouldContain("ContentMargin=\"28,10,28,28\"");
|
||||
source.ShouldContain("Name=\"SpaceSemanticPreview\"");
|
||||
source.ShouldContain("SemanticOwner=\"{Binding #SpaceSemanticOwner}\"");
|
||||
source.ShouldContain("SemanticOwnerType=\"{x:Type atom:Space}\"");
|
||||
source.ShouldContain("Path=\"root\"");
|
||||
source.ShouldContain("Path=\"item\"");
|
||||
source.ShouldContain("Path=\"separator\"");
|
||||
source.ShouldContain("SpaceShowCaseLangResource SemanticRootDescription");
|
||||
source.ShouldContain("SpaceShowCaseLangResource SemanticItemDescription");
|
||||
source.ShouldContain("SpaceShowCaseLangResource SemanticSeparatorDescription");
|
||||
source.ShouldContain("SpaceShowCaseLangResource SemanticPartStyleTitle");
|
||||
source.ShouldContain("SpaceShowCaseLangResource SemanticPartStyleDescription");
|
||||
source.ShouldContain("SourceKey=\"space-semantic-part\"");
|
||||
source.ShouldNotContain("Selector=\"atom|TextBlock.info-label\"");
|
||||
source.ShouldNotContain("Selector=\"atom|TextBlock.info-value\"");
|
||||
CountOccurrences(source, "Classes=\"info-label\"").ShouldBe(0);
|
||||
CountOccurrences(source, "Classes=\"info-value\"").ShouldBe(0);
|
||||
source.ShouldNotContain("LineHeight=\"22\"");
|
||||
source.ShouldContain("Description=\"{gallery:SpaceShowCaseLangResource PageDescription}\"");
|
||||
CountShowCaseItemElements(source).ShouldBe(9);
|
||||
CountOccurrences(source, "IsDeferredContentEnabled=\"True\"").ShouldBe(9);
|
||||
CountOccurrences(source, "<gallery:ShowCaseItem.DeferredContentTemplate>").ShouldBe(9);
|
||||
CountOccurrences(source, "DataTemplate x:DataType=\"viewModels:SpaceViewModel\"").ShouldBe(9);
|
||||
CountShowCaseItemElements(source).ShouldBe(10);
|
||||
CountOccurrences(source, "IsDeferredContentEnabled=\"True\"").ShouldBe(10);
|
||||
CountOccurrences(source, "<gallery:ShowCaseItem.DeferredContentTemplate>").ShouldBe(10);
|
||||
CountOccurrences(source, "DataTemplate x:DataType=\"viewModels:SpaceViewModel\"").ShouldBe(11);
|
||||
source.ShouldContain("SpaceShowCaseLangResource BasicTitle");
|
||||
source.ShouldContain("SpaceShowCaseLangResource SizeTitle");
|
||||
source.ShouldContain("SpaceShowCaseLangResource AlignTitle");
|
||||
@@ -72,6 +101,228 @@ public class SpaceShowCasePageTests
|
||||
ComputeSha256(normalized).ShouldBe(ReadSnapshotHash(approved));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Space_Semantic_Preview_Is_Materialized_On_Tab_Selection()
|
||||
{
|
||||
AvaloniaTestApp.EnsureInitialized();
|
||||
|
||||
var page = new SpaceShowCase
|
||||
{
|
||||
DataContext = new SpaceViewModel(new TestScreen())
|
||||
};
|
||||
|
||||
ShowInWindow(page, 1280, 900, () =>
|
||||
{
|
||||
page.GetVisualDescendants().OfType<SemanticPartPreview>().ShouldBeEmpty();
|
||||
page.GetVisualDescendants()
|
||||
.OfType<AtomUISpace>()
|
||||
.ShouldNotContain(static space => space.Name == "SpaceSemanticOwner");
|
||||
|
||||
var host = page.GetVisualDescendants().OfType<GalleryShowCaseHost>().Single();
|
||||
host.SelectedTab = GalleryShowCaseTab.SemanticParts;
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
page.GetVisualDescendants().OfType<SemanticPartPreview>().ShouldHaveSingleItem();
|
||||
var preview = page.GetVisualDescendants()
|
||||
.OfType<SemanticPartPreview>()
|
||||
.Single(static candidate => candidate.Name == "SpaceSemanticPreview");
|
||||
var semanticSpace = preview.SemanticOwner.ShouldBeOfType<AtomUISpace>();
|
||||
semanticSpace.Name.ShouldBe("SpaceSemanticOwner");
|
||||
semanticSpace.HorizontalAlignment.ShouldBe(HorizontalAlignment.Center);
|
||||
semanticSpace.VerticalAlignment.ShouldBe(VerticalAlignment.Center);
|
||||
|
||||
var previewStageHost = preview.GetVisualDescendants()
|
||||
.OfType<ContentPresenter>()
|
||||
.Single(static candidate => candidate.Name == "PART_PreviewContentHost");
|
||||
previewStageHost.VerticalAlignment.ShouldBe(VerticalAlignment.Stretch);
|
||||
previewStageHost.VerticalContentAlignment.ShouldBe(VerticalAlignment.Center);
|
||||
semanticSpace.GetVisualDescendants()
|
||||
.Count(static item => item.Classes.Contains("semantic-item"))
|
||||
.ShouldBe(3);
|
||||
semanticSpace.GetVisualDescendants()
|
||||
.Count(static separator => separator.Classes.Contains("semantic-separator"))
|
||||
.ShouldBe(2);
|
||||
|
||||
host.SelectedTab = GalleryShowCaseTab.Examples;
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
page.GetVisualDescendants().OfType<SemanticPartPreview>().ShouldBeEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Space_ShowCase_Declares_The_Semantic_Preview_And_Style_Example()
|
||||
{
|
||||
var source = ReadRepoFile(
|
||||
"controlgallery/AtomUIGallery/ShowCases/Layout/Space/Views/SpaceShowCase.axaml");
|
||||
var english = ReadRepoFile(
|
||||
"controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/en-US.xlf");
|
||||
var semanticSource = ExtractShowCaseItemBySourceKey(source, "space-semantic-part");
|
||||
|
||||
source.ShouldContain("<gallery:GalleryShowCaseHost.SemanticPartsContentTemplate>");
|
||||
source.ShouldContain("Name=\"SpaceSemanticPreview\"");
|
||||
source.ShouldContain("SemanticOwner=\"{Binding #SpaceSemanticOwner}\"");
|
||||
source.ShouldContain("SemanticOwnerType=\"{x:Type atom:Space}\"");
|
||||
source.ShouldContain("HorizontalAlignment=\"Center\"");
|
||||
CountOccurrences(source, "<gallery:SemanticPartDescription").ShouldBe(3);
|
||||
foreach (var path in new[] { "root", "item", "separator" })
|
||||
{
|
||||
source.ShouldContain($"Path=\"{path}\"");
|
||||
}
|
||||
|
||||
semanticSource.ShouldContain("SourceKey=\"space-semantic-part\"");
|
||||
semanticSource.ShouldContain("BadgeText=\"{x:Static gallery:GalleryVersionInfo.DisplayVersion}\"");
|
||||
semanticSource.ShouldContain("SpaceShowCaseLangResource SemanticPartStyleTitle");
|
||||
semanticSource.ShouldContain("SpaceShowCaseLangResource SemanticPartStyleDescription");
|
||||
semanticSource.ShouldContain("Selector=\"atom|Space.semantic-style-demo-object\"");
|
||||
semanticSource.ShouldContain("Selector=\"atom|Space.semantic-style-demo-function\"");
|
||||
semanticSource.ShouldContain("Property=\"Background\"");
|
||||
semanticSource.ShouldContain("Value=\"#F0F0F0\"");
|
||||
semanticSource.ShouldContain("Value=\"#E6F7FF\"");
|
||||
semanticSource.ShouldContain("Property=\"BorderBrush\"");
|
||||
semanticSource.ShouldContain("Value=\"#E0000000\"");
|
||||
semanticSource.ShouldContain("Value=\"#1890FF\"");
|
||||
semanticSource.ShouldContain("Property=\"BorderThickness\"");
|
||||
semanticSource.ShouldContain("Property=\"BorderDashArray\"");
|
||||
semanticSource.ShouldContain("Value=\"4,2\"");
|
||||
semanticSource.ShouldContain("Property=\"Foreground\"");
|
||||
semanticSource.ShouldContain("Value=\"#FF0000\"");
|
||||
semanticSource.ShouldContain("Property=\"FontWeight\"");
|
||||
CountOccurrences(semanticSource, "<atom:SpaceItemStyle ").ShouldBe(1);
|
||||
CountOccurrences(semanticSource, "<atom:SpaceSeparatorStyle ").ShouldBe(1);
|
||||
semanticSource.ShouldContain("x:SetterTargetType=\"atom:Button\"");
|
||||
semanticSource.ShouldContain("x:SetterTargetType=\"atom:TextBlock\"");
|
||||
CountOccurrences(semanticSource, "<atom:Space ").ShouldBe(2);
|
||||
CountOccurrences(semanticSource, "Orientation=\"Horizontal\"").ShouldBe(2);
|
||||
CountOccurrences(semanticSource, "HorizontalAlignment=\"Left\"").ShouldBe(2);
|
||||
CountOccurrences(semanticSource, "SizeType=\"Large\"").ShouldBe(1);
|
||||
CountOccurrences(semanticSource, "Text=\"•\"").ShouldBe(1);
|
||||
CountOccurrences(semanticSource, "Classes=\"space-surface\"").ShouldBe(1);
|
||||
semanticSource.ShouldContain("SpaceShowCaseLangResource P2StyledButtonN1");
|
||||
semanticSource.ShouldContain("SpaceShowCaseLangResource P2LargeSpaceButtonN1");
|
||||
semanticSource.ShouldNotContain("/template/");
|
||||
semanticSource.ShouldNotContain("semantic dom", Case.Insensitive);
|
||||
english.ShouldContain("Custom Semantic Part styling");
|
||||
english.ShouldContain(
|
||||
"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.");
|
||||
english.ShouldNotContain("semantic dom", Case.Insensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Space_Semantic_Style_Example_Applies_The_Official_Style_Values()
|
||||
{
|
||||
AvaloniaTestApp.EnsureInitialized();
|
||||
|
||||
var page = new SpaceShowCase
|
||||
{
|
||||
DataContext = new SpaceViewModel(new TestScreen())
|
||||
};
|
||||
|
||||
ShowInWindow(page, 1280, 900, () =>
|
||||
{
|
||||
var panel = page.GetVisualDescendants().OfType<ShowCasePanel>().Single();
|
||||
var item = panel.Children
|
||||
.OfType<ShowCaseItem>()
|
||||
.Single(static candidate => candidate.SourceKey == "space-semantic-part");
|
||||
item.BadgeText.ShouldBe(GalleryVersionInfo.DisplayVersion);
|
||||
item.MaterializeDeferredContent();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
var demos = page.GetVisualDescendants()
|
||||
.OfType<AtomUISpace>()
|
||||
.Where(static space => space.Classes.Contains("semantic-style-demo-object") ||
|
||||
space.Classes.Contains("semantic-style-demo-function"))
|
||||
.ToArray();
|
||||
demos.Length.ShouldBe(2);
|
||||
|
||||
var objectDemo = demos.Single(static space => space.Classes.Contains("semantic-style-demo-object"));
|
||||
AssertSolidColor(objectDemo.BorderBrush, "#E0000000");
|
||||
objectDemo.BorderThickness.ShouldBe(new Thickness(2));
|
||||
objectDemo.BorderDashArray.ShouldNotBeNull();
|
||||
objectDemo.BorderDashArray.ShouldBe(new double[] { 4, 2 }, ignoreOrder: false);
|
||||
objectDemo.Padding.ShouldBe(new Thickness(8));
|
||||
|
||||
var objectItems = objectDemo.GetVisualDescendants()
|
||||
.OfType<AtomUIButton>()
|
||||
.Where(static button => button.Classes.Contains("semantic-item"))
|
||||
.ToArray();
|
||||
objectItems.Length.ShouldBe(3);
|
||||
foreach (var button in objectItems)
|
||||
{
|
||||
AssertSolidColor(button.Background, "#F0F0F0");
|
||||
button.Padding.ShouldBe(new Thickness(4));
|
||||
}
|
||||
|
||||
var objectSeparators = objectDemo.GetVisualDescendants()
|
||||
.OfType<AvaloniaTextBlock>()
|
||||
.Where(static separator => separator.Classes.Contains("semantic-separator"))
|
||||
.ToArray();
|
||||
objectSeparators.Length.ShouldBe(2);
|
||||
foreach (var separator in objectSeparators)
|
||||
{
|
||||
AssertSolidColor(separator.Foreground, "#FF0000");
|
||||
separator.FontWeight.ShouldBe(FontWeight.Bold);
|
||||
}
|
||||
|
||||
var functionDemo = demos.Single(static space => space.Classes.Contains("semantic-style-demo-function"));
|
||||
AssertSolidColor(functionDemo.Background, "#E6F7FF");
|
||||
AssertSolidColor(functionDemo.BorderBrush, "#1890FF");
|
||||
functionDemo.Padding.ShouldBe(new Thickness(8));
|
||||
functionDemo.SizeType.ShouldBe(AtomUI.CustomizableSizeType.Large);
|
||||
functionDemo.BorderThickness.ShouldBe(new Thickness(0));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Semantic_Part_Localization_Uses_Approved_Copy()
|
||||
{
|
||||
var zhCn = XliffTestDocument.Read(
|
||||
"controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-CN.xlf");
|
||||
zhCn["SemanticRootDescription"].ShouldBe("根元素,包含 flex 布局、间隙设置、对齐方式、换行等间距容器的基础样式");
|
||||
zhCn["SemanticItemDescription"].ShouldBe("包裹的子组件,包含间距项的布局和样式,为每个子元素提供包装用于内联对齐");
|
||||
zhCn["SemanticSeparatorDescription"].ShouldBe("分隔符,包含分隔元素的样式");
|
||||
zhCn["SemanticPartStyleTitle"].ShouldBe("自定义 Semantic Part 样式");
|
||||
zhCn["SemanticPartStyleDescription"].ShouldBe(
|
||||
"对齐 Ant Design style-class 示例:通过生成的 Semantic Part 样式自定义 root 边框、item 背景与 separator 颜色,并为 root 添加虚线边框。");
|
||||
|
||||
var zhTw = XliffTestDocument.Read(
|
||||
"controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/zh-TW.xlf");
|
||||
zhTw["SemanticRootDescription"].ShouldBe("根元素,包含 flex 佈局、間隙設定、對齊方式、換行等間距容器的基礎樣式");
|
||||
zhTw["SemanticItemDescription"].ShouldBe("包裹的子元件,包含間距項的佈局和樣式,為每個子元素提供包裝用於內聯對齊");
|
||||
zhTw["SemanticSeparatorDescription"].ShouldBe("分隔符,包含分隔元素的樣式");
|
||||
zhTw["SemanticPartStyleTitle"].ShouldBe("自定義 Semantic Part 樣式");
|
||||
zhTw["SemanticPartStyleDescription"].ShouldBe(
|
||||
"對齊 Ant Design style-class 範例:透過生成的 Semantic Part 樣式自訂 root 邊框、item 背景與 separator 顏色,並為 root 加上虛線邊框。");
|
||||
|
||||
var enUs = XliffTestDocument.Read(
|
||||
"controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/en-US.xlf");
|
||||
enUs["SemanticRootDescription"].ShouldBe(
|
||||
"Root element with flex layout, gap settings, alignment, wrap and other spacing container basic styles");
|
||||
enUs["SemanticItemDescription"].ShouldBe(
|
||||
"Wrapped item element with spacing item layout and styles, providing wrapper for each child element for inline alignment");
|
||||
enUs["SemanticSeparatorDescription"].ShouldBe("Separator element with divider styling");
|
||||
enUs["SemanticPartStyleTitle"].ShouldBe("Custom Semantic Part styling");
|
||||
enUs["SemanticPartStyleDescription"].ShouldBe(
|
||||
"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.");
|
||||
|
||||
var ptBr = XliffTestDocument.Read(
|
||||
"controlgallery/AtomUIGallery/ShowCases/Layout/Space/Localization/pt-BR.xlf");
|
||||
ptBr["SemanticRootDescription"].ShouldBe(
|
||||
"Elemento raiz com layout flex, configurações de espaçamento, alinhamento, quebra e outros estilos básicos do contêiner de espaçamento");
|
||||
ptBr["SemanticItemDescription"].ShouldBe(
|
||||
"Elemento de item encapsulado com layout e estilos de espaçamento, fornecendo um invólucro para cada elemento filho para alinhamento em linha");
|
||||
ptBr["SemanticSeparatorDescription"].ShouldBe("Elemento separador com estilo de divisor");
|
||||
ptBr["SemanticPartStyleTitle"].ShouldBe("Personalizar estilo de Semantic Part");
|
||||
ptBr["SemanticPartStyleDescription"].ShouldBe(
|
||||
"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.");
|
||||
}
|
||||
|
||||
private static void AssertSolidColor(IBrush? actual, string expected)
|
||||
{
|
||||
actual.ShouldNotBeNull()
|
||||
.ShouldBeAssignableTo<ISolidColorBrush>()
|
||||
.Color.ShouldBe(Color.Parse(expected));
|
||||
}
|
||||
|
||||
private static string ExtractSpaceExampleItems(string source)
|
||||
{
|
||||
const string firstItemMarker = "<gallery:ShowCaseItem";
|
||||
@@ -86,6 +337,21 @@ public class SpaceShowCasePageTests
|
||||
return source[firstItemStart..panelCloseStart];
|
||||
}
|
||||
|
||||
private static string ExtractShowCaseItemBySourceKey(string source, string sourceKey)
|
||||
{
|
||||
var sourceKeyIndex = source.IndexOf($"SourceKey=\"{sourceKey}\"", StringComparison.Ordinal);
|
||||
sourceKeyIndex.ShouldBeGreaterThanOrEqualTo(0);
|
||||
|
||||
var itemStart = source.LastIndexOf("<gallery:ShowCaseItem", sourceKeyIndex, StringComparison.Ordinal);
|
||||
itemStart.ShouldBeGreaterThanOrEqualTo(0);
|
||||
|
||||
const string itemEndMarker = "</gallery:ShowCaseItem>";
|
||||
var itemEnd = source.IndexOf(itemEndMarker, sourceKeyIndex, StringComparison.Ordinal);
|
||||
itemEnd.ShouldBeGreaterThan(sourceKeyIndex);
|
||||
|
||||
return source[itemStart..(itemEnd + itemEndMarker.Length)];
|
||||
}
|
||||
|
||||
private static string NormalizeMarkup(string source)
|
||||
{
|
||||
return ShowCaseSnapshotMarkup.Normalize(source);
|
||||
@@ -149,4 +415,37 @@ public class SpaceShowCasePageTests
|
||||
var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../"));
|
||||
return Path.Combine(repoRoot, relativePath);
|
||||
}
|
||||
|
||||
private static void ShowInWindow(Control content, double width, double height, Action assertion)
|
||||
{
|
||||
var visualLayerManager = new VisualLayerManager
|
||||
{
|
||||
EnableAdornerLayer = true,
|
||||
Child = content
|
||||
};
|
||||
|
||||
var window = new AvaloniaWindow
|
||||
{
|
||||
Content = visualLayerManager,
|
||||
Width = width,
|
||||
Height = height
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
assertion();
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestScreen : IScreen
|
||||
{
|
||||
public RoutingState Router { get; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user