Update watch face: modular refactor + OWM/Solar/background + codegen tools
- Split render into FieldTable/Fields/Icons/Layout/Settings modules - Add Fenix8V3Background, Owm (weather), Solar modules - Add tools/ generators (gen_fields/gen_icons/gen_themes/gen_preview_svg) - Tune README/manifest/strings/settings
This commit is contained in:
@@ -1,20 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One table -> the field id list, both languages, and the settings XML.
|
||||
"""一张表 → 字段 id 表、两种语言的文案、以及设置项 XML。
|
||||
|
||||
Field ids follow the numbering at https://watchface.io/docs/datafields so the
|
||||
two catalogues line up, but only the fields Connect IQ can actually serve from
|
||||
the watch are here. Anything needing a web service (OpenWeatherMap,
|
||||
StormGlass), a companion phone integration (the week-to-date and 7/28-day
|
||||
per-sport aggregates), or another installed app (CGM, hydration, quotes) is
|
||||
deliberately absent -- see README section 8 for the full reasoning.
|
||||
字段编号沿用 https://watchface.io/docs/datafields,方便两边对照;但这里只收录
|
||||
Connect IQ 在手表本机能算出来的字段。
|
||||
|
||||
需要联网服务的(OpenWeatherMap 的部分、StormGlass)、需要手机侧与 Garmin
|
||||
Connect 集成的(按运动分类的周/月累计)、需要用户装了别的 App 的(CGM、
|
||||
补水、行情),都不在这里 —— 具体原因见 README 第 7 节。
|
||||
|
||||
python3 tools/gen_fields.py
|
||||
|
||||
Writes:
|
||||
source/FieldTable.mc ids + short label lookup
|
||||
resources/settings/settings.xml the four field pickers
|
||||
resources/strings/fields.xml English names and labels
|
||||
resources-chn/strings/fields.xml Chinese names and labels
|
||||
产出:
|
||||
source/FieldTable.mc 字段 id 表 + 下标反查
|
||||
resources/settings/settings.xml 四个数据位的选择器
|
||||
resources/strings/fields.xml 英文的字段名与表盘标签
|
||||
resources-chn/strings/fields.xml 中文的字段名与表盘标签
|
||||
|
||||
加字段的流程:在下面的 FIELDS 表里加一行 → 跑一遍本脚本 →
|
||||
到 source/Fields.mc 对应分类的函数里补一个分支。设置项、两种语言的文案、
|
||||
表盘上的标签都会自动跟上,不会漏。
|
||||
"""
|
||||
import os
|
||||
|
||||
@@ -40,6 +44,8 @@ FIELDS = [
|
||||
(868, "MonthNum", "Month (6)", "月份数字", "MON", "月"),
|
||||
(858, "Meridiem", "AM/PM", "上午/下午", "", ""),
|
||||
(859, "WeekIso", "Week Number (ISO)", "周数 (ISO)", "WEEK", "周"),
|
||||
(850, "Seconds", "Seconds", "秒", "SEC", "秒"),
|
||||
(869, "Seconds0", "Seconds (09)", "秒 (前导零)", "SEC", "秒"),
|
||||
(873, "WeekCal", "Week Number", "周数", "WEEK", "周"),
|
||||
|
||||
# ---- activity -------------------------------------------------------
|
||||
@@ -58,6 +64,15 @@ FIELDS = [
|
||||
(6, "MetersUp", "Distance Climbed", "爬升高度", "ASC", "爬升"),
|
||||
(9, "PushDist", "Push Distance", "推行距离", "PUSH", "推行"),
|
||||
(10, "Pushes", "Pushes", "推行次数", "PUSH", "推行"),
|
||||
(219, "StepsWeek", "Steps (Week to Date)", "本周步数", "WEEK", "周步数"),
|
||||
(220, "Steps7d", "Steps (Last 7 Days)", "近 7 天步数", "7D", "7天"),
|
||||
(211, "CalWeek", "Calories (Week to Date)", "本周卡路里", "WEEK", "周千卡"),
|
||||
(212, "Cal7d", "Calories (Last 7 Days)", "近 7 天卡路里", "7D", "7天"),
|
||||
(213, "DistWeek", "Distance (Week to Date)", "本周距离", "WEEK", "周距离"),
|
||||
(214, "Dist7d", "Distance (Last 7 Days)", "近 7 天距离", "7D", "7天"),
|
||||
(203, "FloorsWeek", "Floors (Week to Date)", "本周楼层", "WEEK", "周楼层"),
|
||||
(204, "Floors7d", "Floors (Last 7 Days)", "近 7 天楼层", "7D", "7天"),
|
||||
(200, "ActiveMin7d", "Active Minutes (7 Days)", "近 7 天活动分钟", "7D", "7天"),
|
||||
|
||||
# ---- body -----------------------------------------------------------
|
||||
(618, "HeartRate", "Heart Rate", "心率", "HR", "心率"),
|
||||
@@ -94,6 +109,16 @@ FIELDS = [
|
||||
(731, "UntilSun", "Time Until Sun Event", "距太阳事件", "SUN", "太阳"),
|
||||
(706, "MoonAge", "Moon Age", "月龄", "MOON", "月龄"),
|
||||
(732, "MoonLit", "Moon Illumination", "月球照明", "MOON", "月相"),
|
||||
(710, "CivilAm", "Civil Twilight Start", "民用晨光始", "CIVIL", "民用晨"),
|
||||
(709, "NauticalAm", "Nautical Twilight Start", "航海晨光始", "NAUT", "航海晨"),
|
||||
(708, "AstroAm", "Astronomical Twilight Start", "天文晨光始", "ASTR", "天文晨"),
|
||||
(711, "BlueAm", "Blue Hour (AM)", "蓝调时刻 (晨)", "BLUE", "蓝调晨"),
|
||||
(714, "GoldenAm", "Golden Hour (AM)", "黄金时刻 (晨)", "GOLD", "黄金晨"),
|
||||
(715, "GoldenPm", "Golden Hour (PM)", "黄金时刻 (昏)", "GOLD", "黄金昏"),
|
||||
(718, "BluePm", "Blue Hour (PM)", "蓝调时刻 (昏)", "BLUE", "蓝调昏"),
|
||||
(719, "CivilPm", "Civil Twilight End", "民用昏影终", "CIVIL", "民用昏"),
|
||||
(720, "NauticalPm", "Nautical Twilight End", "航海昏影终", "NAUT", "航海昏"),
|
||||
(721, "AstroPm", "Astronomical Twilight End", "天文昏影终", "ASTR", "天文昏"),
|
||||
|
||||
# ---- weather --------------------------------------------------------
|
||||
(306, "Temp", "Temperature", "气温", "TEMP", "气温"),
|
||||
@@ -107,6 +132,38 @@ FIELDS = [
|
||||
(307, "WindBearing", "Wind Bearing", "风向角", "WIND", "风向"),
|
||||
(310, "WindDir", "Wind Direction", "风向", "WIND", "风向"),
|
||||
(309, "WeatherTime", "Weather Update Time", "天气更新时间", "WX", "更新"),
|
||||
(312, "PressureSea", "Pressure (Sea Level)", "海平面气压", "hPa", "气压"),
|
||||
(313, "Uvi", "UV Index", "紫外线指数", "UVI", "紫外线"),
|
||||
(314, "Visibility", "Visibility", "能见度", "VIS", "能见度"),
|
||||
(315, "DewPoint", "Dew Point", "露点", "DEW", "露点"),
|
||||
|
||||
# ---- OpenWeatherMap -------------------------------------------------
|
||||
# Fetched by the background service; see source/Owm.mc.
|
||||
(500, "OwmTemp", "OWM Temperature", "OWM 气温", "TEMP", "气温"),
|
||||
(515, "OwmMinMax", "OWM Max/Min Temp", "OWM 最高/最低", "HI/LO", "高低温"),
|
||||
(503, "OwmFeels", "OWM Feels Like", "OWM 体感温度", "FEEL", "体感"),
|
||||
(504, "OwmMax", "OWM Temp Max", "OWM 最高温", "HIGH", "最高"),
|
||||
(505, "OwmMin", "OWM Temp Min", "OWM 最低温", "LOW", "最低"),
|
||||
(507, "OwmDescShort", "OWM Description", "OWM 天气", "WX", "天气"),
|
||||
(502, "OwmDescLong", "OWM Description (Long)", "OWM 天气详述", "WX", "天气"),
|
||||
(501, "OwmHumidity", "OWM Humidity", "OWM 湿度", "HUM", "湿度"),
|
||||
(506, "OwmPressure", "OWM Pressure", "OWM 气压", "hPa", "气压"),
|
||||
(509, "OwmVisibility", "OWM Visibility", "OWM 能见度", "VIS", "能见度"),
|
||||
(510, "OwmWind", "OWM Wind Speed", "OWM 风速", "WIND", "风速"),
|
||||
(511, "OwmWindDeg", "OWM Wind Degrees", "OWM 风向角", "WIND", "风向"),
|
||||
(518, "OwmWindDir", "OWM Wind Direction", "OWM 风向", "WIND", "风向"),
|
||||
(512, "OwmGust", "OWM Wind Gust", "OWM 阵风", "GUST", "阵风"),
|
||||
(513, "OwmRain", "OWM Rain 1h", "OWM 1h 降雨", "RAIN", "降雨"),
|
||||
(532, "OwmSnow", "OWM Snow 1h", "OWM 1h 降雪", "SNOW", "降雪"),
|
||||
(536, "OwmClouds", "OWM Cloud Cover", "OWM 云量", "CLD", "云量"),
|
||||
(537, "OwmPrecip", "OWM Precipitation %", "OWM 降水概率", "RAIN", "降水"),
|
||||
(530, "OwmDew", "OWM Dew Point", "OWM 露点", "DEW", "露点"),
|
||||
(534, "OwmTomorrow", "OWM Temp Tomorrow", "OWM 明日气温", "TMRW", "明日"),
|
||||
(522, "OwmMinMaxTmrw", "OWM Min/Max Tomorrow", "OWM 明日高低温", "TMRW", "明日"),
|
||||
(535, "OwmDayAfter", "OWM Temp Day After", "OWM 后日气温", "D+2", "后日"),
|
||||
(523, "OwmMinMaxD2", "OWM Min/Max Day After", "OWM 后日高低温", "D+2", "后日"),
|
||||
(516, "OwmCity", "OWM City Name", "OWM 城市", "CITY", "城市"),
|
||||
(517, "OwmUpdated", "OWM Update Time", "OWM 更新时间", "OWM", "更新"),
|
||||
|
||||
# ---- custom ---------------------------------------------------------
|
||||
(703, "Custom1", "Custom Text 1", "自定义文字 1", "", ""),
|
||||
@@ -141,6 +198,10 @@ OTHER_SETTINGS = """
|
||||
<settingConfig type="numeric" min="-720" max="840"/>
|
||||
</setting>
|
||||
|
||||
<setting propertyKey="@Properties.OwmKey" title="@Strings.SettingOwmKey">
|
||||
<settingConfig type="alphaNumeric" maxLength="40"/>
|
||||
</setting>
|
||||
|
||||
<setting propertyKey="@Properties.Custom1" title="@Strings.SettingCustom1">
|
||||
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||||
</setting>
|
||||
@@ -184,11 +245,15 @@ def main():
|
||||
assert len(ids) == len(set(ids)), "duplicate field id"
|
||||
|
||||
# ---- FieldTable.mc ----
|
||||
L = ["// AUTO-GENERATED by tools/gen_fields.py -- do not edit by hand.",
|
||||
"// Ids follow https://watchface.io/docs/datafields; only the fields a",
|
||||
"// Connect IQ watch face can source on-device are listed.",
|
||||
L = ["// ⚠️ 本文件由 tools/gen_fields.py 自动生成,请勿手改。",
|
||||
"// 要加字段:在生成器的 FIELDS 表里加一行,跑一遍生成器,",
|
||||
"// 再到 source/Fields.mc 对应分类的函数里补一个分支。",
|
||||
"//",
|
||||
"// 字段编号沿用 https://watchface.io/docs/datafields,只收录 Connect IQ",
|
||||
"// 表盘在本机能算出来的那些。",
|
||||
"import Toybox.Lang;", "", "module FieldTable {", "",
|
||||
" // Field ids, in the same order as the Labels resource string.",
|
||||
" // 全部字段 id,顺序与 FieldLabels 资源串(逗号分隔的标签表)严格一致 ——",
|
||||
" // Fields.label() 就是靠这个下标去取对应标签的。",
|
||||
" const IDS = ["]
|
||||
row = " "
|
||||
for i, fid in enumerate(ids):
|
||||
@@ -198,6 +263,7 @@ def main():
|
||||
row = " "
|
||||
row += piece
|
||||
L += [row, " ];", "",
|
||||
" // 由字段 id 反查它在 IDS 里的下标;找不到返回 0(对应「关闭」)。",
|
||||
" function indexOf(id as Number) as Number {",
|
||||
" for (var i = 0; i < IDS.size(); i++) {",
|
||||
" if (IDS[i] == id) { return i; }",
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rasterise the design's icon paths into tintable bitmap resources.
|
||||
"""把设计稿的图标路径光栅化成可染色的位图资源。
|
||||
|
||||
The sunrise and weather icons in design/*.svg are traced outlines whose finest
|
||||
features are two or three design pixels across. Redrawing them from circles
|
||||
and lines works at 454 px and falls apart at 280 px, where those features land
|
||||
under one device pixel. Rendering the real paths once per screen size and
|
||||
blitting them with drawBitmap2(:tintColor) keeps them exact everywhere.
|
||||
design/*.svg 里的日出和天气图标是描边出来的轮廓,最细的特征只有 2–3 个设计
|
||||
像素宽。用圆和线段去重画它们在 454px 上还行,到 280px 就彻底糊掉 —— 那个尺寸
|
||||
下这些细节不足一个物理像素。
|
||||
|
||||
所以改成:把真实路径按每种屏宽各渲染一次,运行时用
|
||||
drawBitmap2(:tintColor) 染上主题色。这样每台设备拿到的都是为它的像素网格
|
||||
专门渲染的图。
|
||||
|
||||
python3 tools/gen_icons.py
|
||||
|
||||
Writes resources-icons-<W>/drawables/{sunrise,sunset,weather}.png plus the
|
||||
matching drawables.xml. White pixels, alpha coverage; the theme's accent
|
||||
colour is applied at draw time via :tintColor.
|
||||
产出 resources-icons-<屏宽>/drawables/{sunrise,sunset,weather}.png 以及配套的
|
||||
drawables.xml;另外产出 resources-launcher-<尺寸>/ 下各机型要求尺寸的启动图标。
|
||||
|
||||
MIP devices have no alpha blending, so for those widths the coverage mask is
|
||||
thresholded to hard edges rather than left antialiased -- a soft edge there
|
||||
would be quantised to something muddier than a clean one.
|
||||
⚠️ 两个要点:
|
||||
1. drawables.xml 里必须写 packingFormat="png"。否则资源编译器会把图片压成
|
||||
调色板格式,而 :tintColor 拒绝调色板化的源,运行时直接抛异常。
|
||||
2. MIP 屏没有 alpha 混合,所以那几个尺寸的覆盖率蒙版会被阈值化成硬边 ——
|
||||
软边在 64 色调色板下反而更脏。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render preview-themes.svg and preview.html by replaying the face's draw calls.
|
||||
"""重放表盘自己的绘制逻辑,产出 preview-themes.svg 和 preview.html。
|
||||
|
||||
Colours are parsed straight out of source/Themes.mc and the geometry constants
|
||||
are the same literals as source/Fenix8V3View.mc, so the preview cannot drift
|
||||
from the firmware. Text is drawn with a web font rather than Garmin's Roboto
|
||||
Condensed / Bionic, so judge geometry and colour here, not glyph shapes.
|
||||
颜色直接解析自 source/Themes.mc,几何常量与 source/Fenix8V3View.mc /
|
||||
source/Layout.mc 保持同一套字面值,所以预览不会和固件漂移。
|
||||
|
||||
⚠️ 文字用的是网页字体,真机是 Garmin 内置的 Roboto / Bionic,字形会有差异。
|
||||
**几何和配色可信,字形不可信** —— 判断字形要看模拟器截图。
|
||||
|
||||
python3 tools/gen_preview_svg.py
|
||||
"""
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate source/Themes.mc from the design kit in design/.
|
||||
"""从 design/ 的设计稿生成 source/Themes.mc。
|
||||
|
||||
Ground truth is the seven watchface-NN-*.svg files (produced by design/rebuild.py
|
||||
from the original raster comps). Every colour written to Themes.mc is measured
|
||||
from those files -- nothing here is hand-picked.
|
||||
事实来源是七个 watchface-NN-*.svg(原作者用 design/rebuild.py 从位图逆向出来的)。
|
||||
写进 Themes.mc 的每一个颜色都是从这些文件里**量出来的**,没有一个是手挑的。
|
||||
|
||||
python3 tools/gen_themes.py
|
||||
|
||||
Extracted per theme:
|
||||
每套主题提取出:
|
||||
|
||||
ringTop[21] lit colour of each top tick, index 0 = 6.5 deg .. 20 = 86.5 deg
|
||||
ringBottom[21] lit colour of each bottom tick, index 0 = 6.0 deg .. 20 = 66.0 deg
|
||||
tickOff colour of an unlit tick
|
||||
anchorDot the four dots between the arcs
|
||||
bandFill/Text date band background / foreground
|
||||
colon the two colon blocks
|
||||
hours[11] vertical gradient ramp of the hour digits, top -> bottom
|
||||
minutes[11] ditto for the minute digits
|
||||
accent icons and DST/STEP labels
|
||||
textPrimary complication and bottom-row values
|
||||
ringTop[21] 顶弧每根刻度点亮时的颜色,下标 0 = 6.5°,20 = 86.5°
|
||||
ringBottom[21] 底弧每根刻度点亮时的颜色,下标 0 = 6.0°,20 = 66.0°
|
||||
tickOff 未点亮刻度的颜色
|
||||
anchorDot 四段弧之间的锚点圆点
|
||||
bandFill/Text 日期带的底色 / 文字色
|
||||
colon 两块冒号
|
||||
hours[11] 小时数字的竖向渐变,从上到下
|
||||
minutes[11] 分钟数字的竖向渐变
|
||||
accent 图标与 DST/STEP 标签
|
||||
textPrimary 各数据位与底部行的数值
|
||||
|
||||
Only 14 of the 21 bottom ticks are lit in any comp, so indices 14..20 of
|
||||
ringBottom are extrapolated (least-squares fit over the last 8 measured ticks).
|
||||
Those ticks only appear once a metric passes ~67%.
|
||||
⚠️ 已知近似:设计稿里底弧最多只点亮 14/21 根,所以 ringBottom 的第 15–21 项
|
||||
无法实测,由最后 8 根做最小二乘线性外推得到。只有当指标超过约 67% 时才会
|
||||
显示这几根。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
@@ -160,11 +159,20 @@ def fmt_list(colours, indent):
|
||||
def main():
|
||||
themes = [read_theme(t) for t in ORDER]
|
||||
L = []
|
||||
L.append("// AUTO-GENERATED by tools/gen_themes.py from design/watchface-*.svg.")
|
||||
L.append("// Do not edit by hand -- change the design kit and re-run the generator.")
|
||||
L.append("// ⚠️ 本文件由 tools/gen_themes.py 从 design/watchface-*.svg 自动生成,请勿手改。")
|
||||
L.append("// 要改配色就改设计稿里的 SVG,然后重新跑一遍生成器。")
|
||||
L.append("//")
|
||||
L.append("// ringTop/ringBottom lit colour per tick, index 0 nearest 12 / 6 o'clock")
|
||||
L.append("// hours/minutes 11-stop vertical gradient of the time digits, top -> bottom")
|
||||
L.append("// 七套主题的全部颜色数据。各数组的下标就是主题序号(0=Ember … 6=Kelp)。")
|
||||
L.append("//")
|
||||
L.append("// RINGTOP/RINGBOTTOM 每根刻度点亮时的颜色,各 21 项。")
|
||||
L.append("// 下标 0 是最靠近 12 点 / 6 点的那根。")
|
||||
L.append("// 这是从设计稿里**逐根取样**的实测值,不是插值近似。")
|
||||
L.append("// HOURS/MINUTES 时间数字的竖向渐变,各 11 个停止点,从上到下。")
|
||||
L.append("// TICKOFF 未点亮刻度的颜色")
|
||||
L.append("// ANCHORDOT 四个锚点圆点")
|
||||
L.append("// BANDFILL/BANDTEXT 日期带的底色 / 文字色")
|
||||
L.append("// ACCENT 图标与底部标签")
|
||||
L.append("// TEXTPRIMARY 各项数值")
|
||||
L.append("import Toybox.Lang;")
|
||||
L.append("")
|
||||
L.append("module Themes {")
|
||||
|
||||
Reference in New Issue
Block a user