Skip to content

Latest commit

 

History

History
1539 lines (1203 loc) · 87.9 KB

tycss-reference.md

File metadata and controls

1539 lines (1203 loc) · 87.9 KB

TyControls .tycss 样式语言参考手册

English: tycss-reference.en.md

本文档是 TyControls v1 样式语言(.tycss)的权威语法参考,面向主题作者与控件开发者。 文中每一条语法规则均与引擎实现(source/tyControls.Css.Lexer.pas、Css.Parser.pas、 Css.Values.pas、StyleModel.pas、Painter.pas)逐条核对,如与其他文档冲突,以本文为准。

加载入口:TTyStyleModel.LoadFromFile(文件名) / LoadFromCss(源码字符串)。 语法错误抛出 ETyCssError(含行/列号);属性值非法(如无法解析的颜色)在加载时抛出异常; 未知的属性名被静默忽略,不会报错。

内置基础层与覆盖语义

库内置一个基础层(由 themes/light.tycss 生成),始终垫在已加载主题下面。它不是名为 default 的主题:default 是 themes/auto.tycss,和每套内置皮肤一样,自己写一部分键, 其余交给基础层。

ResolveStyle 怎样合并两层,取决于规则从哪里来:

  • 主题(ThemeName、ThemeFile、LoadTheme、LoadThemeCss)给某 typeKey 写了普通 规则(不带类、不带状态),内置层对这个键就整体失效,完全由主题决定(包括"留空"的属性 也不会从内置层渗漏)。主题没写普通规则的键整键回退到内置层。因此完整主题与未引入本特性 前渲染一致,而部分主题不会导致未覆盖控件不可见。
  • 追加的补丁(LoadThemeCssAdditive、控制器的 StyleOverride)不接管任何键,只改它 写的属性,不管这个键来自主题还是内置层(§8.1)。

升级既有主题的人先读 §8.1。 v3 把 51 个"借用别人 typeKey"的控件拆成了自己的键; 由于覆盖语义是按 typeKey 全有全无,你的主题没提过的新键会整键回落到内置 light 值。


1. 概览与完整示例

.tycss 是一个刻意精简的 CSS 方言:

  • 顶层只有两种结构::root 变量块 和 样式规则;
  • 选择器只有一种形态:类型 [.变体] [:状态],可用逗号并列多个;
  • 不支持后代选择器、子选择器、通配符等任何组合选择器;
  • 变量在使用处惰性求值,支持 var(--x) 与裸 --x 两种写法;
  • 颜色支持 rgb / rgba 构造函数,以及 lighten / darken / alpha / mix 调整函数,可任意嵌套。

下面是一个可直接使用的最小完整主题:

/* my-theme.tycss —— 最小可用主题 */
:root {
  --accent:     #3B82F6;
  --surface:    #FFFFFF;
  --on-surface: #1F2937;
  --border:     #D1D5DB;
  --danger:     #EF4444;
  --radius:     6px;
  --focus-ring: var(--accent);   /* 焦点环颜色;可独立覆盖 */
}

TyButton {
  background: var(--surface);
  color: var(--on-surface);
  border-color: var(--border);
  border-width: 1px;
  border-radius: var(--radius);
  padding: 6px;
  font-size: 10px;
  font-weight: 400;
}
TyButton:hover    { background: darken(--surface, 4%); }
TyButton:focus    { border-color: var(--accent); outline: 2px var(--focus-ring); }
TyButton:active   { background: darken(--surface, 10%); }
TyButton:disabled { opacity: 0.5; }

