字段 125 → 156 项。
Toybox.Complications(source/Comp.mc)
这条路推翻了我之前几个「表盘做不到」的判断 —— 当时只查了 ActivityMonitor /
UserProfile / Weather,漏了系统把这些作为 complication 发布出来:
- 下一个日程(CIQ 确实没有日历 API,但有 CALENDAR_EVENTS complication)
- 训练状态、完赛预测 ×4、配速预测 ×4、最近高尔夫得分
- 本周跑步 / 骑行距离(按运动分类的,按天历史做不出来)
- 第三方 App 1–5(CGM、补水、行情这类 App 发布的)
需要 ComplicationSubscriber 权限。
其余补齐
- 近 28 天累计:步数 / 卡路里 / 距离 / 楼层
- 事件倒计时:设置里填 YYYY-MM-DD,显示还剩几天
- 自定义时间格式、日期格式 1/2:单字母占位符(h/H/m/s/a/D/M/N/Y/y/w)
- OWM 空气质量:AQI 等级 + 描述 + PM2.5 + PM10 + CO,走
/data/2.5/air_pollution —— 那个接口在免费档就开放,不需要 One Call 订阅
运行时踩的坑
- Complication.unit 不保证是 String(周跑步距离返回的就不是),直接调
.length() 抛 Symbol Not Found。unit 和 shortLabel 都改成先 instanceof 判型。
- 浮点 value 直接 toString 印成 "0.000000",统一格式化成一位小数。
- 第三方 App 槽位每取一次就要走完整个迭代器,四个格子都选 App 时会走四遍。
改成每帧只枚举一次,由 view 在绘制开始时调 Comp.beginFrame() 失效。
测试
全字段验证的 harness 本身会触发看门狗(一次 onUpdate 里算 156 个字段),
改成分批每帧 12 个。实际使用一帧只算 4 个,不受影响。
156 项全部可渲染,零运行时错误;17 款设备全部编译通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
369 lines
23 KiB
Python
369 lines
23 KiB
Python
#!/usr/bin/env python3
|
||
"""一张表 → 字段 id 表、两种语言的文案、以及设置项 XML。
|
||
|
||
字段编号沿用 https://watchface.io/docs/datafields,方便两边对照;但这里只收录
|
||
Connect IQ 在手表本机能算出来的字段。
|
||
|
||
需要联网服务的(OpenWeatherMap 的部分、StormGlass)、需要手机侧与 Garmin
|
||
Connect 集成的(按运动分类的周/月累计)、需要用户装了别的 App 的(CGM、
|
||
补水、行情),都不在这里 —— 具体原因见 README 第 7 节。
|
||
|
||
python3 tools/gen_fields.py
|
||
|
||
产出:
|
||
source/FieldTable.mc 字段 id 表 + 下标反查
|
||
resources/settings/settings.xml 四个数据位的选择器
|
||
resources/strings/fields.xml 英文的字段名与表盘标签
|
||
resources-chn/strings/fields.xml 中文的字段名与表盘标签
|
||
|
||
加字段的流程:在下面的 FIELDS 表里加一行 → 跑一遍本脚本 →
|
||
到 source/Fields.mc 对应分类的函数里补一个分支。设置项、两种语言的文案、
|
||
表盘上的标签都会自动跟上,不会漏。
|
||
"""
|
||
import os
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
ROOT = os.path.dirname(HERE)
|
||
|
||
# id, key, English name, Chinese name, on-face label (en), on-face label (cn)
|
||
FIELDS = [
|
||
(0, "Off", "Off", "关闭", "", ""),
|
||
|
||
# ---- date / time ----------------------------------------------------
|
||
(860, "Time", "Time", "时间", "TIME", "时间"),
|
||
(880, "UtcTime", "UTC Time", "UTC 时间", "UTC", "UTC"),
|
||
(864, "AltTime1", "Alternate Time 1", "第二时区 1", "TZ1", "时区1"),
|
||
(865, "AltTime2", "Alternate Time 2", "第二时区 2", "TZ2", "时区2"),
|
||
(851, "DayMonthDay", "Day Month Day", "星期 月 日", "DATE", "日期"),
|
||
(872, "DayDate", "Day Date", "星期 日", "DATE", "日期"),
|
||
(852, "MonthDay", "Month Day", "月 日", "DATE", "日期"),
|
||
(855, "DayOfMonth", "Day of Month", "日", "DAY", "日"),
|
||
(870, "DayShort", "Day (Tue)", "星期简写", "DAY", "星期"),
|
||
(856, "DayFull", "Full Day (Tuesday)", "星期全称", "DAY", "星期"),
|
||
(853, "MonthName", "Month (Jan)", "月份", "MON", "月"),
|
||
(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", "周"),
|
||
(861, "CustomTime", "Custom Time Format", "自定义时间格式", "TIME", "时间"),
|
||
(862, "DateFmt1", "Date Format 1", "日期格式 1", "DATE", "日期"),
|
||
(863, "DateFmt2", "Date Format 2", "日期格式 2", "DATE", "日期"),
|
||
(735, "Countdown", "Event Countdown", "事件倒计时", "DAYS", "倒计时"),
|
||
|
||
# ---- activity -------------------------------------------------------
|
||
(1, "Steps", "Steps", "步数", "STEP", "步数"),
|
||
(14, "StepsRound", "Steps Rounded", "步数 (取整)", "STEP", "步数"),
|
||
(707, "StepsToGo", "Steps to Go", "剩余步数", "TO GO", "还差"),
|
||
(2, "Calories", "Calories", "卡路里", "KCAL", "千卡"),
|
||
(603, "ActiveCal", "Active Calories", "活动卡路里", "ACAL", "活动"),
|
||
(150, "ActiveMin", "Active Minutes", "活动分钟", "MIN", "分钟"),
|
||
(152, "ActiveMod", "Active Min (Moderate)", "中强度分钟", "MOD", "中强度"),
|
||
(154, "ActiveVig", "Active Min (Vigorous)", "高强度分钟", "VIG", "高强度"),
|
||
(151, "ActiveWeek", "Active Minutes (Week)", "本周活动分钟", "WEEK", "周分钟"),
|
||
(3, "Distance", "Distance", "距离", "DST", "距离"),
|
||
(4, "FloorsUp", "Floors Climbed", "上楼层数", "FLR", "楼层"),
|
||
(5, "FloorsDown", "Floors Descended", "下楼层数", "DESC", "下楼"),
|
||
(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天"),
|
||
(828, "Steps28d", "Steps (28 Days)", "近 28 天步数", "28D", "28天"),
|
||
(832, "Cal28d", "Calories (28 Days)", "近 28 天卡路里", "28D", "28天"),
|
||
(836, "Dist28d", "Distance (28 Days)", "近 28 天距离", "28D", "28天"),
|
||
(840, "Floors28d", "Floors (28 Days)", "近 28 天楼层", "28D", "28天"),
|
||
|
||
# ---- body -----------------------------------------------------------
|
||
(618, "HeartRate", "Heart Rate", "心率", "HR", "心率"),
|
||
(276, "RestingHr", "Resting Heart Rate", "静息心率", "RHR", "静息"),
|
||
(12, "Stress", "Stress Score", "压力指数", "STRS", "压力"),
|
||
(635, "PulseOx", "Pulse Ox", "血氧", "SPO2", "血氧"),
|
||
(11, "Respiration", "Respiration Rate", "呼吸频率", "RESP", "呼吸"),
|
||
(623, "BodyBattery", "Body Battery", "身体电量", "BODY", "体能"),
|
||
(8, "MoveBar", "Move Bar", "活动条", "MOVE", "活动"),
|
||
(15, "MoveBarRev", "Move Bar Reversed", "活动条 (反向)", "MOVE", "活动"),
|
||
(281, "Weight", "Weight", "体重", "WT", "体重"),
|
||
(742, "Bmi", "BMI", "BMI", "BMI", "BMI"),
|
||
(13, "Recovery", "Time to Recovery", "恢复时间", "RCVR", "恢复"),
|
||
(278, "Vo2Run", "VO2 Max (Run)", "最大摄氧量 (跑)", "VO2", "摄氧"),
|
||
(277, "Vo2Bike", "VO2 Max (Bike)", "最大摄氧量 (骑)", "VO2", "摄氧"),
|
||
|
||
# ---- system ---------------------------------------------------------
|
||
(250, "Battery", "Battery", "电量", "BATT", "电量"),
|
||
(251, "BatteryDays", "Battery in Days", "剩余天数", "DAYS", "天数"),
|
||
(257, "BatteryBoth", "Battery / Days", "电量 / 天数", "BATT", "电量"),
|
||
(253, "Alarms", "Alarms", "闹钟", "ALRM", "闹钟"),
|
||
(255, "Notifications", "Notifications", "通知", "MSG", "通知"),
|
||
(254, "DoNotDisturb", "Do Not Disturb", "勿扰模式", "DND", "勿扰"),
|
||
(256, "Bluetooth", "Bluetooth", "蓝牙", "BT", "蓝牙"),
|
||
(252, "Solar", "Solar Intensity", "太阳能强度", "SOLR", "太阳能"),
|
||
|
||
# ---- environment ----------------------------------------------------
|
||
(615, "Altitude", "Altitude", "海拔", "ALT", "海拔"),
|
||
(616, "SeaPressure", "Sea Level Pressure", "海平面气压", "hPa", "气压"),
|
||
(733, "AmbPressure", "Ambient Pressure", "环境气压", "hPa", "气压"),
|
||
(712, "Sunrise", "Sunrise", "日出", "RISE", "日出"),
|
||
(717, "Sunset", "Sunset", "日落", "SET", "日落"),
|
||
(700, "NextSun", "Next Sun Event", "下一太阳事件", "SUN", "太阳"),
|
||
(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", "气温"),
|
||
(639, "TempHiLo", "Temperature High/Low", "最高/最低气温", "HI/LO", "高低温"),
|
||
(300, "FeelsLike", "Feels Like", "体感温度", "FEEL", "体感"),
|
||
(301, "TempHigh", "High Temperature", "最高气温", "HIGH", "最高"),
|
||
(302, "TempLow", "Low Temperature", "最低气温", "LOW", "最低"),
|
||
(305, "Humidity", "Humidity", "湿度", "HUM", "湿度"),
|
||
(304, "Precip", "Precipitation %", "降水概率", "RAIN", "降水"),
|
||
(308, "WindSpeed", "Wind Speed", "风速", "WIND", "风速"),
|
||
(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", "露点"),
|
||
|
||
# ---- 系统 Complication(含第三方 App 发布的)------------------------
|
||
# 这些走 Toybox.Complications,见 source/Comp.mc。
|
||
(612, "NextCal", "Next Calendar Event", "下一个日程", "CAL", "日程"),
|
||
(626, "TrainStatus", "Training Status", "训练状态", "TRN", "训练"),
|
||
(627, "Race5k", "Race Time (5k)", "5k 完赛预测", "5K", "5K"),
|
||
(628, "Race10k", "Race Time (10k)", "10k 完赛预测", "10K", "10K"),
|
||
(629, "RaceHalf", "Race Time (Half)", "半马完赛预测", "HM", "半马"),
|
||
(630, "RaceFull", "Race Time (Marathon)", "全马完赛预测", "FM", "全马"),
|
||
(631, "Pace5k", "Pace (5k)", "5k 配速预测", "5K", "5K配速"),
|
||
(632, "Pace10k", "Pace (10k)", "10k 配速预测", "10K", "10K配速"),
|
||
(633, "PaceHalf", "Pace (Half)", "半马配速预测", "HM", "半马配速"),
|
||
(634, "PaceFull", "Pace (Marathon)", "全马配速预测", "FM", "全马配速"),
|
||
(641, "GolfScore", "Last Golf Round Score", "最近高尔夫得分", "GOLF", "高尔夫"),
|
||
(619, "RunWeek", "Run Distance (Week)", "本周跑步距离", "RUN", "跑步"),
|
||
(620, "BikeWeek", "Bike Distance (Week)", "本周骑行距离", "BIKE", "骑行"),
|
||
(695, "App1", "App 1 (on watch)", "第三方 App 1", "APP1", "App1"),
|
||
(696, "App2", "App 2 (on watch)", "第三方 App 2", "APP2", "App2"),
|
||
(697, "App3", "App 3 (on watch)", "第三方 App 3", "APP3", "App3"),
|
||
(698, "App4", "App 4 (on watch)", "第三方 App 4", "APP4", "App4"),
|
||
(699, "App5", "App 5 (on watch)", "第三方 App 5", "APP5", "App5"),
|
||
|
||
# ---- 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", "后日"),
|
||
(538, "OwmAqi", "OWM Air Quality (AQI)", "OWM 空气质量", "AQI", "空气"),
|
||
(539, "OwmAqiText", "OWM AQI Description", "OWM 空气质量描述", "AQI", "空气"),
|
||
(540, "OwmPm25", "OWM PM2.5", "OWM PM2.5", "PM2.5", "PM2.5"),
|
||
(541, "OwmPm10", "OWM PM10", "OWM PM10", "PM10", "PM10"),
|
||
(542, "OwmCo", "OWM CO", "OWM 一氧化碳", "CO", "CO"),
|
||
(516, "OwmCity", "OWM City Name", "OWM 城市", "CITY", "城市"),
|
||
(517, "OwmUpdated", "OWM Update Time", "OWM 更新时间", "OWM", "更新"),
|
||
|
||
# ---- custom ---------------------------------------------------------
|
||
(703, "Custom1", "Custom Text 1", "自定义文字 1", "", ""),
|
||
(704, "Custom2", "Custom Text 2", "自定义文字 2", "", ""),
|
||
(705, "Custom3", "Custom Text 3", "自定义文字 3", "", ""),
|
||
]
|
||
|
||
SLOTS = [("LeftTop", "Top-Left Field", "左上数据"),
|
||
("RightTop", "Top-Right Field", "右上数据"),
|
||
("BottomLeft", "Bottom-Left Field", "左下数据"),
|
||
("BottomRight", "Bottom-Right Field", "右下数据")]
|
||
|
||
RING = [(0, "Steps", "Steps", "步数"), (1, "Calories", "Calories", "卡路里"),
|
||
(2, "Distance", "Distance", "距离"), (3, "Floors", "Floors", "楼层"),
|
||
(4, "MoveBar", "Move Bar", "动动条"), (5, "ActiveMin", "Active Minutes", "活动分钟"),
|
||
(6, "BodyBattery", "Body Battery", "身体电量")]
|
||
|
||
OTHER_SETTINGS = """
|
||
<setting propertyKey="@Properties.TempUnit" title="@Strings.SettingTempUnit">
|
||
<settingConfig type="list">
|
||
<listEntry value="0">@Strings.TempUnitAuto</listEntry>
|
||
<listEntry value="1">@Strings.TempUnitC</listEntry>
|
||
<listEntry value="2">@Strings.TempUnitF</listEntry>
|
||
</settingConfig>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.AltOffset1" title="@Strings.SettingAltOffset1">
|
||
<settingConfig type="numeric" min="-720" max="840"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.AltOffset2" title="@Strings.SettingAltOffset2">
|
||
<settingConfig type="numeric" min="-720" max="840"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.CountdownDate" title="@Strings.SettingCountdownDate">
|
||
<settingConfig type="alphaNumeric" maxLength="10"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.TimeFmt" title="@Strings.SettingTimeFmt">
|
||
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.DateFmt1" title="@Strings.SettingDateFmt1">
|
||
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.DateFmt2" title="@Strings.SettingDateFmt2">
|
||
<settingConfig type="alphaNumeric" maxLength="16"/>
|
||
</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>
|
||
|
||
<setting propertyKey="@Properties.Custom2" title="@Strings.SettingCustom2">
|
||
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.Custom3" title="@Strings.SettingCustom3">
|
||
<settingConfig type="alphaNumeric" maxLength="12"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.ShowTop" title="@Strings.SettingShowTop">
|
||
<settingConfig type="boolean"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.ShowBand" title="@Strings.SettingShowBand">
|
||
<settingConfig type="boolean"/>
|
||
</setting>
|
||
|
||
<setting propertyKey="@Properties.ShowBottom" title="@Strings.SettingShowBottom">
|
||
<settingConfig type="boolean"/>
|
||
</setting>
|
||
"""
|
||
|
||
|
||
def field_list(indent=8):
|
||
pad = " " * indent
|
||
return "\n".join('%s<listEntry value="%d">@Strings.Fld%s</listEntry>'
|
||
% (pad, fid, key) for fid, key, _, _, _, _ in FIELDS)
|
||
|
||
|
||
def write(path, text):
|
||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||
open(path, "w").write(text)
|
||
print("wrote", os.path.relpath(path, ROOT))
|
||
|
||
|
||
def main():
|
||
ids = [f[0] for f in FIELDS]
|
||
assert len(ids) == len(set(ids)), "duplicate field id"
|
||
|
||
# ---- FieldTable.mc ----
|
||
L = ["// ⚠️ 本文件由 tools/gen_fields.py 自动生成,请勿手改。",
|
||
"// 要加字段:在生成器的 FIELDS 表里加一行,跑一遍生成器,",
|
||
"// 再到 source/Fields.mc 对应分类的函数里补一个分支。",
|
||
"//",
|
||
"// 字段编号沿用 https://watchface.io/docs/datafields,只收录 Connect IQ",
|
||
"// 表盘在本机能算出来的那些。",
|
||
"import Toybox.Lang;", "", "module FieldTable {", "",
|
||
" // 全部字段 id,顺序与 FieldLabels 资源串(逗号分隔的标签表)严格一致 ——",
|
||
" // Fields.label() 就是靠这个下标去取对应标签的。",
|
||
" const IDS = ["]
|
||
row = " "
|
||
for i, fid in enumerate(ids):
|
||
piece = "%d%s" % (fid, "," if i < len(ids) - 1 else "")
|
||
if len(row) + len(piece) > 92:
|
||
L.append(row)
|
||
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; }",
|
||
" }",
|
||
" return 0;",
|
||
" }",
|
||
"}"]
|
||
write(os.path.join(ROOT, "source", "FieldTable.mc"), "\n".join(L) + "\n")
|
||
|
||
# ---- settings.xml ----
|
||
S = ['<settings>', '',
|
||
' <setting propertyKey="@Properties.Theme" title="@Strings.SettingTheme">',
|
||
' <settingConfig type="list">']
|
||
names = ["Ember", "Aurora", "Brass", "Voltage", "Reef", "Acid", "Kelp"]
|
||
for i, n in enumerate(names):
|
||
S.append(' <listEntry value="%d">@Strings.Theme%s</listEntry>' % (i + 1, n))
|
||
S += [' </settingConfig>', ' </setting>', '']
|
||
for slot, _, _ in SLOTS:
|
||
S += [' <setting propertyKey="@Properties.%s" title="@Strings.Setting%s">' % (slot, slot),
|
||
' <settingConfig type="list">', field_list(12),
|
||
' </settingConfig>', ' </setting>', '']
|
||
for ring in ("RingTR", "RingTL", "RingBR", "RingBL"):
|
||
S += [' <setting propertyKey="@Properties.%s" title="@Strings.Setting%s">' % (ring, ring),
|
||
' <settingConfig type="list">']
|
||
for rid, key, _, _ in RING:
|
||
S.append(' <listEntry value="%d">@Strings.Metric%s</listEntry>' % (rid, key))
|
||
S += [' </settingConfig>', ' </setting>', '']
|
||
S.append(OTHER_SETTINGS.rstrip())
|
||
S.append('</settings>')
|
||
write(os.path.join(ROOT, "resources", "settings", "settings.xml"), "\n".join(S) + "\n")
|
||
|
||
# ---- field strings, both languages ----
|
||
for lang, folder, ni, li in (("en", "resources", 2, 4), ("chn", "resources-chn", 3, 5)):
|
||
X = ['<strings>']
|
||
X.append(' <!-- Field names shown in the settings picker. -->')
|
||
for f in FIELDS:
|
||
X.append(' <string id="Fld%s">%s</string>' % (f[1], f[ni]))
|
||
X.append('')
|
||
X.append(' <!-- On-face captions, in FieldTable.IDS order. -->')
|
||
X.append(' <string id="FieldLabels">%s</string>'
|
||
% ",".join(f[li] for f in FIELDS))
|
||
X.append('')
|
||
for rid, key, en, cn in RING:
|
||
X.append(' <string id="Metric%s">%s</string>' % (key, en if lang == "en" else cn))
|
||
X.append('</strings>')
|
||
write(os.path.join(ROOT, folder, "strings", "fields.xml"), "\n".join(X) + "\n")
|
||
|
||
print("%d fields, %d slots" % (len(FIELDS), len(SLOTS)))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|