Files
kindle-calendar/dash/docs/theme-switching-plan.zh.md
2026-03-17 10:37:27 +08:00

20 KiB
Raw Permalink Blame History

Kindle Dashboard 多主题与方向方案

0. 当前实现状态2026-03-16

以下能力已经落地,不再只是设计目标:

  • calendar/ 网站已支持 theme + orientation 两维预览
  • mode=background 下不会显示预览菜单,避免污染截图
  • calendar/ 已产出:
    • dist/themes.json
    • dist/themes/<theme-id>.json
    • dist/themes/<theme-id>/<orientation>/kindlebg.png
  • 兼容输出仍保留:
    • dist/kindlebg.png
    • dist/clock-region.json
  • Kindle 已支持:
    • 拉取 themes.json 和主题级 JSON
    • 通过 /mnt/us/dashboard/switch-theme.sh <theme-id> [orientation] 立即切换
    • 优先使用本地已同步的主题背景图
    • 优先使用本地已同步的主题索引和主题 JSON
    • 本地没有对应背景图时,再回退到远端 background.url
  • 同步脚本 scripts/sync-layered-clock-to-kindle.sh 会先批量导出全部主题背景,再把 /mnt/us/dashboard/themes/ 同步到设备

0.1 横向主题的抓屏评审约定

fbgrab 抓到的是 Kindle 的原始 framebuffer 图,尺寸始终是纵向的 1072 x 1448

这意味着:

  • portrait 主题下,physical.pngraw.png 相同
  • landscape + logo_right 主题下,日常评审应优先看 physical.png
  • raw.png 只用于排查原始 framebuffer 坐标
  • physical.png 等于把 raw 图按设备实际摆放方向逆时针旋转 90 度后的结果

仓库里已经补了一个辅助脚本:

sh scripts/capture-kindle-screen.sh kindle landscape-check

它会同时输出两张图:

  • tmp/landscape-check-raw.png
  • tmp/landscape-check-physical.png

其中:

  • raw.png 用于排查 framebuffer 原始坐标
  • physical.png 用于按 Kindle 实际摆放方向评审横向主题

1. 背景

当前链路已经稳定分成两部分:

  • calendar/ 负责渲染网页和导出背景图
  • dash/ 运行在 Kindle 上,负责拉取背景图并在本地重画时钟

这次方案先明确两个概念:

  • theme:视觉主题,例如 defaultpaperclassic
  • orientation:显示方向,例如 portraitlandscape

其中方向不是主题名的一部分,而是每个主题都必须包含的两套样式:

  • portrait
    • Kindle 竖向摆放
    • 设备外部面板上的 kindle logo 在下方
  • landscape
    • Kindle 横向摆放
    • 设备外部面板上的 kindle logo 在右侧

也就是说,后续系统模型应当是:

  • 每个主题都包含纵向和横向两套显示样式
  • Kindle 端仍然保持“拉背景图片 + 绘制时钟”的职责
  • 时钟的位置、尺寸和绘制参数都由 calendar/ 提供

2. 目标

本方案的目标是:

  1. 访问网站时,可以切换主题和方向进行预览
  2. 网站预览效果应尽量与 Kindle 最终显示效果一致
  3. 当前背景生成流程继续可用,不因预览能力被破坏
  4. calendar/ 提供统一的主题清单 JSON作为主题与方向配置的单一真相源
  5. Kindle 只需要读取固定位置的主题清单和主题配置 JSON
  6. Kindle 切换主题或方向后,应立即拉取对应背景并刷新
  7. 时钟的位置、尺寸和绘制参数由 calendar 的主题配置决定Kindle 不自行决定

非目标:

  • 不在 Kindle 上重新渲染整张页面
  • 不让 Kindle 维护主题路径规则
  • 不让 Kindle 自行推导时钟区域布局
  • 不要求第一阶段就做“本地表盘素材包”体系

3. 设计原则

3.1 calendar/ 是单一真相源

主题系统的单一真相源放在 calendar/ 侧。

建议由 calendar/ 产出:

  • 一个主题清单文件,例如 themes.json
  • 每个主题自己的配置文件,例如 default.jsonpaper.json
  • 每个主题在不同方向下的背景图资源

其中 themes.json 至少描述:

  • 有哪些主题
  • 每个主题叫什么名字
  • 每个主题的配置 JSON 在哪里
  • 默认主题是谁
  • 默认方向是谁