TyButton.primary        { background: var(--accent); color: #FFFFFF; border-color: var(--accent); }
TyButton.primary:hover  { background: lighten(--accent, 8%); }
TyButton.primary:active { background: darken(--accent, 8%); }

TyLabel {
  background: alpha(#FFFFFF, 0);   /* 完全透明 */
  color: var(--on-surface);
  font-size: 10px;
}

TyEdit, TyComboBox {
  background: var(--surface);
  color: var(--on-surface);
  border-color: var(--border);
  border-width: 1px;
  border-radius: var(--radius);
  padding: 4px;
  font-size: 10px;
}
TyEdit:focus, TyComboBox:focus { border-color: var(--accent); outline: 2px var(--focus-ring); }

TyPanel {
  background: var(--surface);
  border-color: var(--border);
  border-width: 1px;
  border-radius: var(--radius);
  padding: 8px;
}

更完整的实例见仓库内置主题:themes/light.tycss、themes/dark.tycss、themes/builtin/showcase.tycss。


2. 文件结构与词法

2.1 顶层结构

一个 .tycss 文件是若干 :root 块与样式规则的序列,顺序任意、可交错:

:root { --a: #fff; }
TyButton { background: var(--a); }
:root { --a: #000; }   /* 允许多个 :root 块;同名变量后定义者覆盖 */

2.2 注释

只支持块注释 /* ... */,可跨行。不支持 // 行注释。

2.3 Token 一览

Token 形式 说明
颜色字面量 #rgb #rrggbb #rrggbbaa # 后只吃十六进制字符;长度必须为 3、6 或 8,否则求值时报错
数字 10 0.45 .5 无符号;可带小数。词法上数字不带负号(见下方"负数")
标识符 bold --accent -2px 首字符 [a-zA-Z_-],后续可含数字;- 可作首字符,故 --accent、-2px 都是单个标识符
函数名 var( lighten( url( 标识符紧跟 ((中间不能有空格)即为函数调用开头
字符串 "Noto Sans" 'a' 单/双引号;无转义序列;不能含同种引号
单位/符号 px % deg px、deg 是普通标识符,紧跟数字时被粘合为 6px、90deg;% 是独立符号,紧贴前一数字
标点 : ; { } . , ( )

负数:-2px 在词法上是一个标识符 token,但长度/数值求值函数能正确解析它, 因此 shadow: 0px -2px 4px #0000002E; 这类负偏移是合法的。

2.4 声明的书写规则

  • 每条声明形如 属性名: 值;,分号必不可少,包括块内最后一条;
  • 值一直读到分号 / } 为止;括号内的逗号、冒号不会截断值;
  • 类型名、变体名、伪类名、属性名、函数名的匹配均不区分大小写 (习惯上请按本文与内置主题的写法书写);
  • 同一规则块内重复写同一属性,后写的覆盖先写的。

3. :root 变量

3.1 定义

:root {
  --accent: #3B82F6;
  --radius: 6px;
  --hover-bg: lighten(--accent, 8%);   /* 变量可以引用其他变量 */
}
  • 变量名必须以 -- 开头,否则解析报错;
  • 值是原始文本,在使用处才求值(惰性求值)。因此变量可以前向/相互引用其他变量, 也可以存放颜色函数、渐变参数片段等任何表达式;
  • 同名变量重复定义时,后定义者覆盖(包括跨多个 :root 块);
  • 引用未定义的变量在使用该变量的规则被求值时(即加载样式表时)抛出异常。

3.2 引用:var(--x) 与裸 --x 都支持

颜色、长度、数值三类表达式中,以下两种写法完全等价:

TyButton {
  background: var(--accent);            /* CSS 标准写法 */
  border-color: --accent;               /* 裸变量写法,同样合法 */
  border-radius: var(--radius);
  padding: --radius;
}

在函数参数里同样两种都可用,内置主题大量使用裸写法以求简洁:

TyButton:hover { background: lighten(--accent, 8%); }      /* 裸 --x */
TyButton:focus { border-color: darken(var(--border), 10%); } /* var(--x) */

注意:var(...) 形式只能整体作为一个值或一个函数参数使用,不存在字符串拼接。

3.3 内置语义令牌

除 --focus-ring(§5.14)外,内置主题在 :root 中还约定了若干语义令牌,供子部件 typeKey 与文本类控件引用。它们都是普通 --变量,可被主题作者整体覆盖:

令牌 内置取值(light) 语义 / 用途
--selection alpha(var(--accent), 0.30) 文本选区高亮带底色(accent 着色的半透明);由 TyTextSelection 引用,使 Edit/Memo 的选区与列表选中行视觉同源
--muted alpha(var(--on-surface), 0.5) 弱化前景色(占位符 / 提示文字);由 TyTextHint 引用
--overlay-hover alpha(var(--on-surface), 0.12) 半透明悬停叠加层(如页签关闭 × 的圆角底片);由 TyTabClose 引用
--input-bg var(--surface) 输入类控件的底色:编辑框、下拉框、列表框、备忘框、微调框、日历、日期框、树、列表视图
--input-border var(--border) 同一批输入类控件的边框色;与 --border 分开,皮肤改输入框边框时不会连带分隔线、网格线和面板边框
--grid-bg var(--surface) 数据表格正文的底色
:root {
  --selection:     alpha(var(--accent), 0.30);
  --muted:         alpha(var(--on-surface), 0.5);
  --overlay-hover: alpha(var(--on-surface), 0.12);
}

改这些令牌即可全局调整对应的外观,无需逐个控件改写。皮肤没有专属规则的控件由内置基础层绘制, 皮肤把 --input-bg、--input-border、--grid-bg 和 --radius 系列指向自己编辑框的取值, 这些控件就会和皮肤自己的编辑框一致。这些令牌与上面的 --accent、--on-surface 一样 在使用处惰性求值,可引用其他变量。

3.4 控件读的令牌:整个应用改,还是只改一个控件

:root 里还有一批令牌是给控件读的。大多是尺寸,定内边距、间距和槽位大小,比如 --listview-text-margin、--treeview-margin,值可以写 6px、6 或 var(--x);也有取值是 词或颜色的,比如树线的 --treeview-lines: classic、--treeview-lines-color: #a0a0a0(§3.5)。

整个应用:写在主题里,或写进控制器的 StyleOverride:

:root {
  --listview-text-margin: 6px;
  --treeview-margin: 6px;
}

只改一个控件:写进这个控件自己的 StyleOverride,只对它生效,优先于主题:

--treeview-margin: 10px; --treeview-icon-gap: 6px;

控件 StyleOverride 里的 -- 声明也是同一块里其他声明的变量,比如 --w: 2px; border-width: var(--w);。值算不出来(写错了单位、引用了没定义的变量)时, 这一条不起作用,沿用主题的值。取值是词的令牌,值如果只是一个引用(var(--x) 或 --x), 读到的是它指向的值;指不到东西时同样沿用主题的值。

目前按控件读令牌的是:TTyListView(含 TTyShellListView)读的全部尺寸令牌,即 --listview-*、--row-height 和 --scrollbar-size;TTyTreeView(含 TTyShellTreeView)的 --treeview-margin、--treeview-icon-gap 和 §3.5 的四个树线令牌;TTyMemo 的 --memo-tab-size; TTyPopover 的六个 --popover-* 令牌(它不是控件,但 StyleOverride 的规则相同)。 其他控件暂时只认主题里的值。另外,每个控件都认 §3.6 的 --over-parent-chrome。 3.0.4 以前的版本会忽略控件 StyleOverride 里的 -- 声明,用新版本保存的窗体在旧版本里照样能打开。

3.5 树线与展开符号

树线和展开符号怎么画,由下面四个令牌决定。和 §3.4 的令牌一样,可以写在主题里,也可以只写进 某一棵树的 StyleOverride。

令牌 取值 默认 含义
--treeview-lines guides / classic / none guides guides:在每个祖先节点的展开符号下面画一条竖线,贯穿它后代的各行;节点自己这一层不画,所以线碰不到展开符号。classic:完整连线,包括节点自己那段从上一行下来、再拐向内容的线。none:不画。
--treeview-lines-style solid / dotted / dashed / 一到两个长度 solid 线型。dotted 就是 1px 1px,dashed 就是 3px 3px。写长度时同 SVG 的 stroke-dasharray:先实后空,只写一个就是实空等长,比如长虚线写 6px 3px。单位是逻辑像素,随 DPI 缩放。
--treeview-lines-color 颜色 树的边框色 线的颜色,可以用 var() 和颜色函数。
--treeview-expander chevron / plus-minus chevron plus-minus 是带方框的 +/-,画在连线交汇的地方,classic 连线到框边为止。点击区域和 chevron 一样。

内置主题里,classic 和 xp 两个皮肤用 plus-minus、classic、dotted,其余都用默认值。

点线和虚线的图案以树的内容区原点为准,不是每一段线各自从头开始:相邻几行的竖线连成一条, 拐角处横竖两条线落在同一套图案上,滚动时图案跟着内容走。

属性 ShowTreeLines 仍是总开关:设成 False 就不画线,令牌怎么写都一样。有没有展开符号 仍由 ShowButtons 决定。

/* 整个应用都用经典的树 */
:root {
  --treeview-expander: plus-minus;
  --treeview-lines: classic;
  --treeview-lines-style: dotted;
}

只让某一棵树不画线,在它的 StyleOverride 里写:

--treeview-lines: none;

3.0.4 以前没有这些令牌,树画的是实线的完整连线加 chevron。

3.6 外框压在子控件上面

子控件放到容器的边框、圆角或分隔线上时,容器会在子控件画完之后把这几样补画在上面,线不会在子控件那里断开。 会这样做的容器:

容器 补画的东西
TTyPanel,以及 TTyExPanel、TTyPaintPanel、TTyScrollBox、TTyScrollPanel、TTyControlBar、TTyCoolBar、TTyGridPanel、TTyRelativePanel 边框、焦点环、圆角外的父背景
TTyCard 同上,加标题下和操作栏上的分隔线
TTyGroupBox,以及 TTyRadioGroup、TTyCheckGroup、TTyToolGroupPanel 边框、焦点环、圆角外的父背景,加标题和它在上边框断开的那一段
TTyPageControl 页面区的边框、焦点环、圆角外的父背景(以前当前页会盖住其中三条边)
TTyToolBar 底部细线

只有看得见的边框或焦点环才算外框:border-width: 0 又没有焦点环的面板,圆角本来就看不出来, 里面子控件的直角照旧。滚动框里贴边的滚动条自己就会画宿主的外框,宿主不再重复画。

不想让某个子控件被盖住,在它的 StyleOverride 里写:

--over-parent-chrome: 1;

写在主题或控制器 StyleOverride 的 :root 里,整个应用都不再补画。值是 0、false、none 或者不写,都表示照常补画。代码里也可以整个关掉:TyChromeOverlayEnabled := False; (tyControls.Base 单元,默认 True)。

补画只发生在 TyControls 自己的控件上;原生 LCL 控件放上去,照旧会盖住外框。这个行为从 3.0.5 开始。


4. 选择器与样式解析

4.1 选择器文法

选择器     ::= 类型名 [ "." 变体名 ] [ ":" 状态名 ]
选择器列表 ::= 选择器 { "," 选择器 }
  • 类型名:控件的 typeKey(见第 8 节),如 TyButton;
  • 变体名(可选):任意标识符,对应控件 StyleClass 属性中的某个 token,如 .primary; 每个选择器最多一个变体;
  • 状态名(可选):只能是 hover、active、focus、disabled、selected 五个之一 (checked 是 selected 的别名,解析成同一个状态),其他伪类名直接解析报错。 每个选择器最多一个状态 —— 不能链式书写 :selected:disabled。
TyButton { ... }                 /* 类型基础样式 */
TyButton.primary { ... }         /* 变体 */
TyButton:hover { ... }           /* 状态 */
TyButton.primary:hover { ... }   /* 变体 + 状态 */
TyEdit:focus, TyComboBox:focus { ... }   /* 逗号列表:同一组声明注册到多个选择器 */

4.2 明确不支持的选择器

以下写法都会导致解析错误或不被识别,引擎没有任何组合选择器:

  • 后代 / 子选择器:TyPanel TyButton、TyPanel > TyButton
  • 通配符:*
  • 多变体:TyButton.primary.large(一个选择器只允许一个 .变体)
  • 纯类 / 纯状态选择器:.primary、:hover(必须以类型名开头)
  • 属性选择器、!important、@media 一概不支持 (@import 与 @mode 是支持的 at-规则,只是它们不是选择器;@import 必须出现在 所有 :root 与规则之前)

4.3 控件状态从哪里来

控件每次重绘时计算当前状态集:

  • disabled(控件 Enabled = False)是排他的:禁用时只有 disabled 一个状态, hover / focus / active 均被忽略;
  • hover:鼠标位于控件上;
  • active:鼠标左键按下未释放(对 CheckBox 等也是"按住"而非"选中");
  • focus:控件拥有键盘焦点。基于 TTyGraphicControl 的控件(TTyLabel 一族、 TTyProgressBar、仪表族等)没有 focus 状态;能取得焦点的窗口化控件都支持它;
  • selected:条目/单元格/页签一类可选中的部件处于选中态。它由子部件的解析点给出 (如 TyListViewItem、TyCalendarCell、TyGridCell、TySegmentedItem),而不是控件整体的瞬时状态。

多个状态可同时成立(如鼠标悬停在已聚焦的按钮上 = hover + focus)。 一个键支持哪些状态,取决于它的解析点传了什么 —— 第 8 节逐键列出内置主题实际写过的伪类, 并点名那些解析时传空状态集、因而伪类选择器是死的键(如 TyLinkLabelLink)。

4.4 样式解析与合并顺序(ResolveStyle)

控件最终样式由多层 TTyStyleSet 按固定顺序合并,后层只覆盖其显式声明过的属性:

  1. 类型基础层:Type { };
  2. 变体层:控件 StyleClass 中的每个空格分隔 token,按其在 StyleClass 字符串中的文本顺序依次应用 Type.变体 { }(StyleClass 可含多个变体,如 "primary large");
  3. 状态层:对当前成立的每个状态,按固定顺序 selected → hover → focus → active → disabled 依次处理; 每个状态内先应用 Type:状态 { },再按变体顺序应用 Type.变体:状态 { }。 selected 排在最前是有意的:它是静态的驻留层,让 hover/active 能逐属性盖掉它冲突的部分, 而 selected 独有的属性(例如只有它声明的 border-color)仍然留下来。

由此可见:带状态的规则永远晚于(强于)不带状态的规则,带变体的状态规则永远晚于 同状态的纯类型规则。

为什么 .primary 的悬停必须写成 TyButton.primary:hover

TyButton          { background: #FFFFFF; }
TyButton:hover    { background: #F0F0F0; }              /* 状态层 */
TyButton.primary  { background: var(--accent); }        /* 变体层 */

鼠标悬停在一个 StyleClass = primary 的按钮上时,合并顺序为: TyButton → TyButton.primary → TyButton:hover。 状态层在变体层之后,所以 TyButton:hover 的 #F0F0F0 会盖掉 .primary 的强调色——primary 按钮一悬停就"褪色"。正确做法是为变体补一条同状态规则, 它在 TyButton:hover 之后应用:

TyButton.primary:hover { background: lighten(--accent, 8%); }

重复选择器:后写者胜(逐属性合并)

引擎对每个 (类型, 变体, 状态) 组合正序应用所有命中的规则,后一条按属性覆盖前一条 —— 与浏览器 CSS 一致,也是 @import / 追加加载能逐属性叠加的原因:

TyButton { background: #FF0000; color: #111111; }
TyButton { background: #00FF00; }   /* 只覆盖 background;color 仍是 #111111 */

这条行为由 tests/test.StyleModel.pas 的 TestDuplicateRuleLastWins 守住。 即便如此,仍不建议在同一个文件里重复定义同一选择器 —— 需要并列声明时把它们写进同一个规则块, 读起来才不用满文件找覆盖。

4.5 typeKey 链:子类继承父类的规则

第三方控件覆写 GetStyleTypeKey 报自己的键(比如 TagButton)时,可以在 Pascal 里登记它从哪个键来 (写法见 subclassing.md 第 2 节):

TyRegisterTypeKeyParent('TagButton', 'TyButton');   // 在 tyControls.StyleModel 里

登记之后,TagButton 先拿到 TyButton 的全部样式(内置层、主题层、变体、状态,和解析 TyButton 本身一模一样),再用自己的规则逐属性盖上去。主题里没写 TagButton 时,它和普通按钮完全一样; 写了就只需写不同的地方:

TagButton { border-radius: var(--radius-pill); }

子键的规则按 §4.4 的阶段和父键交错:普通规则 → 变体 → 各状态(selected → hover → focus → active → disabled),每个阶段里先父后子。所以:

  • 子键的规则盖过父键同一阶段或更早阶段写的值:TagButton { } 盖过 TyButton { }, TagButton.primary 盖过 TyButton.primary,TagButton:hover 盖过 TyButton:hover;
  • 父键更晚阶段写的值仍然有效:TyButton:hover、TyButton:disabled 盖过 TagButton { } 和 TagButton.primary,TyButton:disabled 也盖过 TagButton:hover。
TagButton       { background: #DCFCE7; }   /* 平时是绿的 */
                                           /* 悬停时用 TyButton:hover 的底色 */
TagButton:hover { background: #BBF7D0; }   /* 想要自己的悬停色,就写这一条 */

这和浏览器里「带状态、带类的选择器优先」的直觉一致:给子键写一条普通背景,不会吃掉父键的悬停和禁用效果。

「父键写的值」指父键自己解析出来的结果。例如主题打开了 PropertyCascade、又写了一条普通的 TyButton { background },它在 TyButton 自己的结果里已经盖掉了内置的 :hover 底色,那么对 TagButton 来说这个背景也只算普通阶段写的。

链可以有好几级,比如 FancyTag → TagButton → TyButton:每个阶段里从最上面的父键往下叠, 同一阶段子键胜。一条链最多 8 个键。

链根以下的键,同一阶段里先内置层、后用户层。 链根(最上面的父键)照 §4.4 解析:先整个内置层 (各阶段),再整个用户层。链根下面的键则按阶段走,每个阶段里先内置层、再用户层。所以把一个内置键 登记成子键、又开着 PropertyCascade 时,它内置的 :hover 会盖过主题给它写的普通规则(阶段更晚); 链根自己不会这样(用户层整个排在内置层之后)。第三方键没有内置规则,看不出这条区别。

按属性组交错,不按单条声明。 「哪个阶段写了这个值」按属性组记,组的划分和控件的 StyleOverride 合并进来时一样:一组里任何一个声明都算写了整组,让位时整组一起让。平时一条声明就是一组,要留意的是这几组:

  • outline 与 outline-offset 是一组(焦点环)。子键只写 outline-offset,父键在更晚的阶段 (常见的是 :focus)写了 outline,那个阶段里整组用父键的,子键的偏移也一起让掉:

    TyButton:focus  { outline: 2px var(--focus-ring); outline-offset: 1px; }
    TagButton       { outline-offset: 6px; }   /* 聚焦时不生效:整组用 TyButton:focus 的 */
    TagButton:focus { outline-offset: 6px; }   /* 写在同一阶段,子键胜 */
  • background-size、background-blur 跟 background 一组:父键的 :hover 换了底色,子键的 background-size 也跟着让。

  • 玻璃效果 glass-blur / glass-tint 是单独一组,不跟 background 走:父键的 :hover 只换底色时, 子键的玻璃参数不受影响;父键的 :hover 写了 glass-tint,悬停时子键的 glass-blur 也一起让掉。

  • border-radius 的四个角是一组:让位时四角一起回到父键的值。

  • shadow 的颜色、模糊、偏移是一组。

没有把 outline-offset 单独拆成一项:组的划分和 StyleOverride 合并是同一套,主题作者只记一套规则; 拆开要给样式集加一个新的属性标志,每个读 Present 的地方都得认它,而要碰到这种差别,得父子键在 不同阶段分写同一个焦点环的两半,把偏移和焦点环写在同一条规则里就不会遇到。

内置层的让位按键各算各的(§8.1)。主题写了 TagButton { }(无变体、无状态),只让 TagButton 自己的内置规则失效;TyButton 的内置规则照常垫在下面。第三方键在内置层本来就没有规则, 所以这条只在把内置键登记成子键时才看得出来。

子部件键要分别登记。 控件用 GetStyleTypeKey + 'Fill' 之类拼出来的子部件键不会自动跟着走: TagMeter 的填充解析的是 TagMeterFill,想继承 TyMeterFill 就再登记一条。

登记规则:

  • 在单元的 initialization 里登记,finalization 里用 TyUnregisterTypeKeyParent 撤销。登记表是 整个进程共用的;运行中途才登记,已经画好的控件不会自己重画。
  • 在 initialization 里抛异常会让程序还没启动就中止——比如两个包给同一个键登记了不同的父键, 后加载的那个一抛,整个程序起不来。包里登记用 TyTryRegisterTypeKeyParent:出错时返回 False、 登记表不变、不抛异常,控件只是不继承父键的规则;登记成功或同一对已经登记过返回 True。
  • 键名不分大小写,必须是标识符(字母或 _ 开头,后面是字母、数字、_、-)。
  • 下列情况抛 ETyCssError,登记表保持原样:键名不合法;成环(包括把一个键登记成它自己的父键); 一条链超过 8 个键;同一个键已经登记了别的父键(同一对再登记一次不算错)。
  • 查询用 TyTypeKeyParent、TyTypeKeyChain、TyGetRegisteredTypeKeys。

工具也认这条链:设计期 StyleClass 下拉会列出父键的变体(TagButton 也有 primary、danger、 ghost);控制器级 StyleOverride 编辑器的补全和右侧参考列表都列出登记过的键。lint 不检查 typeKey 名, 子键不会被报。

没登记父键的 typeKey 跟以前一样:只吃主题里为它写的规则。


5. 属性参考

引擎可识别的属性共 23 个(另有 background-color 作为 background 的别名),其余属性名一律被静默忽略。 本节展开其中通用的 16 个。另外 7 个是窗框 / 材质专用,本手册尚未逐条展开 (render-style 见 §9 第 17 条; 其余 background-size、background-blur、glass-blur、glass-tint、 background-under-titlebar 目前只有源码 TyApplyDeclaration 为准)。 所有长度均为逻辑像素(96 DPI 基准),绘制时按控件实际 DPI 缩放。 长度值可写 6px 或裸 6(px 后缀可省略),也可用 var(--x) / 裸 --x。

5.1 background — 背景填充

background: <颜色表达式> ;
background: linear-gradient(<角度>deg, <色标1>, <色标2>[, …]) ;
  • 纯色:任何颜色表达式(#hex、var()、裸 --x、颜色函数,见第 6 节);
  • 渐变:见第 7 节;角度必须是第一个参数,值以 linear-gradient( 开头才按渐变解析;支持 2+ 多色标(可带位置)。
TyPanel  { background: var(--surface); }
TyButton { background: linear-gradient(90deg, lighten(--accent, 10%), var(--accent)); }

background-color 是 background 的别名(仅纯色语义,不接受渐变),二者效果相同:

TyEdit { background-color: rgb(255, 255, 255); }   /* 等价于 background: rgb(255, 255, 255); */

5.2 background-image — 九宫格贴图

background-image: url(<路径>) slice(<上> <右> <下> <左>) [repeat] ;
  • url() 与 slice() 都必须出现,缺一报错;
  • slice 的四个值为整数像素,顺序固定:上 右 下 左(同 CSS 四值顺序), 表示源图四边不拉伸的边框宽度;
  • 末尾可选 repeat(v3):四条边与中心区平铺(1:1 重复)而非拉伸——用于带花纹/ 纹理边的位图皮肤;省略时为拉伸(与旧行为一致)。四角始终 1:1,不受影响;
  • 路径可加引号也可不加,但路径中绝对不能含空格:词法器会在未加引号的路径中插入 空格(如 panel.png 变为 panel. png),引擎通过"无条件删除所有空格"来复原, 因此真实空格也会被一并删除而悄悄读错。目录名、文件名都请保持无空格;
  • 相对路径相对于进程当前工作目录解析(没有"相对主题文件"的解析);文件不存在时 该填充被静默跳过(不报错、不绘制);
  • 不支持 var()。
TyPanel { background-image: url(assets/panel.png) slice(8 8 8 8); }

5.3 color — 前景/文字颜色

color: <颜色表达式> ;

用于文字以及 tier-b 单色字形(CheckBox 勾选符、ComboBox 下拉箭头、SpinEdit/ScrollBar 箭头、 Tab 关闭 × 等,见 §8.7)。

TyButton.primary { color: #FFFFFF; }

注:滑块/旋钮等 tier-a 着色面不再借用本控件的 color,而由各自的子部件 typeKey (TyScrollThumb、TyToggleKnob 等)的 background 决定——详见 §8.2 / §8.7。轨道/轨条背景仍来自父控件 background。

5.4 border-color / border-width / border-style — 边框

border-color: <颜色表达式> ;
border-width: <长度> ;
border-style: none | solid | outset | inset ;

三者配合:border-width 为 0(或未声明 border-color)时不描边。 solid 边框沿圆角矩形内侧描绘。

border-style 取值,默认 solid:

  • none — 强制不描边,即便 border-width > 0;
  • solid — 单色描边(沿圆角内侧);
  • outset / inset(v3)— 两色 3D 立体斜角:上/左与下/右分别用较亮/较暗的边色, outset 凸起、inset 凹陷。亮/暗两色从 border-color 自动派生(lighten 上左 / darken 下右)。 斜角天然是方角(不套用圆角半径),适合经典风格控件(配 border-radius: 0)。

不支持 dashed、dotted、groove、ridge 等其它样式。树线的点线、虚线另有令牌 --treeview-lines-style,见 §3.5。

TyEdit       { border-color: var(--border); border-width: 1px; }
TyEdit:focus { border-color: var(--accent); }
TyPanel      { border-style: none; }   /* 显式去掉边框,即便有 border-width */
/* 经典凸起按钮:面色作为 border-color,自动派生高亮/阴影边 */
TyButton.classic { border: 2px outset #C0C0C0; border-radius: 0; background: #C0C0C0; }

5.5 border — 边框简写

border: <宽度> [<样式>] <颜色> ;

一次性设置 border-width、border-style、border-color 三者,完全等价于分别书写它们。 顺序为宽度在前、颜色在后,中间可选写 solid / none / outset / inset 样式(省略时为 solid)。 颜色可用任意颜色表达式(#hex、var(--x)、rgb(...) / rgba(...),见第 6 节):

TyButton { border: 2px solid var(--accent); }   /* width=2px, style=solid, color=accent */
TyEdit   { border: 1px var(--border); }          /* 省略样式,等价于 solid */
TyPanel  { border: 0px none var(--border); }     /* style=none → 强制不描边 */

5.6 border-radius — 圆角半径

border-radius: <长度> ;
border-radius: <TL> <TR> <BR> <BL> ;

同时作用于背景填充、边框与焦点环形状。0px 为直角。

  • 1 个值:四角使用相同半径(向后兼容原有行为);
  • 4 个值:按 CSS 标准顺序依次为 左上、右上、右下、左下(TL TR BR BL), 空格分隔。值可为长度或 var(--x) / 裸 --x,支持 0 表示直角。
TyButton    { border-radius: var(--radius); }           /* 四角相同 */
TyTitleBar  { border-radius: 6px 6px 0 0; }            /* 上圆下方(标题栏 / 页签) */
TyTabControl { border-radius: var(--radius) var(--radius) 0 0; }

内置主题对标题栏(TyTitleBar)与页签(TyTab)使用 var(--radius) var(--radius) 0 0 实现顶部圆角、底部直角效果。页签的圆角按标签在上方来写;TabPosition 为下方或两侧时, 圆角跟着转到标签伸出去的那一侧。

不支持:每角长手写法(border-top-left-radius 等)、百分比半径、 椭圆双半径写法(<a> / <b>)。

5.7 padding — 内边距

padding: <全部> ;
padding: <上下> <左右> ;
padding: <上> <左右> <下> ;
padding: <上> <右> <下> <左> ;

空格分隔,1 到 4 个值,语义与 CSS 一致;其它个数报错。

TyButton { padding: 6px; }
TyPanel  { padding: 8px 12px; }
TyLabel  { padding: 2px 6px 4px; }
TyEdit   { padding: 4px 8px 4px 8px; }

5.8 font-family — 字体名

font-family: <字体名> ;

值按原始文本直接作为字体名。不要加引号:带引号书写时引号字符会被保留进字体名, 导致找不到字体。多词字体名直接裸写即可(空格会被规范为单个空格):

TyLabel { font-family: Noto Sans CJK SC; }   /* 正确 */
/* font-family: "Noto Sans CJK SC";  错误:引号会成为名字的一部分 */

5.9 font-size — 字号(注意:数值是 pt)

font-size: <数值>[px] ;

数值按"磅"(pt)解释:绘制时换算为像素 round(n × 96 / 72) 再做 DPI 缩放。 允许写 px 后缀,但后缀只是被剥掉,数值仍按 pt 处理——font-size: 10px 实际是 10pt (96 DPI 下约 13 像素)。内置主题沿用了这一写法。

TyButton { font-size: 10px; }   /* = 10pt */

5.10 font-weight — 字重

font-weight: bold | normal | <数值> ;
  • bold = 700,normal = 400,也可直接写数字(如 600);
  • 渲染时只有粗体/常规两档:数值 ≥ 600 绘制为粗体,否则常规,没有中间字重。
TyTitleBar { font-weight: 700; }

5.11 opacity — 整体不透明度

opacity: <0..1 小数> ;

对整个控件的绘制结果(含文字)统一施加不透明度,常用于 disabled 态:

TyButton:disabled { opacity: 0.5; }

opacity 经由通用框架绘制路径(DrawFrame)生效。v1.1 中 TyCheckBox 与 TyRadioButton 已通过修改渲染路径使 opacity 与 shadow 正常生效; 所有 typeKey(含 TyLabel)均已支持。

5.12 shadow — 投影

shadow: <X偏移> <Y偏移> <模糊半径> <颜色> ;
  • 四段以空格分隔,顺序固定:X 偏移、Y 偏移、模糊半径(均为逻辑像素,偏移可为负)、颜色;
  • 颜色必须是单个 token:#hex(推荐带 alpha 的 #rrggbbaa)、var(--x) 或裸 --x。 因为值按空格切分,不能使用含逗号的颜色函数(alpha(...)、mix(...) 等); 需要半透明请直接写 hex-alpha;
  • 颜色 alpha 为 0 时不绘制阴影;
  • 阴影同样走 DrawFrame 路径,对 TyCheckBox / TyRadioButton 无效。
TyButton { shadow: 0px 2px 4px #0000002E; }
TyPanel  { shadow: 0px 1px 3px var(--shadow-color); }

5.13 outline / outline-offset — 焦点环

outline: <宽度> <颜色> ;
outline-offset: <长度> ;

在控件内部绘制一圈焦点指示边框。因为 TyControls 控件完全自绘、绘制结果被裁剪到控件矩形内, 焦点环总是向内偏移而非向外扩张。

  • outline: <宽度> <颜色>:宽度在前(开头为数字)、颜色在后(颜色表达式)。 与 border 独立,不影响布局与 padding。设置此属性会将 tpOutline 加入 Present 集;
  • outline-offset: <长度>:焦点环距控件外边缘的内缩距离,默认 0(贴着控件边缘)。 outline-offset 单独不设置 tpOutline——必须同时有 outline 规则焦点环才会绘制;
  • outline: 0 可在特定规则里禁用焦点环(引擎只处理 outline 简写与 outline-offset,outline-width 属性不被识别、静默忽略);
  • 焦点环的圆角跟随 border-radius 的四角设置(各角减去 outline-offset 后箍合,最小为 0)。

通常写在 :focus 规则内,且引用 --focus-ring 语义令牌:

TyButton:focus { outline: 2px var(--focus-ring); }
TyEdit:focus   { border-color: var(--accent); outline: 2px var(--focus-ring); outline-offset: 2px; }

5.14 --focus-ring — 焦点环颜色语义令牌

这不是一个属性名,而是内置主题在 :root 中定义的语义变量:

:root {
  --focus-ring: var(--accent);
}

所有内置主题(light.tycss、dark.tycss 等)均在 :root 中定义 --focus-ring, 并在所有可聚焦控件的 :focus 规则中用 outline: 2px var(--focus-ring) 绘制焦点环。

主题作者可按以下方式定制:

目的 写法
改变焦点环颜色 :root { --focus-ring: #FF8800; }
全局禁用焦点环 各 :focus 规则中写 outline: 0; 或省略 outline
单控件禁用焦点环 TyEdit:focus { outline: 0; }
回归直角焦点环 对应控件写 border-radius: 0;

6. 颜色函数参考

颜色表达式 = #hex | var(--x) | 裸 --x | rgb(...) / rgba(...) | 以下函数(参数本身又是颜色表达式, 可任意嵌套)。函数名不区分大小写。百分比参数的 % 后缀可写可不写 (8% 与 8 等价)——唯一例外是 alpha,见下。

任何接受颜色的位置(color、background / background-color、border-color、border: 简写、 颜色函数的参数等)都可使用上述全部形式。

6.1 lighten(<颜色>, <百分比 0..100>)

向白色方向提亮:每通道 ch + (255 − ch) × p/100;alpha 保持不变。

TyButton.primary:hover { background: lighten(--accent, 8%); }

6.2 darken(<颜色>, <百分比 0..100>)

向黑色方向压暗:每通道 ch × (1 − p/100);alpha 保持不变。

TyButton:active { background: darken(--surface, 10%); }

6.3 alpha(<颜色>, <不透明度>)

把颜色的 alpha 替换为给定值(RGB 不变),0 全透明、1 不透明。不透明度也可以写成 百分比:alpha(#fff, 50%) 等于 alpha(#fff, 0.5)。

陷阱:不带 % 的数按 0..1 理解,不是百分比。alpha(#fff, 50) 会被钳成完全不透明, 要写 0.5 或 50%。

TyCaptionButton:hover { background: alpha(#FFFFFF, 0.18); }
TyLabel { background: alpha(#FFFFFF, 0); }   /* 透明背景 */

6.4 mix(<颜色1>, <颜色2>, <百分比 0..100>)

线性混合,百分比是第二个颜色的占比:结果 = 颜色1 × (1−p/100) + 颜色2 × p/100。 RGB 与 alpha 四个通道都参与混合。

:root { --tint: mix(--surface, --accent, 20%); }   /* 80% surface + 20% accent */

6.5 rgb(<r>, <g>, <b>) / rgba(<r>, <g>, <b>, <a>)

按 RGB 分量构造颜色。r / g / b 为 0..255 的整数;rgba 的第四个参数 a 是 不透明度,既可写 0..1 的小数,也可写带 % 的百分比(0.6 与 60% 等价)。 rgb(...) 等价于 alpha 为 1 的 rgba(...)。可用在任何接受颜色的位置(含 border: 与 background)。

TyEdit  { background-color: rgb(255, 255, 255); }
TyLabel { color: rgba(0, 0, 0, 0.6); }          /* 60% 不透明的黑 */
TyButton { border: 2px solid rgb(59, 130, 246); }

6.6 嵌套与变量

TyButton:hover {
  background: lighten(mix(--surface, var(--accent), 15%), 4%);
}

7. 线性渐变

background: linear-gradient(<角度>deg, <色标1>, <色标2>[, …<色标N>]) ;
  • 角度必须是第一个参数(angle-first),deg 后缀可省略,角度可带小数;
  • 其后是 2 个或更多颜色色标(v3 起支持多色标,用于光泽等效果);少于 2 个色标报错;
  • 每个色标是 颜色 [位置]:颜色是完整的颜色表达式(可用函数、变量,内部逗号不会误切分), 位置是可选的百分比或 0..1 数值(如 #fff 0%、var(--accent) 50%);
  • 位置按 CSS 规则归一化:首个缺省为 0、末个为 1,中间缺省的在相邻已定位色标间均匀插值, 且强制不递减(更小的位置会被上钳到前一个);
  • 不支持 to right 等关键字方向(方向仍由角度决定,见 7.1)。

多色标示例(经典玻璃光泽:顶部高亮带):

TyButton.primary {
  background: linear-gradient(90deg, #FFFFFF 0%, #EAEAEA 48%, #DADADA 52%, #EDEDED 100%);
}

7.1 角度方向(与 CSS 不同!)

渐变轴穿过控件中心,方向按屏幕坐标系(x 向右、y 向下)的数学角度计算 (dx = cos θ, dy = sin θ),起始色在角度反方向端点:

角度 方向(起始色 → 终止色)
0deg 左 → 右
90deg 上 → 下
180deg 右 → 左
270deg 下 → 上

注意这与浏览器 CSS 约定(0deg 朝上、90deg 朝右)不一致。 内置 showcase 主题中常见的 linear-gradient(90deg, …) 是自上而下的渐变:

TyButton.primary {
  background: linear-gradient(90deg, lighten(--accent, 10%), var(--accent));
  /* 顶部较亮,底部为强调色 */
}

渐变端点取在渐变轴与控件包围盒的交点上,渐变铺满整个控件矩形。


8. typeKey 与内置变体清单

选择器中的类型名即控件 GetStyleTypeKey 返回的 typeKey(含子部件 typeKey)。 第三方控件自己的键可以登记一个父键、继承它的全部规则,见 §4.5。

本节是主题作者的权威键表。清单逐条取自 themes/light.tycss —— 该文件是唯一事实来源, source/tyControls.DefaultTheme.pas(编译进库的内置基础层)由它生成并逐字节同步。

当前规模:

数量
light.tycss 已定义的 typeKey 183
控件解析、但基础层刻意不定义的键(§8.4) 8
主题里有定义、但没有任何代码解析的死键(§8.6) 3

v3 起的重大变化。 此前 51 个控件解析的是别的控件的 typeKey,主题层根本够不着它们 (commit b824a49、d58eada)。这些控件现在各有自己的键,并作为并列选择器加进原捐赠者 的规则块 —— 所以内置主题下像素一动不动,但每一个都变成可单独换肤的钩子。 既有第三方 .tycss 必须补新选择器,原因见下一小节。

8.1 最重要的一条:基础层是「整键替换」,不是「逐属性级联」

对主题,ResolveStyle 的默认行为(PropertyCascade 关闭时)是按 typeKey 全有全无:

  • 主题对某 typeKey 没写普通规则 → 该键整体回落到内置 light 基础层。主题给这个键的类或 状态写的规则(TyFoo.x、TyFoo:hover)叠在内置规则上面;
  • 主题对该 typeKey 写了普通规则(不带类、不带状态)→ 内置层对这个键整体失效,主题 没写的属性也不会从内置层渗漏。

判据是 TTyStyleModel.UserHasTypeKey,只看有没有这样一条规则,不看你覆盖了哪些属性。

追加的补丁不是主题。 LoadThemeCssAdditive 和控制器的 StyleOverride 不接管任何键。 主题交给内置层的键,先把内置层给它的规则复制进来,补丁再叠在上面,所以在 default 主题下 写 TyCascader { padding: 6px 13px; } 只改内距。这个键的 :hover、:focus 规则仍然盖在 补丁上面,和主题自己的一样。3.0.4 之前,这样的补丁会让内置层对该键失效,控件丢掉补丁没写的 所有属性。

这条规则正是升级时最容易踩的坑,也是本节最需要告诉你的一件事:

拆键 = 你的主题多了一批没写过的键。 从前 TyGauge { background: url(...) } 一条规则同时管住十四个仪表控件;现在 TTyMeter 解析 TyMeter,你的主题从没提过这个名字,于是 TyMeter 整键回落到 内置 light 值 —— 在一张图片皮肤上就是照片中间一块不透明的灰方块。 所以给 TyGauge 换过肤,就必须把 TyMeter、TyRating、TyDial……一起点名。

内置主题用并列选择器处理这件事,第三方主题照抄这个写法即可:

/* 一条规则,十八个可分别覆盖的名字 */
TyGauge, TyMeter, TyLevelMeter, TyDial, TyGearDial, TyAnalogClock, TyCircularProgress,
TyActivityIndicator, TyActivityBar, TyGearActivityIndicator, TySparkline, TyRating,
TyLColorPicker, TyHSColorPicker, TyMeterTick, TyAnalogClockHand, TyGearDialTeeth, TyColorArea {
  background: var(--surface-sunk);
  border-color: var(--border);
}

排查手段:tests/test.themes.pas 里的 alias guard 会把每个新键与它拆自的捐赠键逐条比对, themes/ 与 examples/theming/ 下所有样式表都在其覆盖范围内。自制主题可照它的做法自查。

8.2 怎么读下面的表

  • 画什么 —— 该键在控件上负责哪一块像素。写「文字色」意味着该键只被读 color, 写 background 意味着只被读背景,不写则表示整套盒模型属性都生效。
  • 谁解析 —— 解析这个键的控件类。一个键可能被多个控件解析(共用是有意的,见 §8.5)。
  • 状态 / 变体 —— 内置 light 主题实际写过的伪类与 .变体。 未列出不等于控件不上报该状态:任何键都可以写 :hover/:active/:focus/:disabled/:selected, 只要控件在重绘时上报了对应状态就会命中(§4.3)。反过来,少数键的解析点传的是空状态集 (如 TyLinkLabelLink),那么它的伪类选择器是死的 —— 表里会点名。

8.2.1 窗体与窗框

typeKey 画什么 谁解析 状态 / 变体
TyForm 窗体背景;经 ApplyChromeTheme 写进窗体 Color。内置 background: var(--form-bg),比内容表面略深。窗口圆角 border-radius、阴影 window-shadow 见 ttyform.md §8;macOS 上窗口圆角由系统决定,border-radius 不起作用,window-shadow 照样生效 TTyForm —
TyFormSurface 刻意不定义(§8.4) TTyFormSurface —
TyTitleBar 自绘标题栏条带(与 TyRibbonQuickAccess 共用一个规则块) TTyTitleBar —
TyCaptionButton 标题栏系统按钮 TTyCaptionButton :hover :active;变体 .close(:hover/:active)、.min(:hover)、.max(:hover)

8.2.2 按钮族

下面六个键共用同一个规则块,取值逐字节相同,但可分别覆盖:

typeKey 画什么 谁解析
TyButton 普通按钮外框 + 文字 TTyButton(及同族未重写键的控件:TTyGlyphButton、TTyColorButton、TTyDropDownButton、TTyMenuButton、TTyTransferArrowButton)
TySpeedButton 工具条上的快捷按钮 TTySpeedButton
TyGlyphContainerButton 带图标容器的按钮 TTyGlyphContainerButton
TyRibbonAppMenu Ribbon 左上角的应用菜单按钮 TTyRibbonAppMenu
TyButtonGroup 分段按钮组的整体外框 TTyButtonGroup
TyUpDown 上下微调器外框、中缝与箭头墨色 TTyUpDown

状态::hover :focus :active :disabled。 变体:.primary(:hover/:active)、.danger(:hover/:active)、 .ghost(平时透明,:hover/:active/:selected/:focus/:disabled 齐全)。

同族但独立成块的还有一个:

typeKey 画什么 谁解析 状态
TyBadge 按钮右上角的数字角标胶囊 TTyBadge、TTyButton 无状态

8.2.3 文本与静态标签族

下面七个键共用同一个规则块(透明底 + --on-surface 墨色 + 基准字号):

typeKey 画什么 谁解析
TyLabel 静态标题文字 TTyLabel
TyHtmlLabel 富文本块(逐 run 绘制、下划线/删除线带) TTyHtmlLabel
TyLinkLabel 超链接标签的盒子(字体/内容,不含链接墨色) TTyLinkLabel
TyShadowLabel 带投影的文字 TTyShadowLabel
TyGlowLabel 带光晕的文字 TTyGlowLabel
TyDivider 分隔线及其嵌入标题 TTyDivider
TyCharImage 字形/图标字符块 TTyCharImage

状态:仅 :disabled(opacity)。

typeKey 画什么 谁解析 注意
TyLinkLabelLink 链接墨色,只读 color TTyLinkLabel 解析时传空状态集,TyLinkLabelLink:hover / :disabled 是死选择器;悬停色由代码 TyLighten(col, 15) 合成

TTyHtmlLabel 里 <a> 的墨色不经任何 typeKey —— RenderTo 直接 ResolveOverride('color: var(--accent);'),钉死在 --accent。想改只能改 --accent。 TyHtmlLabelLink 是推迟项,不存在(§8.4)。

8.2.4 输入框族

typeKey 画什么 谁解析 状态 / 变体
TyEdit 单行输入框 TTyEdit 及同族未重写键的控件(TTyMaskEdit/TTyCurrencyEdit/TTyURLEdit/TTyNumericEdit/TTyCalcEdit/TTyValueEdit/TTyComboEdit/TTyTrackEdit);TTyCalculator 的显示条也显式解析它 :hover :focus :disabled
TySpinEdit 数字微调输入框 TTySpinEdit :hover :focus :disabled
TyMemo 多行文本框 TTyMemo :hover :focus :disabled
TyComboBox 下拉框字段(下拉箭头用 color) TTyComboBox 及同族的另外 11 个控件;TTyTreeSelect 刻意共用(§8.5) :hover :focus :disabled
TyCascader 级联选择字段 TTyCascader :hover :focus :disabled
TyDateTimePicker 日期时间字段 TTyDateTimePicker :hover :focus :disabled
TyTextSelection 文本选区高亮带,只读 background TTyEdit / TTyMemo / TTyDateTimePicker 无状态
TyTextHint 占位符 / 提示文字,只读 color TTyEdit / TTyTreeSelect 无状态

8.2.5 勾选、开关、滑杆、进度

typeKey 画什么 谁解析 状态
TyCheckBox 复选框的小方块(控件整体透明) TTyCheckBox、TTyCheckListBox、TTyDateTimePicker :hover :active :focus :disabled
TyRadioButton 单选钮的圆圈 TTyRadioButton :hover :active :focus :disabled
TyToggleSwitch 开关的轨道(Checked=True 时追加 :active) TTyToggleSwitch :hover :active :focus :disabled
TyToggleKnob 开关旋钮(tier-a 着色面) TTyToggleSwitch 无状态
TyTrackBar 滑杆轨道 TTyTrackBar :focus :disabled
TyRangeTrackBar / TyMultiTrackBar / TyGradientTrackBar 多滑块控件本体,父键 TyTrackBar(没写自己的规则时与 TTyTrackBar 一样) TTyRangeTrackBar / TTyMultiTrackBar / TTyGradientTrackBar :focus :disabled
TyTrackThumb 滑杆滑块(tier-a);:focus 的 outline 画在滑块内侧(outline-offset 往里缩),默认主题用 --on-accent 画一圈环。多滑块控件会在滑块自己的类后面再加两个变体:.removing(拖出轨道、松手就删的滑块;控件把它的 opacity 乘到这个滑块的颜色上,默认主题取 --disabled-opacity)、.stop(TTyGradientTrackBar 的色标;用色标自己的颜色填充,默认主题加 1px --on-surface 的边)。写了自己 TyTrackThumb 规则的主题要连这两个变体一起写(§8.1) TTyTrackBar、TTyRangeTrackBar、TTyMultiTrackBar、TTyGradientTrackBar、TTyTrackEdit :hover :active :focus(控件有焦点时方向键要动的那个滑块)
TyTrackSelection 滑杆选区(SelStart..SelEnd)和 TTyRangeTrackBar 的区间,读 background、border-radius TTyTrackBar、TTyRangeTrackBar 无
TyTrackSegment TTyMultiTrackBar 两个滑块之间的一段,按 Segments 项的 StyleClass 解析,读 background、border-radius。不带类时不画;默认主题有 .success .warning .danger 三个变体 TTyMultiTrackBar 无
TyProgressBar 进度条轨道 TTyProgressBar :disabled
TyProgressFill 进度条填充段(tier-a);部分填充时只圆起始角 TTyProgressBar 无状态

8.2.6 容器与面板

下面十七个键共用同一个规则块(表面色 + 1px 边框 + --radius + --pad-container)。 TyPanel 之外的每一个,从前都只能通过改 TyPanel 来影响 —— 也就是会牵动全应用的容器:

typeKey 画什么 谁解析
TyPanel 通用容器面板 TTyPanel;TTyRelativePanel / TTyPaintPanel 刻意共用(§8.5)
TyScrollBox 滚动井(约定上比面板下沉) TTyScrollBox;TTyScrollPanel 继承
TyScrollContent 滚动容器的视口(裁剪内容的那个窗口化子控件)。只画 background,不画边框——框归外面的滚动框画。这个键必须有值且必须不透明:控件除了这块底色什么都不画,解析不到就露出宿主未主题化的擦除色(渐变底皮肤如 aero 在暗色模式下就是一块亮斑) TTyScrollContent(TTyScrollBox / TTyScrollPanel 的视口)
TyExPanel 可折叠卡片的外框(其 padding.right 同时决定标题右内缩) TTyExPanel
TyChart 图表外框、标题、图例、刻度标签、两轴与网格线 TTyChart
TyCalculator 计算器背板(显示条另解析 TyEdit,按键是真的 TTyButton) TTyCalculator
TyBevel 凹凸装饰线(只画高光/阴影两条轨,不画面) TTyBevel
TySizeBox 右下角尺寸夹点 TTySizeBox
TyControlBar 可停靠工具条容器与其装饰性握把 TTyControlBar
TyCoolBar 可拖拽分带工具条(握把可交互) TTyCoolBar
TyColorGrid 色块矩阵与选中环 TTyColorGrid
TyShape 矢量图元(椭圆/矩形/三角/线) TTyShape
TyStarShape 星形图元 TTyStarShape
TyArrow 箭头图元 TTyArrow
TyImageView 缩放/平移图片背后的信箱底衬 TTyImageView
TyImage 图片框(Transparent=False 时才画框) TTyImage
TyPreviewBox 文件对话框的预览井 + 空态提示文字 TTyPreviewBox
TyListGroupPanel 导航手风琴的侧栏底面 TTyListGroupPanel

该块无状态规则。想让图片 :disabled 变淡,得自己写 TyImage:disabled { opacity: 0.5; } —— 内置主题没有为这一族声明任何状态规则。

typeKey 画什么 谁解析 状态
TyExPanelHeader 折叠面板的标题带:箭头 + 标题墨色与字体;background 是选择性的(写了才填带底,不写则透出面板表面) TTyExPanel :hover(带内悬停,内置未写,写了即生效)
TyGroupBox 分组框(必须声明 background,用于遮盖标题处的边框线) TTyGroupBox、TTyRadioGroup、TTyCheckGroup;TTyOfficeListBox/TTyOfficeComboBox 的分组标题带也解析它 —
TyToolGroupPanel Ribbon 风格的命令簇(与 TyGroupBox 共块) TTyToolGroupPanel —
TyGridPanel 刻意不定义(§8.4) TTyGridPanel —

8.2.7 滚动条、分隔条、状态栏、工具条

typeKey 画什么 谁解析 状态
TyScrollBar 滚动条轨道背景;端部箭头墨色用 color(tier-b) TTyScrollBar :hover :active :focus :disabled
TyScrollThumb 滑块的独立填充与圆角(tier-a) TTyScrollBar :hover :active
TySplitter 分隔条(内置 background: none,只有握把墨色) TTySplitter :hover
TyStatusBar 状态栏条带 TTyStatusBar —
TyToolBar 工具条条带 TTyToolBar、TTyToolBarEx —
TyToolSeparator 工具条分隔线:border-color 画线、background 让它与条带无缝(与 TyToolBar 共块) TTyToolSeparator —

8.2.8 列表框族

typeKey 画什么 谁解析 状态
TyListBox 列表外框 TTyListBox 及同族 14 个未重写键的控件;TTyComboBox/TTyValueListEditor 的下拉体、TTyPopupSurface、TTyGalleryGrid(画廊弹出网格)也解析它 :hover :focus :disabled
TyValueListEditor 属性网格外框(与 TyListBox 共块) TTyValueListEditor :hover :focus :disabled
TyRibbonGallery Ribbon 画廊的内嵌行外框(与 TyListBox 共块) TTyRibbonGallery :hover :focus :disabled
TyListItem 单行条目:background 决定行底、color 决定文字 TTyListBox;TTyRibbonGallery 的图块也仍解析它 :hover :active(=选中行)
TyValueListEditorRow 属性网格的一行(与 TyListItem 共块) TTyValueListEditor :hover :active
TyValueListEditorGroup 属性网格的标题行(TTyValueRow.Heading,属性表格的分类)平时的底色、文字色、字重;挂在 TyValueListEditorRow 下面 TTyValueListEditor —(悬停、选中时照 TyValueListEditorRow 画)

TyValueListEditorKey / -Value / -Divider / -Expander 由代码解析但基础层刻意不定义,见 §8.4。

8.2.9 树、列表视图、列头

typeKey 画什么 谁解析 状态
TyTreeView 树外框(与 TyListView 共块) TTyTreeView;TTyShellTreeView、TTyTreeSelect 的下拉树继承 —
TyListView 列表视图外框 TTyListView;TTyShellListView 继承 —
TyTreeNode 一个树节点行 TTyTreeView :hover :selected :disabled
TyListViewItem 一个列表视图条目(与 TyTreeNode 共块) TTyListView :hover :selected :disabled
TyTreeHeader 树的列头带(画在自己框内,不描边) TTyTreeView —
TyListViewHeader 列表视图的列头带 TTyListView —
TyListViewGroupHeader 列表视图的分组带(从前与列头带同键,无法分开配) TTyListView —
TyHeaderControl 独立表头条:它就是外框,故 border-radius/shadow 在这里有意义(与上面三个共块) TTyHeaderControl —
TyTreeHeaderSection 一个列头单元格 TTyTreeView;TTyHeaderControl 仍共用(其专属 section 键是推迟项) :hover :selected
TyListViewHeaderSection 列表视图的列头单元格(与上共块) TTyListView :hover :selected
TyTreeCheckBox 树节点勾选框 TTyTreeView :active :selected :disabled
TyListViewCheckBox 列表视图勾选框(与上共块) TTyListView :active :selected :disabled

TyListViewLine / TyListViewMarquee 由代码解析但基础层刻意不定义,见 §8.4。

8.2.10 数据网格 TTyGrid

网格自成一整套键,不借用树/列表的键。基础层给全套定义,所以新皮肤一条网格规则不写也能正常显示。 TTyDrawGrid / TTyStringGrid 刻意共用 TyGrid(§8.5)。网格的每一次解析都会带上控件的 StyleClass,所以 .变体 在子部件键上也有效 —— 这一点与 ListView / ValueListEditor 不同。

typeKey 画什么 状态
TyGrid 网格外框 —
TyGridCell 正文单元格(内置 background: none,让表面透出) :hover :selected
TyGridCellSelectedInactive 失焦时的选区(HideSelectionWhenInactive) —
TyGridActiveCell 焦点格(整行选中模式下指示光标所在格) —
TyGridCellMarked 选区盖在用户显式着色的格上时的半透明层 —
TyGridSelectionFrame 选区外框 + 右下角填充柄(color = 柄的描边色) —
TyGridCellAlt 斑马纹隔行底色 —
TyGridFixed 冻结行列区 —
TyGridIndicator 行头槽 —
TyGridHeader 列头带 —
TyGridHeaderSection 一个列头单元格 :hover :selected :active
TyGridHeaderGroup 分组表头带(横跨若干列的上层标题) —
TyGridFilterRow 内嵌筛选行(底色跟表面走、边框跟输入框走,以示"这里能打字") —
TyGridGroupRow 分组带。内置 background: none 是刻意的当前观感;想要 chrome 底色自行声明 —
TyGridSummaryRow 汇总/页脚带。刻意没有 border-color —— RenderFooter 不读,写了是死属性 —
TyGridLine 格线,只读 background;设成透明即可去掉格子 —
TyGridCheckBox 勾选框单元格 :selected
TyGridHyperlink 超链接单元格墨色 —
TyGridCommentMark 批注角标墨色 —
TyGridProgress 进度条单元格轨道 —
TyGridProgressFill 进度条单元格填充 —
TyGridRating 评分单元格的实心星 —
TyGridRatingEmpty 评分单元格的空心星 —
TyGridButton 按钮单元格(自己的键,免得改网格按钮波及全库按钮) :hover :active

同名警告:TyGridCell 被两个不相干的东西认领。 除数据网格的正文格外,TTyGridPanel 的布局格子 TTyGridCell 的 GetStyleTypeKey 也返回 'TyGridCell'。后者的 Paint 什么都不画,所以今天没有可见冲突;但它并不是一个 单独可寻址的钩子 —— 你写给数据网格的 TyGridCell 规则名义上也罩着它。

8.2.11 标签页族

typeKey 画什么 谁解析 状态
TyPageControl 多页控件的页容器外框 TTyPageControl :hover :focus :disabled
TyTabSheet 页体:只有不透明表面填充,无边框,以免透出 OS 窗体背景 TTyTabSheet —
TyTabSet 只有页签条、没有页体的标签条 TTyTabSet :hover :focus :disabled
TyTab 单个页签头(:active 在此表示选中而非按下) 页签条引擎(TTyPageControl / TTyTabSet);Ribbon 的页签头也解析它 :hover :active
TyTabClose 关闭 × 悬停时的圆角底片(× 笔画本身用 TyTab 的 color,tier-b) 页签条引擎 无状态

TyTabSet 因为 HasPageBody = False 而跳过 DrawFrame,只画一条基线导轨, 所以它实际只消费 border-color 与 border-width;写 background / border-radius / shadow 不会被画出来。专属的 TyTabSetRail 是推迟项,不存在(§8.4)。

8.2.12 菜单

typeKey 画什么 谁解析 状态
TyMenuBar 顶部应用菜单条 TTyMenuBar —
TyMenuView 下拉/右键弹出体 TTyMenuView —
TyMenuPopup 弹出宿主(与 TyMenuView 同值的镜像键) TTyMenuView —
TyMenuItem 一行菜单项 / 菜单条单元;基础态的 border-color 就是分隔线墨色 TTyMenuBar / TTyMenuView :hover :active :disabled

8.2.13 日历与日期

typeKey 画什么 谁解析 状态
TyCalendar 日历外框 TTyCalendar、TTyDateTimePicker 的下拉日历 —
TyCalendarTitle 顶部年月标题(粗体) TTyCalendar :hover
TyCalendarWeekday 星期行表头(弱化墨色) TTyCalendar —
TyCalendarCell 一个日期格 TTyCalendar :hover :selected :disabled
TyDateTimePicker 见 §8.2.4 TTyDateTimePicker :hover :focus :disabled

8.2.14 Ribbon 族

typeKey 画什么 谁解析 状态
TyRibbon 命令带表面 TTyRibbon;TTyRibbonPage 刻意共用(§8.5);TTyPopupSurface 的默认 StyleKey 也是它 —
TyRibbonGroup 带标题的命令分组框 TTyRibbonGroup —
TyRibbonBackstage 后台视图的内容面(与 TyRibbon 共块) TTyRibbonBackstage —
TyRibbonQuickAccess 快速访问条(与 TyTitleBar 共块) TTyRibbonQuickAccess —
TyRibbonAppMenu 见 §8.2.2 TTyRibbonAppMenu 同按钮族
TyRibbonGallery 见 §8.2.8 TTyRibbonGallery :hover :focus :disabled

两处仍在借用,是推迟不是设计。 后台视图的侧栏与命令行解析 ResolveStyle('TyButton', 'primary')(含 :hover/:active),所以侧栏只能是主按钮色; 画廊的图块仍解析 TyListItem、弹出网格仍解析 TyListBox。 TyRibbonBackstageSidebar / -Item / -Back / -Separator、TyRibbonGalleryItem / TyRibbonGalleryPopup 都不存在(§8.4)。

另:TyRibbonTab 这个名字从未被使用 —— Ribbon 的页签头解析的是 TyTab。 早先文档把它列为"预留",现已无此保留;不要为它写规则。

8.2.15 仪表与指示器族

从前十四个互不相干的控件全部硬返回 TyGauge、并且都解析同一个 TyGaugeFill, 于是星级评分、模拟钟秒针与进度环按构造是同一个颜色。现在各有其键,共用一个规则块。

表盘/底面(与 TyGauge 共块:下沉底 + 边框):

typeKey 画什么 谁解析 状态
TyGauge 仪表底面(本族的定义者,不是借用者) TTyGauge :disabled
TyMeter 仪表读数区与文字 TTyMeter :disabled
TyLevelMeter 电平表轨道 TTyLevelMeter :disabled
TyDial 旋钮底面 TTyDial :disabled
TyGearDial 齿轮旋钮底面 TTyGearDial :disabled
TyAnalogClock 表盘与刻度(color) TTyAnalogClock :disabled
TyCircularProgress 环形进度的轨道环 TTyCircularProgress :disabled
TyActivityIndicator 忙碌指示器的轨道环 TTyActivityIndicator :disabled
TyActivityBar 跑马灯进度条的轨道框 TTyActivityBar :disabled
TyGearActivityIndicator 齿轮忙碌指示器挖空的孔(角色与仪表相反:这里画的是孔不是轨道) TTyGearActivityIndicator :disabled
TySparkline 迷你折线图底面 TTySparkline :disabled
TyRating 评分控件底面 TTyRating :disabled
TyLColorPicker 亮度条外框与游标(条身是 HSV 生成像素,无子部件键) TTyLColorPicker :disabled
TyHSColorPicker 色相/饱和度方块外框与十字游标 TTyHSColorPicker :disabled
TyMeterTick 仪表刻度,只读 color TTyMeter 空状态集
TyAnalogClockHand 时针 + 分针,只读 color TTyAnalogClock 空状态集
TyGearDialTeeth 齿轮齿,只读 background TTyGearDial 空状态集
TyColorArea 颜色对话框的 HSV 方块 / 色相条的边框与游标 TTyHSVSquare / TTyHueBar 空状态集

点亮部分(与 TyGaugeFill 共块:background: var(--accent)):

typeKey 画什么 谁解析
TyGaugeFill 仪表填充。唯一一个硬写字面量的子部件键(ResolveStyle('TyGaugeFill', …)),其余都是 GetStyleTypeKey + '后缀' TTyGauge
TyMeterNeedle 仪表指针 TTyMeter
TyLevelMeterFill 电平条 TTyLevelMeter
TyLevelMeterPeak 峰值标线 —— 给它一个对比色,从前它与它要标示的条同色 TTyLevelMeter
TyDialPointer 旋钮指针 TTyDial
TyGearDialPointer 齿轮旋钮指针 TTyGearDial
TyAnalogClockSecondHand 秒针 + 中心轴 TTyAnalogClock
TyCircularProgressFill 环形进度的进度弧 TTyCircularProgress
TyActivityIndicatorFill 旋转弧 TTyActivityIndicator
TyActivityBarFill 行进中的色块 TTyActivityBar
TyGearActivityIndicatorFill 齿与盘体 TTyGearActivityIndicator
TySparklineFill 折线 TTySparkline
TySparklineDot 折线端点 TTySparkline
TyRatingStar 星形 TTyRating

TyRatingStar 是本族唯一带新状态轴的键:预览悬停时它是带 :hover 解析的 (Rating.pas),而内置主题刻意不写 TyRatingStar:hover,好让像素不动; 皮肤补上这一条即可给预览上色。

:disabled { opacity } 在本族大部分成员上是空转的 —— Opacity 只在 DrawFrame 里推进画笔,而本族只有 ActivityBar、LevelMeter、Sparkline 与 TTyGauge 的线性样式 走 DrawFrame。上表照实列出了主题里写了的 :disabled 规则,但其中多数今天渲染不出效果。

8.2.16 卡片、标签、提示、浮层

typeKey 画什么 谁解析 状态 / 变体
TyCard 卡片表面(头/尾是画在其上的带) TTyCard :hover :disabled
TyCardHeader 标题带:只有分隔线与标题墨色,无背景故透出卡片自身表面 TTyCard —
TyCardActions 底部动作带的分隔线 TTyCard —
TyTag 标签胶囊 TTyTag :disabled;变体 .accent .danger
TyTagClose 标签的关闭 × TTyTag :hover
TyAlert 内嵌提示条 TTyAlert :disabled;变体 .info .success .warning .error(硬契约:AlertType 直接映射到这四个名字)
TyAlertClose 提示条的关闭 × TTyAlert :hover
TyNotification 角落浮层通知 TTyNotification :hover;变体 .info .success .warning .error(变体的 color 是图标墨色)
TyNotificationClose 通知的关闭 × TTyNotification :hover
TyEmpty 空状态占位(透明,躺在空列表自己的表面上) TTyEmpty :disabled
TyEmptyImage 空状态插图的墨色(比正文更淡,故单独成键) TTyEmpty —
TyHint 主题化的 tooltip 表面(替代原生 LCL 提示) TTyHintWindow、TTyBalloonHint —
TyPopover 可承载控件的浮层表面(箭头是从同一表面切出的,故无需单独的键) TTyPopover —
TyPopoverTitle 浮层标题 TTyPopover —
TyChartTooltip 图表内的悬停提示框。图表不画这个框除非这个键给了背景 TTyChart —

8.2.17 导航与数据录入

typeKey 画什么 谁解析 状态
TySegmented 分段控件的凹槽 TTySegmented :focus :disabled
TySegmentedItem 一个分段(圆角刻意小于凹槽,读作"槽里的一片") TTySegmented :hover :selected :disabled
TyPagination 分页条(透明,页码本身才是 chrome) TTyPagination :disabled
TyPaginationItem 一个页码格 TTyPagination :hover :selected :disabled
TySteps 步骤条 TTySteps :disabled
TyStepsItem 步骤标记。状态即语义:已完成 = 无伪类、当前 = :selected、未走到 = :disabled,所以基础规则画的是已完成的样子 TTySteps :hover :selected :disabled
TyStepsConnector 步骤间连线,只读 background(刻意不声明 color);取它通向的那一步的状态 TTySteps :selected :disabled
TyBreadcrumb 面包屑条(分隔符取条带自己的墨色) TTyBreadcrumb :disabled
TyBreadcrumbItem 一节面包屑;:selected = 最后一节(当前位置,读作普通墨色而非链接) TTyBreadcrumb :hover :selected :disabled
TyTransfer 穿梭框外框(两个列表面与移动键复用 TyListBox / TyButton) TTyTransfer :disabled
TyTransferTitle 穿梭框每一侧的标题带 TTyTransfer —
TyCascaderPanel 级联多列面板 TTyCascaderPanel —
TyCascaderItem 级联面板中的一行 TTyCascaderPanel :hover :selected :disabled
TyListGroupHeader 导航手风琴的分组行(无底色,只有弱化墨色 + 右侧箭头;:selected = 该组已展开) TTyListGroupPanel :hover :selected
TyListGroupItem 导航项;:active = 选中,画成柔和内缩圆角药丸 TTyListGroupPanel :hover :active :disabled

TTyTreeSelect 没有自己的键,也不该有:它解析 TyComboBox(它就是一个下拉树的组合框), 弹出体是真的 TTyTreeView、由 TyTreeView 着色。写 TyTreeSelect { … } 是永不命中的死 CSS。

8.3 变体不是封闭集合

任何标识符都可以当变体,只要控件的 StyleClass 属性包含对应 token(空格分隔、可多个)即可匹配。 上表里的"变体"列只是内置主题实际定义过的那些:

  • 按钮族的 .primary / .danger / .ghost;
  • TyCaptionButton 的 .close / .min / .max(由窗体镶边自动赋给三个标题栏按钮);
  • TyTag 的 .accent / .danger;
  • TyAlert / TyNotification 的 .info / .success / .warning / .error —— 这四个是硬契约,控件的 AlertType / NotificationType 直接映射到这四个名字并据此画图标。

注意 §4.4:变体层在状态层之前应用,所以变体的悬停必须写成 Ty….变体:hover。

8.4 刻意不定义的键

下面这些键代码会解析,基础层刻意不给定义。它们不是漏网,而是可选钩子: 每一个在绘制侧都有明确的兜底,而且兜底是依状态而变的 —— 在这里写死一个值会移动像素, 而不是保持像素。皮肤可以任意声明它们,基础层保持沉默好让兜底继续生效。

typeKey 谁解析 不定义的理由 / 兜底
TyGridPanel TTyGridPanel 格子布局宿主不是一块面。没有主题背景时 DrawFrame 走 TyFillParentBg,于是格子之间的间隔取父容器的颜色,而不是在承载它的东西上戳出一块白面板。tests/test.gridpanel.pas 断言没有任何内置主题给这个键背景。想要可见的网格面,自己写 TyGridPanel { background: …; border: … } 即可
TyFormSurface TTyFormSurface **它没有自己的 CSS。**Surface 渲染的是宿主窗体的 form 背景(见其 Paint),并承载各自解析样式的子控件。中性键的意义是:不要让 TyForm 的 padding / border 意外套到 surface 自己的布局上
TyListViewLine TTyListView background → 否则取列表视图外框的 border-color
TyListViewMarquee TTyListView background → 否则取 TyListViewItem:selected 的背景(色相跟主题走、alpha 是常数)
TyValueListEditorKey TTyValueListEditor color → 否则取该行当前状态下的行色
TyValueListEditorValue TTyValueListEditor 同上
TyValueListEditorExpander TTyValueListEditor 同上
TyValueListEditorDivider TTyValueListEditor background → 否则取行色的 alpha 0x28

后四个是锋利边缘:选中行的兜底墨色是 --on-accent,所以在这里写一句平的 color: var(--on-surface) 会让选中行的键名变得读不出来。要声明就把 :active / :hover 变体一起声明。

已推迟、因而并不存在的子部件键

b824a49 / d58eada 只做了盒子键。审计同时提出的一批子部件键被刻意推迟—— 它们是可换肤性的扩展而不是覆盖缺口,而且其中数个会移动像素(代码目前在本该由键供色的 地方自己发明了一个颜色)。

不要为下面这些名字写规则,它们今天什么都不匹配:

TyHtmlLabelLink、TyButtonGroupItem、TyUpDownButton、 TyChartTitle / TyChartLegend / TyChartAxis / TyChartGrid / TyChartLabel / TyChartSeries1..8、 TyCalculatorDisplay / TyCalculatorExpression、TyBevelHighlight / TyBevelShadow、 TySizeBoxDot、TyControlBarGripper、TyCoolBarGripper、TyColorGridCell、 TyRibbonBackstageSidebar / -Item / -Back / -Separator、 TyRibbonGalleryItem / TyRibbonGalleryPopup、TyTabSetRail、 TyHeaderControlSection / -SortMark / -Divider。

这些控件的对应部位目前不可单独换肤;它们要么借用上表里已有的键,要么颜色/几何是 Pascal 常量。

8.5 刻意共用的键:借用是设计,不是漏网

约四十个控件仍然共用别的控件的 typeKey,这些是逐个审过、有据可查的 (理由见上面那份 plan 文档与两条 commit message)。判据是:皮肤是否应该能把它们分开看待。 不要"修"它们。

共用者 用的键 为什么共用是对的
TTyMaskEdit、TTyCurrencyEdit、TTyURLEdit、TTyNumericEdit、TTyCalcEdit、TTyCalcCurrencyEdit、TTyValueEdit、TTyComboEdit TyEdit 一个带掩码/带计算器下拉的输入框就是一个输入框。盒子、边框、内距、状态全是 TyEdit 的,差别只在输入过滤与尾部按钮的行为。共用键正是它们在同一行表单里不"起缝"的原因,也让 TyEdit.small 一次穿好全部
TTyFontComboBox、TTyFontSizeComboBox、TTyMRUComboBox、TTyColorComboBox、TTyCheckComboBox、TTyComboBoxEx 等 11 个 TyComboBox 同上,它们都是组合框字段
TTyTreeSelect TyComboBox 它画的三个记号与 TTyComboBox 逐一相同;下拉体是真的 TTyTreeView,由 TyTreeView 着色
TTyRadioGroup、TTyCheckGroup TyGroupBox 分组框就是"一圈带标题的框",框里是各自有键的真控件
TTyShellTreeView TyTreeView(整族) 外壳适配器自己不画一个像素;目录树就是树
TTyShellListView TyListView(整族) 同上
TTyDrawGrid、TTyStringGrid TyGrid(整族) 三层结构里只有 TTyCustomGrid 画像素
TTyRibbonPage TyRibbon 页面画的是 ribbon 盒子的严格子集,且刻意不画边框;给它另一种颜色会在每一页周围露出 1px 的 ribbon 本色
TTyRelativePanel TyPanel 它一个像素都不画(无 Paint、无 RenderTo),全部来自 TTyPanel.RenderTo;它只是一种布局策略,而布局策略不是皮肤需要区分的视觉身份
TTyPaintPanel TyPanel 与普通面板逐字节兼容,它就是面板
TTyScrollPanel TyScrollBox(继承) 它加的是手势不是表面;不多画任何东西,共用滚动井的键就是正确结果。注意它不再是 TyPanel —— 基类已经拆走
TTyGlyphButton TyButton 它就是一个内容区被切开的按下式按钮
TTyGalleryGrid(画廊弹出网格) TyListBox 见 §8.2.14 的推迟说明
TTyHeaderControl 的列头单元格 TyTreeHeaderSection 见 §8.2.9 —— 尚未拆分,不是"设计如此"

8.6 死键:主题里有、但没有任何代码解析

下列名字在部分随库样式表里有定义,但 source/ 里没有任何解析点。给它们写规则不会有任何效果。 (注意覆盖面并不一致:TyGridSelection 只出现在 light.tycss 一份里,TyTabControl 与 TyDateTimeButton 各出现在 6 份里 —— 所以"每份主题都有"这句是不成立的。)

typeKey 情况
TyTabControl TTyTabControl 这个类已被删除(commit d201419);该键只作为 TyTabSet 的并列选择器残留
TyDateTimeButton TTyDateTimePicker 的下拉按钮从不解析它
TyGridSelection 网格实际用的是 TyGridSelectionFrame
TyTreeSelect TTyTreeSelect 返回 'TyComboBox'(有意共用,见 §8.5)。该规则块 3.0 起已从 light.tycss 删除,现在连"有定义"都谈不上 —— 纯死名字

8.7 子部件着色的两层约定(tier-a / tier-b)

控件内部那些不属于"控件本体框"的小部件,按其是不是一块独立的着色面分两类处理颜色, 这是官方约定而非遗留缺口:

  • tier-a — 着色面(colored surfaces): thumb / knob / fill / pointer / needle 等本身就是一块需要 独立配色的实心面。它们各有专属子部件 typeKey(TyScrollThumb、TyToggleKnob、 TyTrackThumb、TyProgressFill,以及 §8.2.15 里那十四个 …Fill / 指针键), 颜色取该 typeKey 的 background、圆角取其 border-radius, 与父控件本体的 background/color 互不干扰。主题作者可单独为它们(及其 :hover/:active)配色。

  • tier-b — 单色字形(monochrome glyphs): 复选框勾号 / 单选圆点、ComboBox 下拉箭头、 SpinEdit 上下箭头、ScrollBar 端部箭头、Tab 关闭 × 等只是一笔单色墨迹,没有独立的面。 它们借用所属控件(或子部件)样式的 color(TextColor)作为墨色——这是官方约定, 不是"应当各给一个 typeKey 却漏了"。为这类字形配色,直接改对应控件 typeKey 的 color 即可 (例如 TyScrollBar { color: … } 改端部箭头墨色、TyTab { color: … } 改关闭 × 墨色)。

个别 tier-a 部件会同时叠加一个 tier-b 字形:页签关闭 × 的底片是 tier-a(TyTabClose 的 background),其上的 × 笔画是 tier-b(TyTab 的 color)。


9. 限制汇总(v1)

引擎层限制(均已在上文相应小节展开):

  1. 无组合选择器:不支持后代/子/通配/多变体/纯类/纯状态选择器(§4.2)。

  2. 重复选择器后写者胜,且是逐属性合并(§4.4)——与浏览器一致。 真正与直觉相反的是基础层的全有全无:主题只要提到某 typeKey 一次, 内置默认层对该键整体失效(§8.1),所以拆出来的新键必须逐个点名。

  3. url() 路径不能含空格:重建文件名时空格被无条件删除(§5.2); 路径相对进程工作目录解析,缺失文件静默跳过。

  4. shadow 颜色必须单 token:#hex / var(--x) / 裸 --x,不能用带逗号的 颜色函数;需要半透明用 #rrggbbaa(§5.12)。

  5. opacity 与 shadow 全控件生效(v1.1):v1.1 修复了 TyCheckBox 与 TyRadioButton 的渲染路径,使其也支持 opacity 和 shadow;所有 typeKey 均已生效。

  6. alpha() 第二参数不带 % 时是 0..1 小数:alpha(#fff, 50) 是完全不透明, 半透明写 0.5 或 50%(§6.3)。

  7. 渐变角度方向与 CSS 不同:0deg 左→右,90deg 上→下(§7.1); 只有线性渐变,方向只能写角度,不支持 to right 这类关键字(§7)。

  8. font-size 数值按 pt 解释,px 后缀只是装饰(§5.9);font-weight 渲染只分 ≥600 粗体 / 其余常规两档(§5.10)。

  9. font-family 不要加引号,引号会保留进字体名(§5.8)。

  10. 子部件着色分 tier-a / tier-b(Batch ④):thumb / knob / fill 等着色面由专属子部件 typeKey(TyScrollThumb、TyToggleKnob、TyTrackThumb、TyProgressFill)的 background 着色;勾号 / 箭头 / 关闭 × 等单色字形借用所属控件的 color(TextColor)作墨色。二者均为官方约定(§8.7)。TyScrollThumb / TyToggleKnob 的默认值与早前借用 color 渲染时逐字一致。

  11. 页签溢出可横向滚动(v1.10):所有页签头宽度之和超过控件宽度时,页签头条带进入溢出模式可横向滚动——条带左右两端渲染 tgArrowLeft / tgArrowRight 箭头按钮,鼠标在条带上滚轮也能滚动,且切换选中页时会自动把目标页签滚入可见区。绘制时页签头被裁剪到两个箭头之间的可见带(箭头始终绘制在最上层)。这套条带引擎由 TTyPageControl 与 TTyTabSet 共用;详见 controls/pagecontrol.md。 (原文此处写的是 TyTabControl —— 该控件类已删除,该 typeKey 现为死键,见 §8.6。)

  12. 不支持 @media、!important、转义字符串、// 行注释。 @import(必须先于所有 :root/规则)与 @mode 是支持的。

  13. 边框/盒模型的取舍(v1.6,v3 起加 3D 斜角):border-style 支持 none / solid / outset / inset(后两者为两色 3D 斜角,见 5.4),无 dashed、dotted、groove、 ridge;不存在 margin 属性(外边距请用容器布局实现)。border 简写只是 border-width / border-style / border-color 三者的合写,并不引入额外能力。

  14. border-radius 不支持的形式:每角长手写法(border-top-left-radius 等)、 百分比半径、椭圆双半径语法(<a> / <b>)均不支持(§5.6)。outline-offset 单独声明时不激活焦点环——必须同时声明 outline(§5.13)。

  15. 度量令牌(metric tokens,v3):虽然主题不能做通用布局(无 width/height/ margin/gap 属性,控件尺寸与排布归 .lfm/LCL),但部分控件的内在几何可用 :root 里的命名长度令牌调整,控件按名读取、未设时回退到内置常数(故 golden 不变):

    • --checkbox-size / --checkbox-gap — 复选框指示框边长 / 与标题的间距(默认 16 / 6);
    • --radio-size / --radio-gap — 单选钮同上(圆点随框尺寸等比缩放);
    • --groupbox-caption-height — GroupBox 顶部标题带高度(默认 16)。
    • --track-remove-distance — 多滑块控件里,滑块垂直于轨道拖出控件边缘多远算「拖出去了」,松手即删(默认 24)。
    • --listview-text-margin / --listview-icon-gap — 列表视图报表行内容的起点 / 图标与文字的 间距(默认 4 / 2);
    • --treeview-margin / --treeview-icon-gap — 树主列的前导留白 / 文字前的间距(默认 4 / 2)。 这两组还可以只给一个控件设,见 §3.4。
    • --memo-tab-size — Memo 的制表位间距,写法同 CSS 的 tab-size:写数字是几个空格宽,写长度 (如 32px)是像素(默认 8 个空格)。也可以只给一个 Memo 设,见 §3.4。
    • --popover-move-distance / --popover-show-duration / --popover-hide-duration — 浮层移入 走的距离(默认 8)、显示动画和淡出的毫秒数(默认 150 / 100,不带单位)。也可以只给一个 浮层设,见 §3.4。
    • --line-height — 多行标题的行框高度(logical px)。没有内置常数:未设时用字体 自己的行框,所以加上这个令牌之前一切不动;设了就是 行数 × 该值,多出来的部分作为行距 平分到每行上下。控件由此派生的高度下限(文字块高 + 上下 padding)也跟着走—— 主题把 font-size 和 --line-height 一起调小,下限就一起降下来。 值可为 16、16px 或 var()。(后续版本会陆续把更多写死几何提升为度量令牌。)
  16. 字形覆盖令牌(glyph override,v3):勾号/圆点等内置矢量字形可用图标字体码点替换。 在 :root 里写 --glyph-<槽>: "字体族名" "\<码点>";——族名须为系统已安装或应用已注册 的字体(自带 .ttf 的 url() 加载为后续扩展),码点为十六进制(前导 \ 可省)。当前支持的槽:

    • --glyph-check / --glyph-check-indeterminate — 复选框勾号 / 中间态;
    • --glyph-radio — 单选钮圆点;
    • --glyph-close / --glyph-minimize / --glyph-maximize / --glyph-restore — 标题栏按钮图标;
    • --glyph-arrow-up / --glyph-arrow-down / --glyph-arrow-left / --glyph-arrow-right — SpinEdit / 滚动条箭头;
    • --glyph-chevron-down / --glyph-chevron-right / --glyph-chevron-left / --glyph-chevron-up — 展开/折叠 chevron:树、Grid 树列、列表视图分组、ListGroupPanel、ExPanel、属性检查器(ValueListEditor / PropertyGrid)、菜单的子菜单标记、级联选择的分支行、Ribbon 折叠钮(-up / -down)。 -left 是 -right 的镜像伙伴:RTL 网格里折叠三角朝阅读前进的方向, 走的就是这个槽(见 docs/controls/grid.md);
    • --glyph-dropdown — 下拉字段的指示 V:组合框族、级联、树选择、日期选择、下拉 / 拆分按钮、 工具条下拉按钮、Ribbon 画廊、Grid 下拉单元格(都经 TyDrawDropChevron,一条规则全部生效)。

    展开标记的大小与墨色:上面两组 chevron 是同一个标记——90° 开口、圆头圆角、线宽为大小的 5/28、 顶点对齐整像素。--glyph-chevron-size 是它中心线的长边(默认 7,现代密度 8;以前只管下拉 V、 默认 9),槽位只决定位置、不决定大小;--glyph-chevron-alpha(0..255,默认 200)把它画得比 旁边的文字浅一些,设 255 即与文字同色。这组取值是对照 Windows 11、macOS、GNOME 同角色标记取的 共同区间(墨迹约为旁边正文大写字母高度的 0.9 倍)。

    下拉字段的按钮区:组合框族、组合编辑框、日期选择(下拉模式)、树选择、级联的按钮区宽 --field-button-width 加两侧各 --field-drop-pad(默认 5,即 18 + 10 = 28),下拉 V 在其中 居中、离右框约 10 px(Windows 11 约 16、GNOME 13、macOS 6.5–12;以前在 18 宽的区里居中,只有 5)。微调框的上下按钮只用 --field-button-width,不加这个余量。

    文本字段的左右内距:内置主题(light、auto、dark、system、green)里编辑框(及基于它的组合编辑框、 掩码编辑框、数字 / 货币 / 浮点微调框等)、组合框族、微调框、级联、日期选择的 padding 都是 var(--pad-field),默认 4px 9px(现代密度 8px 12px)。加上 1 px 边框,文字离外框 10 px,和下拉 V 离右框一样(以前用 --pad-control 的 4 px,文字只离 5 px;日期选择用 --pad-datetime,离 7 px)。 在 :root 里改这一个令牌就能统一调所有字段;复选框、标签页、菜单仍读 --pad-control,不跟着动。 内置皮肤保留各自的字段内距。

    令牌名遵循 --glyph-<kind> 约定,内部字形绘制统一走一个可覆盖入口,所以其余字形槽(日历翻页等) 如需也能一行接入——只是这些内部字形一般无人换肤,暂未接。 例:--glyph-check: "Segoe MDL2 Assets" "\e73e";。未设时用内置矢量字形。注意:设了有效覆盖 就一定用图标字体渲染(即便该字体/码点渲染为空);只有未设或格式错误才回退矢量。

  17. render-style 家族预设(v3):控件属性 render-style: flat | bevel3d | inset3d;—— bevel3d/inset3d 让 DrawFrame 自动应用凸起/凹陷两色 3D 斜角 + 方角 + 默认边宽(2)+ 边色缺省从(纯色)背景派生,省去在每个控件上重复写 border-style/border-width/ border-color/border-radius。flat(默认)= 原有行为。只作用于走 DrawFrame 的控件外框 (按钮/输入框/面板/进度条轨道/窗体等),以及页签(TyTab)、滚动条拇指(TyScrollThumb)、 滑块拇指与滑槽(TyTrackThumb / TyTrackGroove);复选框指示框等其他单独绘制的子部件不受其影响。 示例皮肤见 examples/theming/classic.tycss(经典 3D 风)。