Tinycast 设计令牌系统:ramp 与 adaptive 两个助手函数搞定深色浅色双主题
2026/9/20 22:25:37 网站建设 项目流程

Tinycast 设计令牌系统:ramp 与 adaptive 两个助手函数搞定深色浅色双主题

【免费下载链接】tinycastTinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history.项目地址: https://gitcode.com/GitHub_Trending/ti/tinycast

Tinycast 是一款小巧、全原生(fully native)的 macOS 启动器、热键与剪贴板历史工具。它的所有界面颜色都来自一套**设计令牌(Design Tokens)**系统,而撑起深色/浅色双主题的,其实只有两个助手函数:rampadaptive。这篇文章带你快速看懂这套令牌系统如何用最少的代码,让整款应用在深色和浅色外观下自动换色、零闪烁、零手工维护。

一、设计令牌:把"魔法数字"关进一个文件

在大型项目里最头疼的问题之一是:颜色、间距、圆角散落在全网代码里,想统一换肤就要满世界找。Tinycast 的解法很直接——所有视觉常量集中在一个枚举里:Theme.swift。

Theme就是唯一的"设计令牌来源"(single design-token source),内部按维度分组:

令牌组管什么例子
Theme.Spacing间距刻度xxs 2·md 8·xxl 20
Theme.Radius圆角面板26、行10、对话框20
Theme.Size尺寸面板宽750、底部栏高52
Theme.Duration动画时长面板进入0.18s、退出0.12s
Theme.Typography字体只用语义字体样式,支持动态字体
Theme.Colors颜色本文主角:rampadaptive

项目规则也很硬气:视图里禁止硬编码任何有令牌对应的颜色、间距、圆角(详见 docs/ui.md)。想加新颜色?先加令牌,再引用——这是整套系统能保持视觉一致的关键。

二、ramp 函数:一条"透明度斜坡"搞定文字与控件换色

先看ramp的完整定义,只有几行(Theme.Colors):

static func ramp(dark: Double, light: Double) -> Color { adaptive(dark: .srgbInk(1, alpha: dark), light: .srgbInk(0, alpha: light)) }

它的思路非常巧妙,一句话总结:深色主题下用"白色墨水",浅色主题下用"黑色墨水",透明度(alpha)就是颜色强度

  • dark: 0.10表示深色界面里 10% 透明的白色
  • light: 0.09表示浅色界面里 9% 透明的黑色
  • 墨水颜色由 srgbInk 统一构建,保证两种主题下的"同一个透明度档位"落在完全相同的像素逻辑上

于是每个令牌只需要两个透明度数字,深浅主题就自动配对了。docs/ui.md 里维护着这张核心对照表:

令牌深色(白)浅色(黑)用途
selection0.100.09选中行的填充
rowHover0.050.045鼠标悬停(永远比选中更淡)
textPrimary1.001.00搜索文字、光标
textSecondary0.600.60次要标签
border0.200.18描边式快捷键芯片
cardFill0.050.04设置/计算器卡片底色

为什么浅色档位的数字和深色不完全一样?因为白墨和黑墨在视觉上的"重量"不同——设计师会微调浅色值,让两个主题的观感完全一致,而代码结构一行都不用改。

三、adaptive 函数:当"简单反转"不够用时

ramp的公式是固定的"白色↔黑色反转",但有些颜色不是简单反转就能成立的。这时用更底层的adaptive(Theme.swift#L336-L340):

static func adaptive(dark: NSColor, light: NSColor) -> Color { Color(nsColor: NSColor(name: nil) { $0.isDark ? dark : light }) }

它接收两个显式的颜色,并包装成一个"动态颜色"——颜色会跟随所在窗口的effectiveAppearance自动解析和重绘。典型使用场景:

  • panelScrim(面板压暗层):ramp 的"反向"——深色主题里用 40%黑色压暗毛玻璃,浅色主题里反而要用 55%白色提亮。同白同黑的反转在这里不成立。
  • glassFrost(液态玻璃的霜感):两种主题都必须是白色,因为霜的作用是"把玻璃提亮",而不是"上墨"。
  • layoutPreviewGround(窗口布局预览的底板):屏幕预览里画的"显示器"永远是深色的,所以两种主题都保持深色,用ramp反而会在浅色主题下错误地反转。

一句话判断法则:

🌗颜色是"白墨↔黑墨"的镜像关系 → 用ramp;两个主题要各自独立选色 → 用adaptive

四、真实代码里怎么用

令牌在视图中的引用非常克制,例如底部栏按钮 BarButton.swift 选中时填充Theme.Colors.selection,悬停时填充更淡的rowHover——选中永远压过悬停,所有列表因此看起来完全一致。

第三方扩展屏面也有自己的一套颜色,存放在 ExtensionColors.swift:它不放进Theme(第三方界面不能反过来牵动启动器界面),但复用同一个ramp机制,只是数值归功能自己管:

static let fieldFill = Theme.Colors.ramp(dark: 0.05, light: 0.04) static let checkboxStroke = Theme.Colors.ramp(dark: 0.26, light: 0.30)

这种"机制共享、数值自治"的分层,正是整套设计令牌系统能长期不腐化的原因。

五、新手最容易踩的 3 个坑

  1. 在视图里直接写Color.white.opacity(0.1)—— 深色主题下看起来完美,切到浅色主题就"消失"了。规则明确写着:永远走Theme.Colors.*
  2. 该用ramp的地方用了adaptive(或反过来)——ramp的价值在于"墨水自动反转 + 透明度档位语义清晰";滥用adaptive手写两个颜色,会让"档位表"失去意义。
  3. 改动深色主题的既有数值—— 项目的深色档位是"冻结"的设计基线(frozen baseline),浅色档位则是唯一允许自由微调的列。想改深色?那是另一个明确的任务,而不是一路顺手改。

六、小结

  • Theme.swift是唯一设计令牌来源,间距、圆角、尺寸、时长、字体、颜色各归其组,视图里禁止魔法数字。
  • ramp(dark:light:)用"透明度斜坡 + 墨水自动反转"让 90% 的颜色一行声明即双主题就绪。
  • adaptive(dark:light:)是为"非镜像"颜色保留的逃生舱:压暗层、玻璃霜感、恒暗预览底板都是它的用武之地。
  • 想深入了解更多规则(同心圆角、边缘溶解、液态玻璃),官方 UI 文档在 docs/ui.md,架构总览见 docs/architecture.md。

两个函数、一张档位表,就是 Tinycast 深色浅色双主题的全部秘密——简单,正是它能长期保持一致性的原因。

【免费下载链接】tinycastTinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history.项目地址: https://gitcode.com/GitHub_Trending/ti/tinycast

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询