这样做的好处是:

  • 主题路径由 calendar 管理,不在 Kindle 端写死
  • 新增主题时Kindle 无需改代码里的路径规则
  • 网站预览和 Kindle 切换都依赖同一套主题元数据

3.2 themeorientation 分开建模

不建议把“横向主题”“纵向主题”作为主题名本身。

推荐模型是:

  • theme
  • orientation

例如:

  • theme=default + orientation=portrait
  • theme=default + orientation=landscape
  • theme=paper + orientation=portrait

这样更符合实际含义:

  • theme 代表视觉风格
  • orientation 代表设备摆放方向与对应布局

3.3 网站预览与导出解耦

主题和方向预览只是网站访问时的交互能力,不应破坏当前导出主流程。

当前已明确三条链路:

  • 预览链路:用户访问 /?mode=full&theme=<id>&orientation=<orientation>,通过页面菜单切换主题和方向
  • 兼容导出链路:export:background 继续生成默认背景图,保持兼容
  • 批量导出链路:export:themes 一次性生成全部主题和方向背景

也就是说:

  • 预览能力已经落地
  • 当前 kindlebg.png 导出链路继续保留
  • 多主题多方向导出已经接入

3.4 Kindle 侧最简化

Kindle 侧只保留最小能力:

  • 拉取背景图
  • 读取主题 JSON
  • 根据当前方向读取对应 variant
  • 按 JSON 提供的时钟参数绘制时钟

Kindle 不负责:

  • 推导主题路径
  • 决定时钟区域坐标
  • 维护布局规则

推荐让 Kindle 只认识一个固定入口,例如:

https://shell.biboer.cn:20001/themes.json

然后从这个 JSON 中知道:

  • 当前有哪些主题
  • 默认主题和默认方向是什么
  • 每个主题的配置 JSON 在哪里

3.5 当前时钟实现已经足够轻量

当前 Kindle 侧时钟是本地几何绘制,不依赖外部表盘或指针素材文件。

这意味着第一阶段主题与方向切换只需要关注:

  • 背景图 URL
  • 时钟区域位置和尺寸
  • 本地绘制时钟所需的参数

不需要先引入复杂的“主题素材包”机制。

4. 数据约定

4.1 主题清单 themes.json

建议由 calendar/ 在固定位置提供主题清单,例如:

{
  "updatedAt": "2026-03-16T10:00:00+08:00",
  "defaultThemeId": "default",
  "defaultOrientation": "portrait",
  "themes": [
    {
      "id": "default",
      "label": "Default",
      "configUrl": "https://shell.biboer.cn:20001/themes/default.json",
      "orientations": ["portrait", "landscape"]
    },
    {
      "id": "paper",
      "label": "Paper",
      "configUrl": "https://shell.biboer.cn:20001/themes/paper.json",
      "orientations": ["portrait", "landscape"]
    },
    {
      "id": "classic",
      "label": "Classic",
      "configUrl": "https://shell.biboer.cn:20001/themes/classic.json",
      "orientations": ["portrait", "landscape"]
    }
  ]
}

第一阶段建议先保留当前主题并命名为 default,然后新增:

  • paper
  • classic

4.2 主题配置 <theme-id>.json

每个主题提供一份主题级配置,内部包含两个方向的 variant例如

{
  "id": "default",
  "label": "Default",
  "variants": {
    "portrait": {
      "devicePlacement": "logo_bottom",
      "background": {
        "path": "themes/default/portrait/kindlebg.png",
        "url": "https://shell.biboer.cn:20001/themes/default/portrait/kindlebg.png",
        "refreshIntervalMinutes": 120
      },
      "clock": {
        "x": 347,
        "y": 55,
        "width": 220,
        "height": 220,
        "faceRadiusRatio": 0.47,
        "faceStroke": 3,
        "tickOuterInset": 6,
        "majorTickLength": 14,
        "minorTickLength": 7,
        "majorTickThickness": 4,
        "minorTickThickness": 2,
        "hourLengthRatio": 0.48,
        "minuteLengthRatio": 0.72,
        "hourThickness": 9,
        "minuteThickness": 5,
        "centerRadius": 7
      }
    },
    "landscape": {
      "devicePlacement": "logo_right",
      "background": {
        "path": "themes/default/landscape/kindlebg.png",
        "url": "https://shell.biboer.cn:20001/themes/default/landscape/kindlebg.png",
        "refreshIntervalMinutes": 120
      },
      "clock": {
        "x": 795,
        "y": 659,
        "width": 220,
        "height": 220,
        "faceRadiusRatio": 0.47,
        "faceStroke": 3,
        "tickOuterInset": 6,
        "majorTickLength": 14,
        "minorTickLength": 7,
        "majorTickThickness": 4,
        "minorTickThickness": 2,
        "hourLengthRatio": 0.48,
        "minuteLengthRatio": 0.72,
        "hourThickness": 9,
        "minuteThickness": 5,
        "centerRadius": 7,
        "rotationDegrees": 90
      }
    }
  }
}

