SusSkin

皮肤机制 — 将已发现的皮肤描述符应用于活动的 VisualElement 树:layout 样式表无论皮肤开或关都保持挂载,因此 .sharq 组合类继续解析;brand 样式表(令牌/覆盖)仅在皮肤启用时挂载 — 这正是「关闭 → 默认 SUS 外观」的含义。可选的 component 层在皮肤启用期间把小型覆盖样式表带入每个组件。根/表面类用普通 USS 切换,在 glass 与 paper 表面之间切换。
包
SusKit · 服务(商业)
SusSkin 是普通的 C# 静态类,不是 .sharq 组件。Kit 本身从不命名具体皮肤 — 每个皮肤都以自己的 描述符资产(SusSkinAsset)交付,并通过同一机制接入,无论是免费的仅布局皮肤还是付费品牌皮肤集。
使用
// Fluent SusApp entry — layout sheets always, brand + component sheets and fonts only when enabled
SusApp.Create(document)
.UseSkin(mySkinAsset, enabled: true)
.Run();
// Re-apply on a live root (surface defaults to the descriptor's DefaultSurface)
SusSkin.Apply(root, mySkinAsset, enabled: true, SusSkinSurface.Glass);
// Remove the skin entirely — classes off, layout, brand AND component sheets detached
SusSkin.Off(root, mySkinAsset);| 成员 | 角色 |
|---|---|
UseSkin(skin, enabled) | Fluent SusApp 入口 — 始终挂载 layout 样式表,启用时挂载 brand、component 样式表和字体 |
Apply(root, skin, enabled, surface?) | 切换根/表面类,在活动根上(重新)挂载样式表,添加或移除 component 层 — 幂等 |
Off(root, skin) | 分离全部内容,包括 layout 样式表和 component 层 — 用于彻底离开树 |
AttachLayoutSheets(element, skin) | 仅 layout 样式表,用于挂在应用根面板之外的子树 |
皮肤描述符
皮肤就是数据:由 SusSkinAsset 解析的 sus-skin.<id>.json 描述符(schema sus-skin/v1)。 样式表条目是 Resources 路径。
{
"schema": "sus-skin/v1",
"id": "myskin",
"class": "myskin-skin",
"sheets": {
"layout": ["SusRuntime/myskin-screens"],
"brand": ["SusRuntime/myskin-tokens", "SusRuntime/myskin-overrides"],
"component": ["SusRuntime/myskin-component-overrides"]
},
"surfaces": { "glass": "myskin-skin--glass", "paper": "myskin-skin--paper" },
"defaultSurface": "glass"
}| 层 | 挂载到 | 何时 | 放什么 |
|---|---|---|---|
sheets.layout | 皮肤根 | 只要皮肤存在,无论开或关 | 皮肤自身 .sharq 外框的组合类 |
sheets.brand | 皮肤根 | 仅在皮肤启用时 | 设计令牌(custom properties)以及写在皮肤类下的覆盖 |
sheets.component(可选) | 皮肤根下每个带样式的组件,位于该组件自身生成样式表之后 | 仅在皮肤启用时 | 不带皮肤类前缀、只含覆盖的小型样式表 — 不含令牌 |
没有 sheets.component 的描述符会让每个组件的样式表与无皮肤时完全相同。
品牌规则如何胜出
UI Toolkit 在不同样式表的规则冲突时先比较特异性。只有特异性相同时,挂载在层级更低处的样式表才胜出。 每个带样式的 Kit 组件都在组件元素本身上携带自己的生成样式表(.g.uss),因此根上的品牌规则若与 Kit 规则特异性相同,就会输给它 — Kit 外观保持不变。
让品牌规则胜出有两种方式。
默认:在皮肤类下编写覆盖
给每条覆盖加上皮肤的根类(描述符的 class)作为前缀。多出的一个类把特异性提到 Kit 的单类规则之上, 于是规则从根上胜出。这在运行时没有开销,也不需要额外的层。
/* Loses: (0,1,0) on the root ties with (0,1,0) on the component itself */
.sus-menu-button { border-width: 1px; }
/* Wins: (0,2,0) beats the kit's (0,1,0) */
.myskin-skin .sus-menu-button { border-width: 1px; }当 Kit 规则本身是复合选择器时,要在前缀下重复整个 Kit 选择器。此时单类前缀不够: .myskin-skin .sus-menu-button 是 (0,2,0),与 Kit 的 .sus-menu-button--flat.sus-menu-button--color-primary 相同,而平局归组件自身的样式表。
/* Wins: (0,3,0) beats the kit's compound (0,2,0) */
.myskin-skin .sus-menu-button--flat.sus-menu-button--color-primary { background-color: var(--myskin-accent); }关闭皮肤(Apply(root, skin, enabled: false))会移除皮肤类和 brand 样式表,所有带前缀的覆盖都不再匹配, Kit 外观随之恢复。
可选:component 层
按 Web CSS 方式编写的皮肤 — 覆盖不带皮肤类前缀,期望特异性相同的规则胜出 — 把这些样式表列在 sheets.component 中。皮肤启用期间,SusSkin 会把它们追加到皮肤根下每个带样式组件自身生成样式表之后, 于是特异性相同的规则胜出:
/* In a sheets.component sheet, on a SusMenuButton that carries the class my-screen__back-btn:
(0,1,0) now beats the kit's .sus-menu-button (0,1,0) */
.my-screen__back-btn { border-width: 1px; }- 只放覆盖,不放令牌。 该层会在每个嵌套层级追加,所以请保持精简。在
sheets.brand中于根上设置的 custom properties 本来就会继承到每个组件。 - 关闭即恢复 Kit 外观。 禁用或移除皮肤会从所有被添加过该层的组件上移除它。
- 把皮肤应用到应用根。 覆盖层(菜单、对话框、提示)只有在覆盖层位于皮肤所应用的根之下时才会得到该层 — 在
SusApp上使用UseSkin,或在SusApp.Root上使用Apply。直接挂载到覆盖层、位于皮肤根之外的组件得不到该层;从皮肤根之下的组件打开的覆盖层则会得到。 - 之后创建的组件在挂载到面板时会拾取该层。
注意
- Surface 是二选一 —
SusSkinSurface.Glass或SusSkinSurface.Paper— 在皮肤描述符中声明为DefaultSurface,可在每次Apply调用时覆盖。 - Storybook story Skin (
skin):发现项目链接的任意皮肤包(与 Skins 窗口相同),并与默认 SUS 外观并排显示,带有「Skin enabled」开关和 glass/paper 表面切换。 - Hub shot
kit-skin.png— 展示(待拍摄)。