主题包制作
.fsbt 是 ZIP 压缩包。根目录包含 manifest.toml(UTF-8 编码的 TOML)、可选的
background.png / background.jpg / background.jpeg、可选的 icons/ 文件,
以及可选的 splash_logo.png / .jpg / .jpeg / .svg。format 版本 1 包含 UI
颜色、应用内 icon、背景、组件圆角和 splash 屏。启动器 icon 和字体单独管理,主题包
不接受这两项。
主题包格式可参考
Aurora。
开发时可在桌面端选择 Install theme → Folder,导入主题源目录进行预览。应用会校验
并复制文件;修改后重新选择该目录即可重新加载。分发时,将目录内容打成 ZIP,确保
manifest.toml 位于压缩包根目录,再将扩展名设为 .fsbt。
内置主题的打包、加载与分发方式见主题实现说明;面向 普通用户的主题选择与安装说明见主题使用指南。
id = "example.amethyst"name = "Amethyst"modes = ["dark"]
# SVG icon、icon 颜色和 splash 都是 schema 2 的功能,所以这个包能被读取的# 最低版本是 2。见 "Schema versions"。[schema]min = 2max = 2
[colors]mode = 2seed = 0xFF880E4F
[colors.palette.dark]primary = 0xFFC87FD0surface = 0xFF141014
[icons.images]"tab.server" = "icons/tab_server.svg""nav.settings" = "icons/nav_settings.png"
# 每个 icon 的颜色:ARGB 整数或 palette role 名。[icons.colors]"tab.server" = "primary""nav.settings" = 0xFFC87FD0
[background]type = "image"image = "background.png"blur = 8
[splash]color = "surface"logo = "splash_logo.svg"duration = 900必填项只有 id、name、modes 和 schema 表。省略的字段取下表默认值;整张表
都可以省略。只写部分字段的表只覆盖写出的字段。显式的 0 会被保留;非法值会被拒绝,
不会被默认值替换。
| 字段 | 默认值 |
|---|---|
format |
1 |
colors.mode |
0(跟随系统) |
colors.seed |
0xFF880E4F |
colors.systemColor |
false |
colors.palette.light、colors.palette.dark |
空 |
icons.style |
"classic" |
icons.images |
空 |
icons.colors |
空(跟随 ambient icon color) |
splash |
不存在(无 splash) |
background.type |
"none" |
background.opacity |
0.18 |
background.blur |
0 |
background.tile |
不存在(图片只绘制一次,cover 铺满) |
shapes.card、shapes.tile、shapes.button |
12、8、10 |
components |
空(沿用应用样式) |
Default 主题是 Dart const,继续使用自身的圆角值(13、9、30)。上表中的默认值
适用于从文件夹导入的主题和 .fsbt 主题。
modes 必须声明 ["light"]、["dark"] 或 ["light", "dark"]。空列表、重复项和
未知模式都会被拒绝。只支持一种模式时会锁定应用的 ThemeMode,系统外观变化时也不变;
设置项会提示用户:要切换模式,需要换用支持另一模式的主题。支持两种模式时,System、
Light 和 Dark 均可选。
colors.mode 是初始偏好:0(跟随系统)、1(light)、2(dark)。只支持一种模式时
该偏好被覆盖。AMOLED 是内置主题,两种模式都支持:light 用生成色,dark 用纯黑表面。
旧的 AMOLED 设置迁移为 Dark + AMOLED,Auto AMOLED 迁移为 System + AMOLED;AMOLED
不再是 ThemeMode 的选项。
format 表示 ZIP 中 manifest 结构的版本。schema 表示该包支持的 theme UI schema
版本范围,包含首尾。应用目前支持 schema v1–v3,并在 Install theme 的帮助文字
中显示该范围。安装器要求包与应用支持的范围有交集;加载已安装主题时也会再次检查。
schema.min
和 schema.max 都是必填。
seed 和 palette 颜色是 ARGB 整数,支持十六进制 TOML 写法如 0xFF61AFEF。
icons.style 取 classic 或 mingcute,用于该包没有提供 image 的 icon。
background.type 取 none、gradient 或 image;type 不是 image 时不要写
image。不透明度 0–0.6,模糊 0–30,各圆角 0–40。
background.tile(schema 3,仅用于 image,16–1024)把图片作为图案重复绘制,每个重复
单元宽为该数值的逻辑像素,从左上角开始,而不是把一张图拉伸铺满窗口。请绘制可无缝拼接、
背景透明的 tile,surface 颜色会从透明处露出。
image 路径必须使用上文列出的固定文件名。安装器会拒绝其他归档条目、重复路径、符号
链接、加密文件和路径穿越。未知的顶层表也会被拒绝,因此拼错的 section 会明确报错,
不会被静默忽略。
编辑器用的 manifest schema
Section titled “编辑器用的 manifest schema”manifest 可通过 JSON Schema 校验,编辑时即可发现未知表或字段、超出范围的值、无效的 icon key,以及文件名与 key 不匹配的 icon:
https://raw.githubusercontent.com/lollipopkit/flutter_server_box/main/docs/schemas/fsbt-manifest.schema.json支持 Taplo schema directive 的 TOML 编辑器(Taplo、VS Code 的 Even Better TOML、 Tombi)会从文件第一行读取 schema。示例 也使用了这种写法:
#:schema https://raw.githubusercontent.com/lollipopkit/flutter_server_box/main/docs/schemas/fsbt-manifest.schema.json通过文件名关联 schema 的编辑器,则需在各自配置中填写同一个 URL。只有该 directive 可 跨编辑器通用。
schema 与安装器同样严格,只有一条规则无法表达:icons.colors 中的 key 必须在
icons.images 中有对应条目,因为这项检查涉及两张表。schema 报出的其他问题也都会被
安装器拒绝。schema 始终描述最新版本,因此声明 min = 1 的包也能通过校验。schema
的定义依据及校验方式见主题。
Schema 版本
Section titled “Schema 版本”schema.min 表示能够读取该包的最旧应用 schema 版本,安装时会据此检查兼容性。应用
不会迁移不兼容的包,而是直接拒绝:支持范围与应用不相交的包无法安装;已安装的包若
之后变得不兼容,也不会加载(界面会标记为无法读取,且保留其设置)。
Schema 2 增加了 SVG icon、icons.colors 和 splash 表。使用其中任一功能的包都
必须声明 min = 2。否则仅支持 schema 1 的构建仍会安装这些文件,却会静默丢弃相关功能:
SVG icon 会退回内置字形,指定颜色和 splash 也不会生效。
Schema 3 增加了 schema 2 七个组件之外的组件(textButton、outlinedButton、
iconButton、search、appBar、segmented、sidebar、menu、tooltip、
toast、switch、slider、progress、badge、chip、divider、scrollbar)、
按钮表的 minHeight 字段、background.tile,以及 [layout] 和 [variants] 表。使用其中任一项的包必须声明
min = 3:schema 2 的构建会拒绝未知的表,商店需要在下载前得知这一点。当前上限为
max = 3;即使包的范围高于该值,只要与应用支持的范围有交集,仍可安装。
icons.images 把 icon key 映射到 icons/ 里承载它的文件。文件名必须是 key 里的点
换成下划线,扩展名两种都可:
[icons.images]"tab.server" = "icons/tab_server.svg""nav.settings" = "icons/nav_settings.png"PNG 最大为 512 × 512 像素、256 KiB。SVG 不含光栅尺寸,因此改为校验文档结构:必须是
UTF-8,根元素必须是 svg,最大 256 KiB。DTD、实体声明、<script>、<style>、
<foreignObject>,以及任何引用外部文件的写法(非本文档片段的 href 或 url(…))
都会被拒绝。icon 缺失或校验失败时,会显示该 key 对应的内置字形。
两种格式的 icon 都只使用单一颜色。需要随颜色变化的图形部分应使用 currentColor。
未设置 icons.colors 时,icon 使用 ambient icon color,与主题包支持自定义颜色之前的行为
一致。设置该表后,每项可为一个 icon 指定颜色:
[icons.colors]"tab.server" = "primary" # 任意 palette role,按 brightness 解析"nav.settings" = 0xFFC87FD0 # 或 ARGB 整数如果为包中没有对应 image 的 key 指定颜色,安装器会拒绝该包,避免无效配置被静默接受。
colors.palette.light 和 .dark 接受下面全部 46 个未废弃的 ColorScheme role。
未指定的 role 取自 seed。未知和已废弃的 role 会被拒绝。palette 的值是 ARGB 整数。
primary, onPrimary, primaryContainer, onPrimaryContainerprimaryFixed, primaryFixedDim, onPrimaryFixed, onPrimaryFixedVariantsecondary, onSecondary, secondaryContainer, onSecondaryContainersecondaryFixed, secondaryFixedDim, onSecondaryFixed, onSecondaryFixedVarianttertiary, onTertiary, tertiaryContainer, onTertiaryContainertertiaryFixed, tertiaryFixedDim, onTertiaryFixed, onTertiaryFixedVarianterror, onError, errorContainer, onErrorContainersurface, onSurface, surfaceDim, surfaceBrightsurfaceContainerLowest, surfaceContainerLow, surfaceContainer, surfaceContainerHighsurfaceContainerHighest, onSurfaceVariant, outline, outlineVariantshadow, scrim, inverseSurface, onInverseSurfaceinversePrimary, surfaceTint公共值写在 [components.card],按 brightness 区分的覆盖写在
[components.light.card] 或 [components.dark.card]。所有表和字段都是可选的。
缺失的字段保留应用原有样式;显式的 0 和 false 会被保留。组件的 radius 优先于
[shapes]。颜色可以是 ARGB 整数或任意 ColorScheme role 名(例如
backgroundColor = "surfaceContainer"),按最终 palette 解析。未知的组件名、字段、
状态和 role 名都会被拒绝。
| 组件 | 支持的字段 |
|---|---|
card |
backgroundColor, radius, borderColor, borderWidth, elevation, shadowColor, surfaceTintColor, margin |
tile |
backgroundColor, selectedTileColor, textColor, iconColor, selectedColor, radius, borderColor, borderWidth, padding |
button |
backgroundColor, foregroundColor, overlayColor, radius, borderColor, borderWidth, elevation, shadowColor, surfaceTintColor, padding, minHeight³ |
input |
filled, fillColor, radius, borderColor, borderWidth, focusedBorderColor, errorBorderColor, disabledBorderColor, padding |
navigation |
backgroundColor, indicatorColor, indicatorRadius, selectedIconColor, unselectedIconColor, selectedLabelColor, unselectedLabelColor, elevation |
dialog |
backgroundColor, radius, borderColor, borderWidth, elevation, shadowColor, surfaceTintColor, barrierColor, insetPadding |
sheet |
backgroundColor, radius, borderColor, borderWidth, elevation, shadowColor, surfaceTintColor, barrierColor, dragHandleColor |
textButton³, outlinedButton³ |
与 button 相同的字段 |
iconButton³ |
button 的字段,加 iconSize |
search³ |
backgroundColor, radius, borderColor, borderWidth, elevation, iconColor, textColor, hintColor, height, padding |
appBar³ |
backgroundColor, foregroundColor, titleColor, iconColor, elevation, shadowColor, surfaceTintColor |
segmented³ |
backgroundColor, selectedColor, textColor, selectedTextColor, radius, borderColor, borderWidth |
sidebar³ |
backgroundColor, selectedColor, textColor, selectedTextColor, iconColor, selectedIconColor, radius, padding |
menu³ |
backgroundColor, textColor, radius, borderColor, borderWidth, elevation, shadowColor, surfaceTintColor |
tooltip³ |
backgroundColor, textColor, radius, borderColor, borderWidth, padding |
toast³ |
backgroundColor, textColor, radius, borderColor, borderWidth, elevation |
switch³ |
thumbColor, trackColor, trackOutlineColor, selectedThumbColor, selectedTrackColor, selectedTrackOutlineColor |
slider³ |
activeTrackColor, inactiveTrackColor, thumbColor, overlayColor, trackHeight |
progress³ |
color, trackColor, thickness, radius |
badge³ |
backgroundColor, textColor, smallSize, largeSize |
chip³ |
backgroundColor, selectedColor, textColor, radius, borderColor, borderWidth, padding |
divider³ |
color, thickness |
scrollbar³ |
thumbColor, trackColor, radius, thickness |
³ Schema 3。
圆角范围为 0–40,边框宽度为 0–8,elevation 为 0–24,尺寸(minHeight、height、
iconSize、badge 尺寸)为 0–96,粗细(thickness、trackHeight)为 0–16。内边距
(padding、margin、insetPadding)是四个数 [left, top, right, bottom],每个
0–64。数值必须有限。filled 是布尔值。字体仍是单独的设置。
button 只作用于主按钮:Material 的 elevated、filled 按钮和 Btn.elevated。文本按钮
(含 Btn.text)、outlined 按钮和 icon 按钮保留应用自身的样式,除非 textButton、
outlinedButton 或 iconButton 另有设置。这样填充色的主按钮不会让每个文本操作都变成
填充按钮。
search 作用于自带胶囊外框的搜索框:设置页搜索、服务器切换器和列表栏中的搜索。胶囊内
的文本框不会套用 input 的边框和填充。segmented 作用于 segmented 按钮和应用的
segmented tab;sidebar 作用于侧栏和设置菜单的行;toast 作用于 toast 和 snack bar;
menu 作用于弹出菜单和右键菜单;divider 作用于分隔线和 hairline;badge 也作用于
导航栏的计数 badge。
状态表([components.button.hovered]、.pressed、.focused、.selected、
.disabled)存在于 button、textButton、outlinedButton 和 iconButton,接受与
各自基础表相同的字段。状态表会在公共配置和 brightness 配置之间
合并。每个属性的优先级是 disabled > pressed > hovered > focused > selected > 基础值。
未指定的属性沿用既有的按钮主题和 Flutter 默认值。
这些样式适用于 Material 组件。自定义 NavigationRail 也使用 navigation 的颜色和
指示器形状;CardX 使用 card 的形状和 elevation;Input 使用 input 的边框和内边距;
SideBarTile 先使用 sidebar,再使用 tile 的颜色和边框,同时保留它的紧凑间距。Btn.elevated 使用 button
样式;紧凑的 Btn 行/列保留自身布局并使用主题圆角。单个 Widget 的显式覆盖仍然优先。
主题预览在内存中使用同一套设置;关闭选择器会恢复原样。
Schema 3。[layout] 存放不属于某个组件的全局设置:
[layout]# Material 控件的间距:"compact"、"standard" 或 "comfortable"。# 省略时使用平台默认值。density = "compact"Splash 屏
Section titled “Splash 屏”应用启动并绘制首帧时,splash 会显示一层背景色和可选 logo,随后淡出。省略该表即可
禁用 splash,避免每个主题包都默认显示启动画面。
[splash]color = "surface" # 任意 palette role,或 ARGB 整数logo = "splash_logo.svg" # 可选,只能是压缩包根目录下的固定文件名duration = 900 # 毫秒,100–3000,默认 600logo 是压缩包根目录下名为 splash_logo.png、splash_logo.jpg、splash_logo.jpeg
或 splash_logo.svg 的单个文件,此处只能写文件名。PNG 或 JPEG 最大 2048 × 2048
像素、512 KiB;SVG 最大 512 KiB,并按 icon 同样的文档规则校验。它以 96 逻辑像素
居中绘制,保留自身颜色:与 icon 不同,logo 不会被染色。SVG 中的 currentColor 取调色板的
onSurface,因此 logo 可以同时使用固定颜色和随主题变化的颜色。
duration 表示 splash 淡出前的显示时长,会延后应用启动,因此上限设得较低。该值只在
应用绘制首帧时读取一次:之后切换主题不会再次显示 splash,启动过程中选中的主题包也
不会显示自己的 splash。
主题只能控制应用启动后的这一层。Dart 启动前由操作系统绘制的内容(Android 的 window background 或 iOS 的 launch storyboard)在构建应用时确定,只能随系统 brightness 变化。
Image key 与上限
Section titled “Image key 与上限”支持的 icon key 是 tab.<tab> 和 tab.<tab>.selected,其中 <tab> 取 server、
ssh、file、snippet、agent、benchmark、remoteDesktop、virt。navigation key 有
nav.more、nav.settings、nav.tune、nav.privacy、nav.agent、nav.tabs、
nav.server、nav.sort、nav.terminal、nav.folder、nav.cloud、nav.snippet、
nav.inbox、nav.key、nav.info、nav.download、nav.desktop。
以上是 ServerBox 会绘制的 key。这一格式与基于同一个库的其他 app 共用,为其他 app
准备的 key(点分隔的小写单词,如 tab.chat)会被接受并保留,但 ServerBox 不绘制。
格式不合法的 key 会被拒绝。编辑器用的 schema 只列出 ServerBox 的 key,所以编辑时
拼错的 key 仍会被标出。
压缩包和解压后总内容各限制 16 MiB。manifest.toml 限制 64 KiB。icon 限制
256 KiB,splash logo 512 KiB,背景 8 MiB、单边 8192 像素、总计 6400 万像素。安装器
写入隔离的内容寻址安装目录前,安装器会检查每张图片:解码 PNG/JPEG,并按文档结构
解析 SVG。
Schema 3。一个主题包可以包含多个主题,例如 Pride 的 Trans、Nonbinary 和 Rainbow。
整个包只安装、更新一次,只有一个版本号。主题方案选择器把每个变体作为独立主题列出
(Pride · Trans),商店和官网只显示一次这个包,用分段按钮切换变体。
[variants] 之外的内容由所有变体共享。每个 [variants.<key>] 表包含 name,以及
colors、icons、background、splash、shapes、components、layout 中的
任意几项,覆盖在共享表之上:表按键合并,其他值直接替换共享值。key 只能用小写字母、
数字、- 和 _,最多 8 个。
[background]type = "image"image = "background.png"opacity = 0.12
[variants.trans]name = "Trans"
[variants.trans.colors.palette.light]primary = 0xFF1F74A8
[variants.trans.background]tile = 120variants/<key>/ 中的文件会替换该变体下包里同名的文件:variants/trans/background.png
就是 Trans 变体的 background.png,没有提供时使用包自己的文件。只有背景和 splash logo
可以这样替换。icon 文件由所有变体共享,因此变体可以设置 icons.style 和
icons.colors,但不能设置 icons.images。安装器会把每个变体当作完整主题检查,并拒绝
没有任何变体使用的文件。新安装时应用第一个变体。
商店分两层读取。第一层是 catalog,即逐条列出 repository 的 TOML 文件,不包含 具体主题或版本。第二层是 repository,即存放主题 TOML 文件的 git 仓库,每个主题 对应一个文件。
在 Settings → Appearance → Theme store 可打开主题商店页面。页面将 catalog 中的 主题与本机已安装的主题合并为一个列表,同一主题只显示一项。点按未安装的主题可安装并 应用;点按已安装的主题可应用。每项右侧的按钮会根据状态提供安装或移除操作。移除当前 主题后,应用会切回默认主题。
主题列表会保留到下次启动,因此页面打开时可立即显示上次读取的内容。标题栏下方会注明 列表来自哪些 repository,以及读取时间。点按该行旁边的刷新按钮可重新读取 catalog。 当前版本无法读取的主题仍会列出并标注状态,方便确认该主题是否存在。
schema = 1name = "ServerBox themes"
[[repo]]url = "https://serverbox.lollipopkit.com/store.tar.gz"repository URL 必须使用 HTTPS,可以指向 git 仓库或 tarball。对于 git 仓库,应用从
<address>/archive/HEAD.tar.gz 获取内容,以使用仓库的默认分支而不假设分支名称。
随应用提供的 catalog 列出官方 repository,即本仓库的
store/
目录,由官网以 tarball 形式提供。发布官方主题的方法见
主题。
主题 repository
Section titled “主题 repository”repo.toml 声明该 repository 及其 schema;themes/ 下一个主题一个文件,文件名取
主题的 id,路径与 id 不一致的文件会被拒绝。
schema = 1name = "Somebody's themes"id = "amethyst"name = "Amethyst"description = "A purple palette"homepage = "https://example.org/amethyst"license = "MIT"
[[version]]version = "1.2.0"schema_min = 1schema_max = 1url = "https://github.com/example/themes/releases/download/amethyst-1.2.0/amethyst.fsbt"sha256 = "0000000000000000000000000000000000000000000000000000000000000000"size = 40960
[[version]]version = "1.1.0"schema_min = 1schema_max = 1path = "packages/amethyst-1.1.0.fsbt"sha256 = "1111111111111111111111111111111111111111111111111111111111111111"description 可以是字符串,在所有语言下原样显示;也可以是以语言标签为键的表。商店
按应用语言查找:先找完整标签(zh-TW),再找只含语言的标签(zh),然后是 en,
最后取第一项。标签中的 - 和 _ 等价。
[description]en = "A purple palette"zh = "紫色调色板"zh-TW = "紫色調色盤"每个版本必须提供 url(指向该版本的 release)或 path(指向 repository 树中的
.fsbt 文件)。商店安装时必须校验 sha256;size 用于在安装前显示下载大小。
schema_min 和 schema_max 表示主题包支持的 manifest schema 范围。这样一个 repository
可以同时服务不同版本的应用:应用会安装自己支持的最新主题版本,不一定是列表中的
最新版本。若主题只有当前应用无法读取的版本,仍会显示并注明原因。使用 schema 2 功能的
版本必须设置 schema_min = 2;若误设为 1,旧版应用可能安装后静默丢弃 icon、颜色或
splash。使用 schema 3 组件或 [layout] 的版本必须设置 schema_min = 3。
第三方主题在作者自己的仓库发布,只有官方主题在本仓库发布。serverbox-theme skill 的
scripts/publish.py 按官方主题的方式发布:检测哪些主题有变化,把包上传到作者仓库的
一个 pre-release(永远不会成为 Latest),并向 listing 追加版本。主题包也可以放在任何
通过 HTTPS 提供下载的位置。listing
中的 sha256 把地址和内容对应起来:没有 sha256 的版本,商店不会安装;digest 不一致
的内容,同样不会安装。若要将 repository 加入应用 catalog,请向本仓库提交包含新增
[[repo]] 条目的 pull request。
repository 可在 themes/ 旁边包含 plugins/ 目录。本构建只读取主题,因此会跳过该
目录,不会因此拒绝整个 repository。
直接 URL 安装接受 HTTPS 的 .fsbt 链接,无需商店。安装器最多跟随三次 HTTPS
重定向,不发送凭据。catalog 和 repository 树各自的大小上限见
主题。
主题商店中的官方主题 One Dark Pro,把下面这个 VS Code 主题的 UI 调色板改编到 Material 的表面、选中态、卡片和按钮上。这一改编与 VS Code 扩展包无关。字体和 终端/编辑器配色仍是单独的设置。AMOLED 是 ServerBox 原创的调色板,在 dark 下使用 纯黑表面、在 light 下使用生成色,因此支持 System 外观。
必要时会调整次要文字颜色,使正常文字的对比度不低于 4.5:1。
上游署名
One Dark Pro
Source: One Dark Pro
The MIT License (MIT)
Copyright (c) 2013-2022 Binaryify
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.