说明:

  • 背景图真实路径由主题配置提供,不在 Kindle 端拼接猜测
  • background.path 是设备本地相对路径,用于优先读取已同步的本地背景
  • 每个方向都有自己的背景图和时钟区域
  • landscapeclock 已经是 Kindle 设备坐标,不再是网页坐标
  • landscaperotationDegrees=90 表示 Kindle 本地绘制时钟时,表盘和指针再顺时针旋转 90 度
  • 时钟坐标和大小来自 calendar 中该主题该方向下的时钟占位,不允许 Kindle 任意改
  • 后续如果某个主题在不同方向下需要不同绘制参数,也由该 JSON 一起提供

4.3 远端配置边界

远端主题配置只有两类:

  • themes.json
  • 每个主题自己的 JSON例如 themes/default.json

除此之外:

  • 背景图属于资源文件,不属于主题配置
  • Kindle 本地缓存属于运行时状态,不属于主题配置

4.4 Kindle 本地状态

Kindle 本地只需要保存少量状态:

/mnt/us/dashboard/local/theme.env
/mnt/us/dashboard/local/state/themes.json
/mnt/us/dashboard/local/state/current-theme.json
/mnt/us/dashboard/local/state/theme-runtime.env
/mnt/us/dashboard/local/state/background-updated-at

其中:

  • theme.env 记录当前 THEME_IDORIENTATION
  • themes.json 是最近一次同步到本地的主题清单缓存
  • current-theme.json 是当前主题配置缓存
  • theme-runtime.env 是 Kindle 实际消费的运行时快照,包含背景路径和时钟参数

例如:

export THEME_ID=default
export ORIENTATION=portrait

4.5 Kindle 本地主题资源

当前实现中,主题背景图会随部署一起同步到 Kindle

/mnt/us/dashboard/themes/default/portrait/kindlebg.png
/mnt/us/dashboard/themes/default/landscape/kindlebg.png
/mnt/us/dashboard/themes/paper/portrait/kindlebg.png
/mnt/us/dashboard/themes/paper/landscape/kindlebg.png
/mnt/us/dashboard/themes/classic/portrait/kindlebg.png
/mnt/us/dashboard/themes/classic/landscape/kindlebg.png

Kindle 拉背景时的规则是:

  1. 先读 theme-runtime.env 中的 BACKGROUND_PATH
  2. 如果本地对应文件存在,直接从 /mnt/us/dashboard/themes/... 拷贝
  3. 如果本地不存在,再回退到 BACKGROUND_URL

主题元数据同步的规则是:

  1. 先读本地 /mnt/us/dashboard/themes.json
  2. 再读本地 /mnt/us/dashboard/themes/<theme-id>.json
  3. 本地 bundle 不存在时,才回退到远端 URL

5. calendar/ 侧方案

5.1 网站访问时增加主题与方向预览菜单

预览菜单只出现在网站访问场景,不出现在背景截图结果里。

建议行为:

  1. 用户访问 /?mode=full 时,在页面顶部显示两个可选菜单
  2. 一个菜单切换 theme
  3. 一个菜单切换 orientation
  4. mode=background 下不显示这些菜单,避免影响截图导出

建议 URL 形态如下:

  • /?mode=full&theme=default&orientation=portrait
  • /?mode=full&theme=default&orientation=landscape
  • /?mode=full&theme=paper&orientation=portrait

5.2 预览效果与 Kindle 效果对齐

主题预览不能只是“网页版好看”,还需要尽量接近 Kindle 最终效果。

建议做到:

  • 预览和导出使用同一套主题配置
  • 预览和导出使用同一套方向布局配置
  • Kindle 侧按主题 JSON 中当前方向的参数绘制,避免预览和实机错位

