风格切换
三种风格
| 风格 | id | 一句话 | 适合 |
|---|---|---|---|
| 青藤 Clean | clean |
安静克制,白墙上只在关键处冒出绿意 | 团队日常协作、长时间盯屏;默认风格 |
| 萌虎 Sticker | sticker |
粗描边、硬阴影、糖果色,完成任务像贴贴纸 | 个人、学生、小团队 |
| 像素 Pixel | pixel |
等宽字体、像素小虎、快捷键提示常驻,信息密度最高 | 开发者、运维、键盘党 |
每种风格都有亮色和暗色,主题 id 写作 <style>-<mode>,一共 6 套:clean-light、clean-dark、sticker-light、sticker-dark、pixel-light、pixel-dark。
什么会变,什么不变
切换风格只换皮,不搬家具。
| 跟着风格变 | 所有风格都一样 |
|---|---|
| 颜色(53 个语义 token 的取值) | 信息架构:概览、即将到期、通知、项目树、过滤器、团队、管理面板 |
| 字体与字号阶梯 | 页面分区:侧栏、列表、详情面板、看板分栏的位置 |
| 圆角、描边粗细、阴影 | 组件清单和组件的属性 |
| 密度:行高、控件高度、侧栏宽度 | 快捷键 |
| 图标描边粗细和端点 | 文案的意思(口吻可以不同) |
| 动效节奏 | 语义色的含义:红是紧急或逾期,黄是高优先级或今天到期,绿是完成 |
| 少数组件写法(勾选框、优先级、标签、指派人、吉祥物、空状态口吻) | 无障碍要求 |
风格专属写法只限于 README「使用规则」表里列出的几处,组件靠 data-theme 前缀或 Flutter BiShape 里的枚举来分支,其余一律走 token。设计稿里出现过的「萌虎详情用弹窗」「像素底部状态栏」这类布局差异不跟风格走:详情面板在三种风格里都是右侧面板(窄屏全屏);快捷键提示条是一个独立开关「显示快捷键提示」,像素风格默认打开,其他风格默认关闭。
设置入口
- 「设置 › 外观」放
ThemeSwitcher:三张风格卡片(每张带当前明暗下的缩略图),下面是「跟随系统 / 亮色 / 暗色」三选一,最后是「App 图标跟随风格」开关。 - 命令面板(⌘K / Ctrl K)里有「切换风格…」和「切换明暗」两个命令。
- 新用户默认
clean+ 跟随系统。首次启动不弹风格选择,避免多一步。
存储与同步
- 把选择存成
{ "style": "clean", "mode": "system", "appIcon": false },写进 BostonIvy OS 的用户前端设置(frontend_settings.bostonivy.appearance),这样 Web、macOS、Android 三端一致。 - 每台设备再存一份本地缓存,启动时先用缓存渲染,避免先闪一下默认主题,再被服务器配置覆盖。服务器返回不同的值时,以服务器为准并刷新界面。
mode为system时由各端自己解析:Web 用matchMedia('(prefers-color-scheme: dark)')并监听变化,Flutter 用ThemeMode.system。
实现要点
Web(bostonivy-web)
<head>
<link rel="stylesheet" href="/bostonivy-tokens.css">
<script>
// 在首帧之前设置主题,避免闪烁
try {
var a = JSON.parse(localStorage.getItem('bostonivy.appearance') || '{}');
var dark = a.mode === 'dark' || (a.mode !== 'light' && matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.dataset.theme = (a.style || 'clean') + '-' + (dark ? 'dark' : 'light');
} catch (e) {}
</script>
</head>
组件只读语义变量和风格别名(var(--accent)、var(--radius-md)、var(--type-task)),不认具体风格。需要按风格换写法的那几处,用 [data-theme^="pixel-"] .x { … } 这样的选择器。
Flutter(bostonivy-macos、bostonivy-android)
MaterialApp(
theme: bostonIvyTheme(style, Brightness.light),
darkTheme: bostonIvyTheme(style, Brightness.dark),
themeMode: mode, // ThemeMode.system / light / dark
);
// 组件里
final c = Theme.of(context).extension<BiColors>()!;
final s = Theme.of(context).extension<BiShape>()!;
if (s.checkbox == BiCheckboxVariant.bracket) { /* 像素风格的 [ ] 勾选 */ }
切换风格时用 AnimatedTheme 过渡,时长取 s.motionDuration;像素风格的过渡时长是 80ms,等于直接切换。
App 图标跟随风格
- macOS:运行时设置
NSApplication.shared.applicationIconImage,Dock 图标即时生效;Finder 里的图标不变。默认开启。 - Android:为每种风格声明一个
activity-alias,切换时启用对应别名。部分桌面会因此重启应用或丢失快捷方式,所以默认关闭,打开前提示一句「切换图标时应用可能会重新启动」。 - Web:同步替换
<link rel="icon">。