Theme API 参考
Theme API 负责语义颜色、字体、尺寸、动效、高程和主题切换。业务系统应通过主题定义调整整体视觉,通过组件公开参数表达局部差异,不应复制框架内部的组件状态配方。
主题编译模型
DirectSurface UI 将“主题作者输入”和“运行时消费对象”明确分开:
ThemeDefinition
→ compileTheme() / createTheme()
→ ResolvedTheme
→ 组件内部样式配方
→ 绘制值
ThemeDefinition:主题作者输入。根 token 和六个嵌套层都可以只覆盖需要修改的字段。ResolvedTheme:完整、深冻结且只读的运行时主题。组件和 runtime 只消费该类型,不在绘制或交互期间补默认值。compileTheme(base, definition):在一个完整基础主题上编译主题定义,复制输入并返回新的ResolvedTheme。createTheme(base, definition):面向应用代码的等价便捷入口。freezeTheme(theme):冻结已经完整解析的主题。通常无需直接调用。
ThemeData 和 ThemeOverrides 已从公开 API 删除。迁移旧代码时,分别改用 ResolvedTheme 和 ThemeDefinition。
框架内置完整主题:
ImGuiLightThemeImGuiDarkThemeMedicalCompactLightThemeModernCompactLightTheme
对比度模式
ResolvedTheme.contrastMode 是主题的渲染策略,不是根据颜色亮暗推断出来的状态:
'normal':默认模式。组件保留业务数据或文档明确配置的颜色,并在必要时修正屏幕文字的可读性。'high':强制高对比度模式。支持该策略的文档型组件可以用主题语义色覆盖文档颜色,但不会修改业务模型、持久化数据或导出内容。
内置主题均使用 'normal'。创建高对比度主题时应显式声明:
const HighContrastTheme = createTheme(ImGuiLightTheme, { contrastMode: 'high', surfaceDataBody: rgba(0, 0, 0), textPrimary: rgba(255, 255, 255), borderData: rgba(255, 255, 255),})
包入口导出 ThemeContrastMode 类型。不要通过判断 surfaceCanvas 是否为黑色等颜色特征来识别高对比度主题;这种判断会把普通深色主题误当成强制高对比度模式。
创建业务主题
推荐基于最接近业务场景的完整主题提供一个 ThemeDefinition:
import { ModernCompactLightTheme, createTheme, rgba, type ResolvedTheme, type ThemeDefinition,} from 'ds-ui' const CompanyThemeDefinition: ThemeDefinition = { accentPrimary: rgba(18, 104, 180), focusBorder: rgba(18, 104, 180), typography: { titleFontWeight: 600, }, elevation: { popupShadowBlur: 18, popupShadowOffsetY: 6, }, chart: { negative: rgba(185, 28, 28), },} export const CompanyTheme: ResolvedTheme = createTheme( ModernCompactLightTheme, CompanyThemeDefinition,)
编译过程会:
- 合并根 token。
- 分别深度合并六个嵌套层。
- 复制颜色对象、数组和嵌套对象,不修改
base或definition。 - 补全完整运行时契约。
- 深度冻结最终主题。
不要把 ThemeDefinition 直接传给组件,也不要在 paint、layout 或 pointer 事件中重复编译主题。
Surface 层级
Surface token 表达区域层级,不表达具体颜色:
| Token | 语义 |
|---|---|
surfaceCanvas |
应用 Canvas 的最外层底色和浮动窗口之后的工作区。 |
surfaceContent |
页面、文档和窗口客户区的主要内容底色。 |
surfaceWindowTitle |
非活动窗口标题栏。 |
surfaceWindowTitleActive |
当前活动窗口标题栏。 |
surfacePanel |
卡片、工具面板和页面分区。 |
surfacePopup |
Dropdown、Menu、Tooltip 等脱离布局流的临时浮层。 |
surfaceControl |
普通输入框、按钮和选择器。 |
surfaceControlHover |
普通控件的 hover 表面。 |
surfaceControlActive |
普通控件的 pressed 表面。 |
surfaceNav |
导航栏、tab strip、toolbar chrome 和 dock rail。 |
surfaceDataHeader |
Grid/Table/Tree 的表头、分组头和数据 chrome。 |
surfaceDataBody |
Grid/Table/Tree 的数据正文区域。 |
surfaceWindow 已被删除。旧代码应根据真实语义改用 surfaceCanvas、surfaceContent 或 surfaceDataBody,不要把三种用途机械映射到同一个 token。
交互与编辑 token
stateHoverOverlay 和 statePressedOverlay 是可叠加在当前基础 surface 或 selection 上的交互覆盖色。它们不是独立页面背景;组件内部配方负责合成透明度和组合状态。
字段编辑使用:
fieldBg:输入或表格编辑字段背景。fieldFocusBorder:字段编辑焦点边框。fieldSelectionBg:字段内文本选择背景。
editor.selectionBg / editor.selectionText 保留给 PlainTextEditor、CodeEditor、Markdown 等文档编辑器。字段选择和文档编辑器选择是不同语义,不应互相替代。
Popup 阴影全部位于 elevation 层。根级 popupShadow* 已删除。accentSuccessActive 和 accentWarningActive 也已删除;成功、警告通常是状态语义,不应模拟持续的 pressed 主操作。
必需设计系统层
每个 ResolvedTheme 都必须包含以下六个完整层:
| 层 | 内容 | 典型消费者 |
|---|---|---|
typography |
body/title/secondary 的 size、lineHeight、weight,以及等宽字体。 | Text、PageHeader、Section、数据摘要。 |
motion |
fast/normal/slow 过渡时长。 | Button、Tabs、Modal、Drawer、Tooltip、Notification。 |
elevation |
Modal/Drawer 遮罩,Popup/Window/Card 阴影和 Card 高光。 | 所有浮层和高程容器。 |
editor |
文档编辑选择、搜索命中和当前命中。 | PlainTextEditor、CodeEditor、Markdown。 |
chart |
图表序列色、负值色和性能时间线辅助色。 | Charts、PerformanceTimeline。 |
syntax |
string/number/boolean/function/type/meta 语法色。 | 编辑器和结构化 token。 |
根级 fontFamily 是通用界面字体,根级 fontSize 是普通控件默认字号。typography 负责正文、标题和辅助文字的角色化 size、lineHeight 与 weight;monoFontFamily 只用于代码、日志和等宽数据。包入口同时导出 DefaultFontFamily 与 DefaultMonoFontFamily 作为内置主题的默认字体常量。
颜色工具与主题校验
公开工具:
rgba()lerpColor()colorToCSS()contrastRatio()resolveContrastText()resolveSelectionText()validateTheme()haveSameThemeMetrics()
主题编译器会在注册前拒绝 NaN、无穷数、稀疏/空图表色板,以及缺少 r/g/b/a 数字通道的颜色。
validateTheme(theme) 继续检查关键前景/背景对比度、颜色通道范围、状态区分、字体和密度契约,
用于报告结构正确但视觉质量不合格的主题。自定义主题应在测试或构建检查中验证:
import { ModernCompactLightTheme, validateTheme } from 'ds-ui' const issues = validateTheme(ModernCompactLightTheme)if (issues.some(issue => issue.severity === 'error')) { throw new Error(JSON.stringify(issues, null, 2))}
通用选中态使用 selectionBg / selectionStrong 与 textOnSelection。当实际背景经过组合时,使用 resolveSelectionText(theme, actualBackground) 计算可读前景,不要默认把浅色选择背景搭配 textOnAccent。
组件样式配方边界
组件样式配方负责把语义主题转换为 Button、Grid、Dock、Popup 等组件的最终状态值。它们是框架内部实现,会被缓存并可随组件实现调整。
业务代码不应:
- 从
src/theme/component_styles或构建产物内部路径导入。 - 依赖
derive*Style()或内部*StyleTokens的对象形状。 - 在页面代码中复制 hover、selected、focused、disabled 的合成逻辑。
组件文档中出现的 deriveButtonStyle()、deriveGridCellStyle() 等名称仅用于说明内部视觉来源,不代表包入口的稳定公共 API。公开边界是主题定义、主题校验工具、组件 options 和明确列入组件文档的样式覆盖项。
ModernCompactLight v2
ModernCompactLightTheme 面向高密度桌面业务界面。v2 遵循以下层级原则:
- Canvas、内容区、面板、数据正文和浮层有明确层级,不用同一层浅蓝铺满页面。
- 数据正文保持中性和高可读,导航与数据 chrome 可以使用更冷的色调建立结构。
- Popup、Modal、Window 通过 surface、边框与 elevation 共同从内容区抬升。
- hover 是轻反馈,selected 是持续状态,pressed 更明确,focus ring 独立叠加。
- 13px 基础字号、28px 普通控件、29px 数据行和小圆角保持紧凑;视觉现代化不以放大控件或增加无效留白为代价。
Theme Lab
Theme Lab 使用同一组固定组件样本对比内置主题,覆盖 surface 层级、角色化排版、控件组合状态、DataGrid 密度、Tree/Table、Dock、Popup、Window 和 Notification。修改主题 token 或组件样式配方后,应先在这里做横向视觉复核,避免只优化单个页面。
运行 npm run dev:showcase 后访问:
http://127.0.0.1:4175/showcase/?example=theme-lab
生产站点对应 /showcase/index.html?example=theme-lab。Website 的组件示例目录也提供“主题视觉实验室”入口和真实 TypeScript 源码。
运行时切换
通过 Application.run(..., { theme }) 传入的主题和 host.setTheme(theme) 都要求完整 ResolvedTheme:
import { ImGuiLightTheme } from 'ds-ui' host.setTheme(ImGuiLightTheme)host.useMainWindowTheme()
setTheme() 设置显式运行时主题;useMainWindowTheme() 恢复由主窗口提供的主题。主题值相同的新对象不会重复触发布局;颜色变化只需重绘,字体、lineHeight 和几何尺寸变化才需要重新布局。
Canvas 文本绘制依赖字体就绪。loadDirectSurfaceFonts() 会等待当前 document.fonts.ready 并清理文本测量缓存;自定义字体资源仍由宿主应用声明和加载。