这样评审主题时,看到的页面效果和 Kindle 最终效果才是一致的。

5.3 现有导出流程保持兼容

当前默认导出链路继续保留:

calendar/dist/kindlebg.png
calendar/dist/dashboard-manifest.json
calendar/dist/clock-region.json

兼容规则建议如下:

  • 如果导出脚本没有显式传入 theme,默认导出 default
  • 如果导出脚本没有显式传入 orientation,默认导出 portrait
  • 现有 export:background 的行为保持不变
  • 默认导出继续只产出 default + portrait

这样可以保证:

  • 网站预览菜单先落地
  • 当前生产链路不被打断
  • Kindle 当前单主题纵向使用方式继续可用

5.4 多主题多方向导出已作为附加能力落地

当前 export:themes 会生成如下产物:

calendar/dist/themes.json
calendar/dist/themes/default.json
calendar/dist/themes/default/portrait/kindlebg.png
calendar/dist/themes/default/landscape/kindlebg.png
calendar/dist/themes/paper.json
calendar/dist/themes/paper/portrait/kindlebg.png
calendar/dist/themes/paper/landscape/kindlebg.png
calendar/dist/themes/classic.json
calendar/dist/themes/classic/portrait/kindlebg.png
calendar/dist/themes/classic/landscape/kindlebg.png

这里的关键点是:

  • 原有 calendar/dist/kindlebg.png 继续保留
  • 多主题多方向目录和 themes.json 是新增能力,不是替换能力
  • 主题和方向的路径管理由 calendar 决定,不在 Kindle 侧固化为固定模式
  • 同步到 Kindle 时,会把整个 calendar/dist/themes/ 目录一起带上
  • landscape 的网页先按 1448x1072 渲染,再顺时针旋转 90 度,写成 1072x1448 的 Kindle 设备图
  • 因此 landscapekindlebg.png 是设备向产物,不是给桌面直接平铺预览的横图

6. Kindle 侧方案

6.1 主题发现方式

Kindle 只需要知道一个固定地址:

themes.json 的 URL

推荐两种同步方式并存:

  • 被动同步Kindle 每天拉取一次 themes.json
  • 主动同步:用户进入主题切换流程时,先即时拉取最新 themes.json
  • 如果设备上已经有本地同步的 themes.json 和主题 JSON本地 bundle 优先

这样能兼顾:

  • 平时低频更新
  • 切换主题时拿到最新主题清单

6.2 主题与方向切换流程

主题切换时建议执行以下流程:

  1. 拉取最新 themes.json
  2. 根据用户选择的主题 ID 找到对应 configUrl
  3. 拉取该主题对应的 <theme-id>.json
  4. 确定当前要使用的 orientation
  5. variants[orientation] 里取出背景和时钟配置
  6. THEME_IDORIENTATION 写入本地 theme.env
  7. 将主题 JSON 缓存在本地
  8. 优先读取本地 themes/<theme>/<orientation>/kindlebg.png
  9. 如果本地没有对应背景,再回退拉取 background.url
  10. 立即执行一次全屏刷新
  11. 之后继续按当前分钟级时钟刷新逻辑运行

这里有两个关键要求:

  • 主题或方向切换后,不等待下一个两小时周期
  • 而是立即拉取一次对应的新背景

6.3 时钟绘制保持现有模型

当前 Kindle 时钟是本地几何绘制,已经满足主题切换第一阶段需求。

因此 Kindle 侧仍然保持:

  • 拉背景图片
  • 读取主题 JSON
  • 读取当前方向的 variant
  • 用本地 lua + fbink 画时钟

不需要在第一阶段增加:

  • 表盘图片素材同步
  • 指针图片素材同步
  • 复杂的本地主题资源目录

如果未来某个主题确实需要独立表盘素材,再在第二阶段扩展。

6.4 切换入口

当前已经有一个可直接 SSH 调用的入口:

/mnt/us/dashboard/switch-theme.sh <theme-id> [orientation]

例如:

ssh kindle '/mnt/us/dashboard/switch-theme.sh default landscape'
ssh kindle '/mnt/us/dashboard/switch-theme.sh paper portrait'

如果本地刚改过页面样式,推荐先同步一次:

sh scripts/sync-layered-clock-to-kindle.sh kindle

KUAL 动态主题入口仍然是后续可选项,不是当前必需项。

如果后续要补 KUAL目标交互仍然可以保持

  1. 用户点击“Theme”
  2. 设备根据 themes.json 获取当前可用主题清单
  3. 用户选择具体主题
  4. 用户选择 portraitlandscape
  5. 立即切换到该主题该方向

不管具体 UI 形态如何,数据契约都应保持一致:

  • 主题列表来自 themes.json
  • 具体切换行为来自所选主题对应的 <theme-id>.json
  • 最终生效资源来自该主题该方向下的 variant

7. 分阶段落地与当前进度

第一阶段:网站主题与方向预览(已完成)

目标:

  • 新增 theme 参数
  • 新增 orientation 参数
  • 增加顶部预览菜单
  • 默认主题命名为 default
  • 新增 paperclassic
  • 每个主题都支持 portraitlandscape

结果:

  • 用户可以在网站上预览三套主题的两种方向
  • mode=background 不显示预览控件
  • 现有截图导出流程保持不变

第二阶段:主题元数据发布(已完成)

目标:

  • calendar/ 产出 themes.json
  • 每个主题产出自己的 <theme-id>.json
  • 每个主题在两个方向下都有独立背景图 URL

结果:

  • 主题列表、方向列表、时钟配置都由 calendar/ 统一提供
  • 主题配置里已同时提供 background.pathbackground.url

第三阶段Kindle 端主题与方向切换(已完成)

目标:

  • Kindle 能拉取 themes.json
  • Kindle 能按选择主题拉取对应的 <theme-id>.json
  • Kindle 能切换 portraitlandscape
  • 切换后立即拉取新背景并全屏刷新

结果:

  • Kindle 端已具备多主题多方向切换能力
  • 切换后会立即刷新
  • SSH 命令切换已实机验证通过

第四阶段:按需扩展主题能力(未开始)

只有在未来确实出现需求时,再考虑:

  • 为某些主题提供独立表盘素材
  • 增加主题级本地资源同步
  • 增加更复杂的切换交互

这一步不是当前必需项。

8. 风险与约束

8.1 预览和实机效果偏差

如果预览和导出用的不是同一套主题和方向配置,最终会出现:

  • 网页看起来正确
  • Kindle 上时钟错位或背景不一致

因此必须保证:

  • 预览
  • 导出
  • Kindle 时钟绘制

都依赖同一套主题元数据和方向配置。

8.2 方向切换后的缓存问题

如果主题或方向切换后没有立即拉取新背景Kindle 可能继续显示旧缓存。

因此切换时必须同时处理:

  • 更新 THEME_ID
  • 更新 ORIENTATION
  • 拉取新的 <theme-id>.json
  • 拉取或读取当前方向对应的新背景图
  • 重置背景更新时间戳或直接覆盖缓存背景

如果设备本地没有同步主题背景,而远端对应 PNG 也还没发布完成Kindle 可能拉到 HTML 或错误页而不是图片。

因此当前推荐做法是:

  • 同步到 Kindle 时总是一起同步 /mnt/us/dashboard/themes/
  • 设备优先使用本地主题背景
  • 远端 background.url 只作为回退

8.3 主题清单缓存过旧

如果 Kindle 长时间只读本地缓存,可能看不到新主题或新方向配置。

因此建议:

  • 日常低频同步一次 themes.json
  • 用户进入切换流程时再主动刷新一次

9. 当前推荐结论

推荐采用下面这条路线:

  1. 继续保留默认 kindlebg.png 导出链路,默认仍是 default + portrait
  2. 同时保留 export:themes 批量导出,作为多主题切换的资源来源
  3. calendar 继续维护 themes.json 和主题级 <theme-id>.json
  4. 每个主题配置里同时提供 portraitlandscape 两套 variant
  5. Kindle 继续读取固定位置的 themes.json
  6. 切换主题或方向时优先使用本地同步的主题背景,并继续使用现有本地时钟绘制

这条路线的优点是:

  • 不打断现有 kindlebg.png 生成流程
  • 主题和方向都由 calendar 统一管理
  • Kindle 侧实现最简化
  • 预览和实机显示可以基于同一套配置对齐
  • 即使远端主题图片还没发布完整SSH 主题切换也能工作
  • 当前时钟实现不依赖表盘和指针素材,第一阶段改造成本低