From 22340b09fa4c8b84d12b8f2a670007aea60541db Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Thu, 10 Sep 2026 11:27:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=BA=E5=9E=8B=E8=A6=86=E7=9B=96=2017=20?= =?UTF-8?q?=E2=86=92=2055=20=E6=AC=BE=EF=BC=8C=E8=A1=A5=20LICENSE=20?= =?UTF-8?q?=E4=B8=8E=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 机型 新增 38 款圆屏 + CIQ ≥ 4.2 的机型:fenix 7 Pro 全系、Venu 2/3S、 Approach S50/S70、Descent G2/Mk3、D2 系列、Instinct 3 AMOLED、 FR255S/265S/570/955 等。其中 34 款的屏宽已有现成图标资源。 机型列表和 monkey.jungle 改由 tools/gen_devices.py 扫描本机 SDK 生成, 筛选条件是「支持 watchFace + 圆屏 + CIQ ≥ 4.2」(Complications 要 4.2)。 55 条资源路径不再手写。 新增 218px 与 360px 两档图标。218px 上图标只有 14×14,设计稿里太阳与云 之间那道 1.7 设计像素的暗缝连一个物理像素都占不到,光栅化出来是一坨没有 结构的橙块 —— 该尺寸改用按目标像素直接摆的简化标记。 常亮模式:结论是不改 为了压所谓的 10% 点亮率,一度把进度环也砍掉了。换成平均亮度(AMOLED 功耗 的合理代理)重新量之后发现:普通模式 14.1%,常亮 2.9%,本来就只有 1/5; 砍掉环只从 3.01% 降到 2.91%。0.1 个百分点换掉一个可见特征不划算,环留着。 两种度量的差异和这个决定都写进了代码注释和 README。 其它 - LICENSE(MIT)。只覆盖代码 —— design/ 下的设计稿版权归第三方, 发布前需取得原作者授权或替换视觉,文件内已注明。 - UserProfile.getProfile() 加异常保护(这个 API 家族已经坑过三次) - CHANGELOG.md,版本号 1.0.0 → 1.1.0 验证:55 款全部编译通过(分三批 18+18+19);.iq 上架包可正常导出。 Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 29 +++++ LICENSE | 29 +++++ README.md | 24 +++- manifest.xml | 56 +++++++-- monkey.jungle | 49 +++++++- resources-icons-218/drawables/drawables.xml | 5 + resources-icons-218/drawables/sunrise.png | Bin 0 -> 151 bytes resources-icons-218/drawables/sunset.png | Bin 0 -> 148 bytes resources-icons-218/drawables/weather.png | Bin 0 -> 148 bytes resources-icons-240/drawables/weather.png | Bin 157 -> 153 bytes resources-icons-360/drawables/drawables.xml | 5 + resources-icons-360/drawables/sunrise.png | Bin 0 -> 579 bytes resources-icons-360/drawables/sunset.png | Bin 0 -> 583 bytes resources-icons-360/drawables/weather.png | Bin 0 -> 621 bytes resources-launcher-38/drawables/drawables.xml | 3 + .../drawables/launcher_icon.png | Bin 0 -> 2295 bytes resources-launcher-54/drawables/drawables.xml | 3 + .../drawables/launcher_icon.png | Bin 0 -> 3786 bytes resources-launcher-61/drawables/drawables.xml | 3 + .../drawables/launcher_icon.png | Bin 0 -> 4370 bytes source/Fenix8V3View.mc | 6 + source/Fields.mc | 12 +- tools/gen_devices.py | 118 ++++++++++++++++++ tools/gen_icons.py | 57 ++++++++- 24 files changed, 374 insertions(+), 25 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 resources-icons-218/drawables/drawables.xml create mode 100644 resources-icons-218/drawables/sunrise.png create mode 100644 resources-icons-218/drawables/sunset.png create mode 100644 resources-icons-218/drawables/weather.png create mode 100644 resources-icons-360/drawables/drawables.xml create mode 100644 resources-icons-360/drawables/sunrise.png create mode 100644 resources-icons-360/drawables/sunset.png create mode 100644 resources-icons-360/drawables/weather.png create mode 100644 resources-launcher-38/drawables/drawables.xml create mode 100644 resources-launcher-38/drawables/launcher_icon.png create mode 100644 resources-launcher-54/drawables/drawables.xml create mode 100644 resources-launcher-54/drawables/launcher_icon.png create mode 100644 resources-launcher-61/drawables/drawables.xml create mode 100644 resources-launcher-61/drawables/launcher_icon.png create mode 100644 tools/gen_devices.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3197ce7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# 更新记录 + +## 1.1.0 + +**机型覆盖 17 → 55 款** + +新增 38 款圆屏、Connect IQ ≥ 4.2 的机型,包括 fenix 7 Pro 全系、Venu 2/3S、 +Approach S50/S70、Descent G2/Mk3、D2 系列、Instinct 3 AMOLED、FR255S/265S/570/955 等。 +机型列表与资源路径改由 `tools/gen_devices.py` 扫描本机 SDK 生成,不再手写。 + +新增两档图标尺寸(218px、360px)。218px 上设计稿的天气图标只有 14×14, +太阳与云之间的暗缝不足一个像素,会糊成一块 —— 该尺寸改用专画的简化标记。 + +**其它** + +- 补 LICENSE(MIT,仅覆盖代码;`design/` 下的设计稿版权归第三方,见文件内说明) +- `UserProfile.getProfile()` 加异常保护 +- 版本号 1.0.0 → 1.1.0 + +## 1.0.0 + +按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。 + +- 156 项可选数据字段,编号对齐 watchface.io 目录 +- 七套主题,配色逐根刻度实测自设计稿 +- 常亮(AOD)模式,含防烧屏位移 +- 简体中文 / 英文双语,表盘文字亦本地化 +- OpenWeatherMap 接入(用户自备 API key,后台定时拉取) +- 系统 Complication 接入(日历、训练状态、完赛预测、第三方 App) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb7ae9f --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +MIT License + +Copyright (c) 2026 ericwyuan + +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. + +--------------------------------------------------------------------------- +注意:本许可只覆盖**本仓库的代码**。 + +design/ 目录下的设计稿(watchface-*.svg、watchface-tokens.json、rebuild.py) +来自第三方 watchface-kit,版权归其原作者所有,不在本许可范围内。 +表盘的视觉呈现是对那套设计的 1:1 还原 —— 若要发布到 Connect IQ Store 或以 +其它形式分发,需先取得原作者授权,或将视觉部分替换为原创设计。 diff --git a/README.md b/README.md index 565dce8..6671182 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fenix 8 V3 — Garmin 数据表盘 -按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。设计画布 **500×500**,渲染时按屏宽等比缩放 `s = screenWidth / 500`,支持圆形 AMOLED(Fenix 8 / Epix 2 / Venu 3 等)与圆屏 MIP(Fenix 7 / Enduro 3 / FR255 等)共 17 款设备。 +按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。设计画布 **500×500**,渲染时按屏宽等比缩放 `s = screenWidth / 500`,支持圆形 AMOLED 与圆屏 MIP 共 **55 款**设备(Fenix 7/8 全系含 Pro、Epix 2、Enduro 3、Venu 2/3、FR 系列、Approach S50/S70、Descent、D2、Instinct 3 AMOLED 等)。 - **SDK**:Connect IQ SDK 9.1.0(`monkeyc` 9.1.0,`minSdkVersion="4.2.0"`) - **语言**:Monkey C;界面文案含英文与简体中文 @@ -24,6 +24,7 @@ fenix8v3-watchface/ ├── tools/ │ ├── gen_themes.py # design/*.svg -> source/Themes.mc │ ├── gen_fields.py # 字段表 -> FieldTable.mc + settings.xml + 两种语言文案 +│ ├── gen_devices.py # 扫描 SDK -> manifest 机型列表 + monkey.jungle │ ├── gen_icons.py # design/*.svg -> 各尺寸图标位图 + 启动图标 │ └── gen_preview_svg.py # source/Themes.mc -> preview-themes.svg + preview.html ├── source/ @@ -49,10 +50,14 @@ fenix8v3-watchface/ 重新生成全部派生文件: ```bash -python3 tools/gen_themes.py && python3 tools/gen_fields.py \ +python3 tools/gen_devices.py \ + && python3 tools/gen_themes.py && python3 tools/gen_fields.py \ && python3 tools/gen_icons.py && python3 tools/gen_preview_svg.py ``` +`gen_devices.py` 要先跑 —— 它决定了需要哪些图标尺寸,`gen_icons.py` 的 +`WIDTHS` / `LAUNCHER` 两张表必须跟它对齐,少一档就会有机型编译不过。 + 三份渲染实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。字段表、设置项、两种语言的文案同样只有 `tools/gen_fields.py` 里那一张表。 --- @@ -283,7 +288,16 @@ AMOLED 机型在常亮模式下必须压低点亮像素,否则既费电又有 - 时间与日期压暗到 55%,其余数据位全部隐藏 - 整个画面按分钟在 ±4px 内游走,避免固定像素长期点亮 -只在 `DeviceSettings.requiresBurnInProtection` 为真时启用;MIP 机型(Fenix 7 / Enduro 3 等)不受影响,照常全量绘制。 +只在 `DeviceSettings.requiresBurnInProtection` 为真时启用;MIP 机型不受影响,照常全量绘制。 + +实测(fenix847mm,取表盘内区): + +| | 点亮像素数 | 平均亮度(功耗代理) | +|---|---|---| +| 普通模式 | 27.6% | 14.1% | +| 常亮模式 | 10.4% | **2.9%** | + +两个口径差别很大:「点亮像素数」把每个抗锯齿边缘像素都算成亮的,和实际功耗对不上。曾试过连进度环也砍掉,平均亮度只从 3.01% 降到 2.91% —— 0.1 个百分点换掉一个可见特征不划算,所以环保留。 模拟器可用 `Settings → Display Mode` 切换验证。 @@ -328,7 +342,9 @@ for d in enduro3 fenix843mm fenix847mm fenix8pro47mm fenix8solar47mm fenix8solar done ``` -屏幕分辨率跨度较大(fenix7s 240×240 至 fenix847mm / venu3 454×454),全部按 `s = width/500` 缩放。 +屏幕分辨率跨度较大(FR255S 218×218 至 454×454 共 8 档),全部按 `s = width/500` 缩放。 + +机型列表和 `monkey.jungle` 由 `tools/gen_devices.py` 扫描本机已安装的 SDK 设备生成:筛选条件是「支持 watchFace + 圆屏 + CIQ ≥ 4.2」(Complications 需要 4.2)。**注意结果取决于本机装了哪些设备**,跑之前先确认 SDK 设备装全了,脚本会打印找到的数量。 --- diff --git a/manifest.xml b/manifest.xml index 800dd4c..deda23b 100644 --- a/manifest.xml +++ b/manifest.xml @@ -7,26 +7,64 @@ minSdkVersion="4.2.0" name="@Strings.AppName" type="watchface" - version="1.0.0"> + version="1.1.0"> + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/monkey.jungle b/monkey.jungle index 500909f..3870674 100644 --- a/monkey.jungle +++ b/monkey.jungle @@ -2,31 +2,68 @@ project.manifest = manifest.xml base.sourcePath = source -# resources-chn holds the Simplified Chinese variant of every string; the -# -chn suffix is what tells monkeyc which language it belongs to. +# resources-chn 是简体中文文案;目录名的 -chn 后缀就是语言标记。 base.resourcePath = resources;resources-chn -# Two more resource trees per device, both generated by tools/gen_icons.py: -# resources-icons- sunrise/sunset/weather rasterised from the -# design paths for that exact pixel grid -# resources-launcher- launcher icon at the size the device asks for +# 下面每台设备再挂两棵资源树,都由 tools/gen_icons.py 生成: +# resources-icons-<屏宽> 按该屏幕像素网格光栅化的日出/日落/天气图标 +# resources-launcher-<尺寸> 该机型要求尺寸的启动图标 +# 这一整段由 tools/gen_devices.py 生成,不要手改。 +approachs50.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-56 +approachs7042mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 +approachs7047mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-70 +d2airx10.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-70 +d2mach1.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 +d2mach2.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 +descentg2.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 +descentmk343mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 +descentmk351mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-60 enduro3.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40 epix2.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 +epix2pro42mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 epix2pro47mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 epix2pro51mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-60 fenix7.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 +fenix7pro.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 +fenix7pronowifi.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 fenix7s.resourcePath = $(base.resourcePath);resources-icons-240;resources-launcher-40 +fenix7spro.resourcePath = $(base.resourcePath);resources-icons-240;resources-launcher-40 fenix7x.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40 +fenix7xpro.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40 +fenix7xpronowifi.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40 fenix843mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 fenix847mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 fenix8pro47mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 fenix8solar47mm.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 fenix8solar51mm.resourcePath = $(base.resourcePath);resources-icons-280;resources-launcher-40 +fenixe.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 +fr165.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54 +fr165m.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54 fr255.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 +fr255m.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 +fr255s.resourcePath = $(base.resourcePath);resources-icons-218;resources-launcher-40 +fr255sm.resourcePath = $(base.resourcePath);resources-icons-218;resources-launcher-40 fr265.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 +fr265s.resourcePath = $(base.resourcePath);resources-icons-360;resources-launcher-60 +fr57042mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54 +fr57047mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 +fr955.resourcePath = $(base.resourcePath);resources-icons-260;resources-launcher-40 fr965.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 +fr970.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 +instinct3amoled45mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 +instinct3amoled50mm.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-60 +instinctcrossoveramoled.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-38 +marq2.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 +marq2aviator.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-60 +venu2.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-70 +venu2plus.resourcePath = $(base.resourcePath);resources-icons-416;resources-launcher-70 +venu2s.resourcePath = $(base.resourcePath);resources-icons-360;resources-launcher-61 venu3.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-70 +venu3s.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-70 +venu441mm.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54 +venu445mm.resourcePath = $(base.resourcePath);resources-icons-454;resources-launcher-65 vivoactive5.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-56 +vivoactive6.resourcePath = $(base.resourcePath);resources-icons-390;resources-launcher-54 base.excludeAnnotations = release diff --git a/resources-icons-218/drawables/drawables.xml b/resources-icons-218/drawables/drawables.xml new file mode 100644 index 0000000..b1703a8 --- /dev/null +++ b/resources-icons-218/drawables/drawables.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/resources-icons-218/drawables/sunrise.png b/resources-icons-218/drawables/sunrise.png new file mode 100644 index 0000000000000000000000000000000000000000..e6f1ecf598bbc9245754f6af76ccd87c3e3529a7 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0y_I8PVHkcv5LgQB?(DDZfBb2|Om(dnI`4`s;V~`>#fe wGbZd7OQ`6p{4wcGJj3?%osNG0HHGXMJr9XVPCQyv3$%v8)78&qol`;+0EbsJ=Kufz literal 0 HcmV?d00001 diff --git a/resources-icons-218/drawables/weather.png b/resources-icons-218/drawables/weather.png new file mode 100644 index 0000000000000000000000000000000000000000..3ebfc13c3970795e9a38be600633fbe63ec3aa5b GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0y_Xipc%kcv4+Cj@dGFyL^GKlZLZ z^?P{G%1zS_{t1aU33U0eh-rfPy0nX;Q`7FSYQ%fhmWr>Ia@zesAfoY|f461Bo#bzm wA8Ovzn&ZcCE|NC1|?yI2MKA<%Wp00i_>zopr0JDKM-~0e0*AXE%mk?V30000L_t(|oSjlJ62LGB0$iUTbT~&bK izm}sb@bx?qSa< + + + + diff --git a/resources-icons-360/drawables/sunrise.png b/resources-icons-360/drawables/sunrise.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe584c6f3b13250cfbc7fe14f384d7fcd79247d GIT binary patch literal 579 zcmV-J0=)f+P)U1h{=7&V$bH??7cU6ugJhK?99&Dxo2nR21y*0bSBsvtYhaC6M!qT`$|(4 zAzOECF9Yv@8=1c?uLd4mS*UDI_9}1zxY+>Kq2Y^iZ~;BreDfFE4><@h1di9dl91H3 zy(Q_Vq!md$NkLM_@t&jwNhiGSRN^lr{gO0ioI+9!{I*82*h5OIWeP*1pN=wp^*^`ok^r=_q>d?M+U zfwme{lD2sfHfpN$SOMHlh<1+&Sk^hmVO zpH=r{oIDg?W3h*(!8NZqF&Dj_vbN=gal57Ci#!VaW%N_oQzmJ}bU?EE%70zQ3PHsb Rj^6+P002ovPDHLkV1kYj5|97@ literal 0 HcmV?d00001 diff --git a/resources-icons-360/drawables/sunset.png b/resources-icons-360/drawables/sunset.png new file mode 100644 index 0000000000000000000000000000000000000000..abdec917f6a8b191bbe45d6c344b096b31f439af GIT binary patch literal 583 zcmV-N0=WH&P)S7S@8<5Vz7!c4W_NbZ+1dHH8ziw?Qtco%SjWc19gwTA|CP2Z zLUG@-Jq5f1&KLeJT!TC~vycTRdj>cT%(jqKw0u!XE}(B)27k1DQ<4A!V1L6a2}wQM z3zEJ`T9VY46eLxC-j}o>>45L6?D-=}Ym(;86D2jsFKQ%r44&|_Qlp74XPCL1FHKAB z5sn9RzYILdYX8C5`{t?hvLp?KJt*m++GR=7;SL;SS)y&KCz76- zXtPBn>9eFMlg5Ilk@;3LB%L$CMn~q!f33i6Y0&U&ISot$pMck{W(d+gh58#^#D^M&-eDKI!7GCI5>unAmYc)rX7f3g?n z7&2e$D7NWr(X54WaFO8{&Hz8yesremL%<@i0^9(u1NVROkT% zlI#n0gF2~RO>DKi8Hj!XM}fP*Zr~NLA2=4g&jQbZXTWKo4P1{5=U2)(YD2xD&be5| zD_M1QDO!rq!LVJ5KuvWv4w+Nu!#);%0|BH^^K1ouzBvk*N;{!21EztU!0*&44W5Pb z4&YSS9tPJpfuZ1e7jQf|r0kgObUN%1uojp~3z~(F0EdCCz!-2p5Ve6B-~n(CxBwhT z(6<8EdEhhfp^o!Gb)!0}zN;yq>QnWg`Z~D(675)9&^&aXIvoMpvCdWWPuQo_$FaW_ zjj$%-6l$J!2kN76F1w&SpeoH+@2F>8JR-W+zY%<0p>t_kNpTkrqMj5{gCjr_xCM+a z`X)}f97tNrpk0Yfm|TC*yZKnoJ^`*MUWJ>11nMiRt8kUkQXdM7P0&fyZ$$`mO zr8heJiHVlLhNwP1c831@N~+cNt7@H`v^V4y^!Z-(iTZODTz^x)s&Cbc_U=o!6*INV z5wHaqr87U4dZk*x3p&Fr1Afq(G40f`99*mAyebXK2O^Qb_{hOaymI6f00000NkvXX Hu0mjfHo_j` literal 0 HcmV?d00001 diff --git a/resources-launcher-38/drawables/drawables.xml b/resources-launcher-38/drawables/drawables.xml new file mode 100644 index 0000000..579343d --- /dev/null +++ b/resources-launcher-38/drawables/drawables.xml @@ -0,0 +1,3 @@ + + + diff --git a/resources-launcher-38/drawables/launcher_icon.png b/resources-launcher-38/drawables/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2afa7175784432074fb6f6b1f93b6b7c5fc33a32 GIT binary patch literal 2295 zcmV3KpeMXkjpp#0J5j78|7|P$9ljn;H`|*4k7^=rAQI zX-fYX11Jbq0U3sYdE7hqb@pEUj0e%&)7L=E{*wj#p71tI$e-LRwP!6kVyK z?CkzrJKo>H%RhODH%sqsZXuZ4iUagKY@Zn|Ef^!w^~8F{Y+TL#d)D)I?`A^XiRwO4 zJtV5VqB=-4dW-k=iRuotcQfIh^}M~YmHQ{wGX}s&H1|2Zakf8n5mwr8fPLYo+&krB zmR~rDI?9R<#Bsh%M9UB@M<^>gAR~}PO$IetRq|K_$iRPg)4ly;)-3oXzXjY0HqMV& z(cR%aS2E?U`K+CKsWcN;5JFdmp$wyCV5>lC1ge2*jTUD>pdaW{!hVn}kj2{^9wi_r zbbhEE55K~lzk7}CC0k!etn}cYZ)D;1O*}Ss9Mw1peH^P5Dw=?x78nUwIGh#^^g&NL z^4N%ka6t`>tt@)*D7YRV3z88vIUH7lgZt@!{ylF0!Lw{U6SoK+IRBDRaL^I@o?Q`a8olhO#o8hjtDld zxD3t;cAyX5_>d*nuIK5J?bC>@UxZ!UhWq_h+<3rg(VOKY-c~N<) z9df5Q-xNdH`_q@?rYm6i0->o<@jXBuG=sMe(u0`4Y+>QDS9!L?;TW+NFslWB_cSh9 zIfHlVYpHaKngqs`K`;{5z6JMhfc|V`FtDHsUZdZW$VfDdfOApk#SHxXJMiFD;0J&V zXbx{dx_U^hexF%?_<)aADy9{@0HTX9(uQpPHXYL%Y4RQ?Wmp%7pc)={86JB5IH8iU z^My6XKVa!}c=87DgCIE|i!&Dc!DqZX{Y9E}5h`tXqjRE^-7t$~(`sm|>ZV&>6 z2cC;a1t#)B_$5gJI1ebqEIhRpmbQTn!Dm47So#R3)zGwI7R#g!r*p8PAhRl|eP*WY zYOJ9eqqqQLtKrdI@cq}KWeENMQmT(5W}x7q@CM=n z;V=*b2!Ie+*9~(L3ONPER|U}N6_SCN(@EI>AEz@y1On$ti;<-I{-3O?&8-)Sp?i9V|y!HNij z=|P+u&SG&K#u$>xiinruO_^9X814*~JTa?dTtoeWFbq+JL?X_Xcen82&RrN0{4iyK zNK`Ny8ZKb=w`P;i7oyk-0`~9g>M*@Wx?aj;a$dw50OvdnjSU!y3f^#`R2~{HY@`%7 zBGzOwIp2Gv>m}3K`O$m%TyAR+SVYu&SD<>-SZeAT0mVr1r6C$6ZwX4Gax|mIj>m;L zMAQd?C7;V}?d<&MJp;JDzW&EW5a$CwkV|k$?LpSuk~!ALixj(pAs7&C;Ht1sAhcjh0wUOUmh^2t>5`Qdc$wyoQqKoQfj3Y$u$4v#PEY+X5TlbLtX zUavV}|3Sk&b78@>h*U5H+f-2OglAm@t8NeubsNYj*G}5)SI*mH8sa-zQ>oP9mQ`2* zWc?yc-614@6&*9jo2H(l>RcX69uiJj)&?)_jFtetd{P*R;>#&q)(DT>0y zR?c06rAVlh-A>_qUxPp101`qtsQ4^CP|V)_I1ym#>e2JEjT?E>-dta&d^o@zs?uD43_; zVc{=cIKzRrgp!8v@M3PBHI8+0AUA*`k4+5<)gctp@XrIV=^$+BSGo#k?%8Ssm|i8! ztA}|FkgS5f0Fvbjaa^JbTM&4AA4~39&(o*s7iSe!md|0~H4}Jje1ht(J_*ANRx(H~ z13@Tg8hZPb18LzvsPq-xR3$JrCX7oeqecip1r%aRtPH3?gECxwjRv3Q=>Pj(ZeP=W z9-_)Iaz%k-(N~$WXfkUjRZ6o@Lwb-fGl&f$2L4z1-%2jPrY;{tJCE%c^dv9LX-1o0YxH~bX&YKt<7hAkSJ&gI$6mI!SV3s5c z%&5w%H8~+4Lg55qMFe63@c?|8(jOO>iLqsW!LM7rohQ?9$Ox3gle5`ApRP+b&%vJP z?jP23`2=Bpm6PU}XR<-+txyTzl$1xi3Wl8nRy+18FaN3Q1aUZ8x-S%m{|kMGDMs>c R?SB9O002ovPDHLkV1lZ|G|B(~ literal 0 HcmV?d00001 diff --git a/resources-launcher-54/drawables/drawables.xml b/resources-launcher-54/drawables/drawables.xml new file mode 100644 index 0000000..579343d --- /dev/null +++ b/resources-launcher-54/drawables/drawables.xml @@ -0,0 +1,3 @@ + + + diff --git a/resources-launcher-54/drawables/launcher_icon.png b/resources-launcher-54/drawables/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e371b51378fec695f66699148ba5bcd1dd94a8 GIT binary patch literal 3786 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 + + diff --git a/resources-launcher-61/drawables/launcher_icon.png b/resources-launcher-61/drawables/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..03602b2718001b561b5de004a609b7de774d6c35 GIT binary patch literal 4370 zcmV+t5$*1YP)%ur`Np3i$D>;8d&)aqF*h+F9ClbJq}jnP_Nfz z7-_?$-|H{?AoDLgi%U*AWeMkKMN}MWViZNFsv!pyp}!NJdx`Z=uI49Sy^9|JZ;8N9 zKZ$KyzH6|LIB27ailD%hTd(Fzt!Hx8sVC7y1Xvsi>gzCAjN}E$09hadWR*QYmjz;= z3dEpQMaw{fHx)b+}PJjY$v|B!zJey@s%AiPVLe^;Ov&n3FCgG3ei@;nwT zXyxm3&ZGqoSdzqJZ9O7U3bF>YPJ&D4YTtD?=#m5%uMmzU-TgfVjlmVs$l!297TPtM&n9 zNct4LT zFXJS_N+GbW)+<;b2R;IgASwVY1VTgT?17D4(Afi9x-}SDV05D}YB-D^4r5yYt55=Z zAsGO1U}|$d37`?sK}8+T)L}Pmg70?Fep3fa4hHz!f!+o9#tT@w=z}aQ)RV*|Y^ISw zV>uuzV89m?4@AR&dMH<5)r;`UXW_9IVC_2C*?ZuJH8#N1i7@9hnE!sb=yYhR2TG81 zhgo8Z5?sHcVGw}0AI%hS#Q~zFE4lfb|HqZS6_dW-NeH$a z1nlLi#uq)`308LaJ(bvWec#MT8{h6$$a|uOSpGT6S1ohnR>z-(FEX{y0>YD zU%UWcUIuS=hI>RdDB<68%oGN|8c5RcV{{Yz^T*)I^MHPErPN~vQs5qZ5w$szZM|f^ zu!6t(-*w#g(*^qmF?)bs4CWsNU&wIc!v*%K(lf<&2g4yUgoFJK!3VSQ}{eCL! z6+UuL0Gvs!dn5rv7}Ugv=XJaV7vBZjdcmg8mU7S&0r6ONl3X^4*2 zrABZuI7Mv+eR>1Mb01@dDw}I@x&hDyhzNSq3~rd!A`SG2w@FG|RiFsYg*p4Rhf~1j z=m3TQIAh?+t#HkQ!R?(3dJ>0oY=Y1uepZV#+%$t5L{ME2j4Y&99wCf>>>PPz=12<8 zgVpdprcQANYz`jW3?F_pJTUQxu%JCmQnUE&=fLNufvW^}rySyWC#W^BW(URdpV1jz z$|e!SJUSO6f?hY7>u2Vvrw?Zp%_T!Eqv3^5^3Q33D3D12u%Y%B#=I?|+d zu%ZMe9(3^4=R=&X9w@WR)g2fVy|_-U0P^!XNQ_hXXbbbt&d6B$#ha3tS`6dku;km+ z4Y#Zdqw0^6`3$;!$yg zIWZ{sa&YInHbBvZlaDdX!0z|%_tvNLt_%zclm&5sN9!#*KQAAzou5D1YO9vKxUx6I zA<9q>HW4a)!hdWHP>&V2-dmgo8%!O`ID|t#W3j z5-zBPXG#di`_5b{Yi;}iUpMJNR`b_WTFQZ-H)p) z=;2In9$^uY{butOR0TnGb7XV8NfN9D@&LRVJ@{TNb_HNwm0T{%o3Fpc%g;Z-9}DWqC!Wq}XPip8T*0Jh6tLE6e_y{ow(^NN(S!-z zzs>lb^}`DJDb-5Vi-Zt7lu~16OdY}cwK>Y=%F)AfP@-6CIc55B6l{@Po!b>+s;bZC zvZmbAvu?tK?%zfmH&Plgy1!%0*zuD+#-l?D0A9(}6&O2V60g1bTF4V0gQQl|Ys}a& zG&D9-tyYmemrvfSk?x-Uj*S~B8G!Ab+wU)xN+M?8<%LTUrcRxLF~_!h3-G>aC+x#Z z0AfT+rIKv#+=-2&T2QH3CXqJG$;suOcEo)QOc+k|Fk%ITO}$ z=jV6OTpz4`tl$o>WW`$H)@ufsGx06TNkln`FrsPATfBOet{q+X0HxN})+nuTs1^!^ z@pD?|yfSh`OQBR&Vjs!$k=wa+#!H+yvXjO<-0`4r!+$E#kkaYVl$su2{)q6cE0mr= zSkpO%2VOau*SklNv$59YgdJTy#V0zRm{BYiH;IIfFW=VIW=o~g#?CFh zoymt!Toau;aUIqu#foLHPx;#mlqc7~{m+EFS#lIJDp6XnpK~I79>XoZrVxT#X4ECZvNfU%6B~s`wLz9I&KLHUbLlvta^!o8}HRCtr2Z+Lm6U|J)Cn1`1?MA z9jLPQ)`#?SZ*0IcV9LXE&F}FUXpBt#M_P$SGm+V!X*=I(!Eyq2wm}upT z+;`g}+TLFxQ%E%NF65ER#dO7)fL5TV1fQNJ{OE(iUyKj`)#}$aRr+8Dpw_WvCGC<* zdt)yc4R_BIzIcjIiXkpR#)bbYY0s!nppau&$R+U1Hu(7lrDF&5?^PB#&;ZnoHB{;h%pMLO zn`7UuwY60 zE7)u~Oz~<3s#OUs%eAh(N+{yLxl?#%r}Au%vbGGJ$vYF>a0~Ct!TW~^=L{334-ds; zmkpXqLjak}DVc1zUp0?5W^pA?boX=IeCwg~hVBjgGiR{)sGhw%Ft65wv}OHqU?0gJ;ban28LM}7YWDJ!SFgI%0ne1xJZdIkj+6h z8=CdB5L%*h1qH!16|fs)xa-%n|M-PN?*iTj_y>;X@@r1z`?JQ;+}R^ZIl)?oNIK6e z#W3$HB?*Mruc39hDr9`9gqpobt~Pg#f!Kh{DQdws2-ZM0ADFqWke*);i9z!j+^80f zp4vq34`1ZEhu=DU{ocEz)?Av+a#H&_+;-tOY0XJzlPZ!_Lyt-|?e5XRl<~`g z*8_JX34xuBfXQp>%7F#5b&9c&$$`zFVsM2PqEZ6C+N>Sz&v0$E%F738emx}60CVRO zb*v&$kOk9ObjcX``dLl1I1fAfNW5Z`iil6QA7~{c>9iU^ySE8hZ3PAQa9H3N#U6M~}rzz4(kE&5ddu@cRmVtNZk>e|h;2xLaqa?7c7Rb0_fG z8F{&UN=7D)FyQ-@ib7Hn?>!oO#D~1P>^-LewSq$)%Ho^M_lejz~oElE&uBJ}grUNJIjel@z)%s@;w^ z*280~OZq@Z_wF5d%R>M5(b<8wmzc$KG2iMS8Pb7Nm=ba7DLH11w{m)uFtY#?vcj;e zkp{1*5v1yMpsMUF!p1&j%_gU-e^=q@-zU5GS{i|GguPdu!_e>Z|ACbcA8b8|0ssI2 M07*qoM6N<$f?>T{ZU6uP literal 0 HcmV?d00001 diff --git a/source/Fenix8V3View.mc b/source/Fenix8V3View.mc index 4350119..eb041f2 100644 --- a/source/Fenix8V3View.mc +++ b/source/Fenix8V3View.mc @@ -281,6 +281,12 @@ class Fenix8V3View extends WatchUi.WatchFace { // 低功耗画面。相比正常画面砍掉的东西: // - 日期带底色(整条亮橙色横幅是最费电的元素,直接不画) // - 未点亮的刻度(只画有数据的那部分,且压暗到 45%) + // + // 关于点亮量:实测普通模式的平均亮度(AMOLED 功耗的合理代理)是 14.1%, + // 常亮是 2.9%,约 1/5。曾经试过连进度环也砍掉,平均亮度只从 3.01% 降到 + // 2.91% —— 0.1 个百分点换掉一个可见特征,不划算,所以环留着。 + // (常按「点亮像素数」算会得到 10% 左右,但那个口径把每个抗锯齿边缘像素 + // 都算成「亮」,跟实际功耗对不上。) // - 四个数据位、电池(全部隐藏) // 保留的时间和日期压暗到 55%。 // diff --git a/source/Fields.mc b/source/Fields.mc index b7a4079..62235ac 100644 --- a/source/Fields.mc +++ b/source/Fields.mc @@ -244,8 +244,18 @@ module Fields { return null; } + // 本项目已经被这个 API 家族坑过三次(Properties.getValue、 + // deleteTemporalEvent、Complication.unit),一律按「声明了权限也可能抛」 + // 来防。取不到就当作没有用户档案,相关字段显示 "--"。 function profile() as UserProfile.Profile? { - if (!mGotProf) { mProf = UserProfile.getProfile(); mGotProf = true; } + if (!mGotProf) { + try { + mProf = UserProfile.getProfile(); + } catch (e) { + mProf = null; + } + mGotProf = true; + } return mProf; } diff --git a/tools/gen_devices.py b/tools/gen_devices.py new file mode 100644 index 0000000..9d3d749 --- /dev/null +++ b/tools/gen_devices.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""扫描本机安装的 Connect IQ 设备,重写 manifest 的机型列表和 monkey.jungle。 + +筛选条件(三条都要满足): + 1. 支持 watchFace 类型 + 2. Connect IQ >= 4.2 —— Toybox.Complications 是这个版本引入的, + 本表盘的日历/训练状态/第三方 App 字段依赖它 + 3. 圆屏(deviceFamily 以 round- 开头) + + python3 tools/gen_devices.py + +改写两处: + manifest.xml 整块 + monkey.jungle 每台设备的 resourcePath(图标按屏宽、启动图标按各自要求的尺寸) + +⚠️ 结果取决于本机 SDK 里**装了哪些设备**。如果某台机器上少装了几款, +跑一遍会把它们从 manifest 里删掉。所以脚本会打印找到的数量, +提交前确认一下数字对不对(当前应为 55 款)。 + +图标资源的尺寸集合由本脚本算出后交给 tools/gen_icons.py 使用, +两者的 WIDTHS / LAUNCHER 必须一致,所以改完这里要跟着跑一遍 gen_icons.py。 +""" +import json +import os +import re +from collections import defaultdict + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +DEV = os.path.expanduser( + "~/Library/Application Support/Garmin/ConnectIQ/Devices") + + +def eligible(): + """返回 [(设备id, 屏宽, 启动图标尺寸, 是否支持alpha), ...],按 id 排序。""" + out = [] + if not os.path.isdir(DEV): + raise SystemExit("找不到设备目录:%s" % DEV) + for d in sorted(os.listdir(DEV)): + p = os.path.join(DEV, d, "compiler.json") + if not os.path.exists(p): + continue + try: + c = json.load(open(p)) + except Exception: + continue + types = {t["type"] for t in c.get("appTypes", []) if isinstance(t, dict)} + if "watchFace" not in types: + continue + if not str(c.get("deviceFamily", "")).startswith("round"): + continue + vers = [str(pn.get("connectIQVersion", "0")) + for pn in c.get("partNumbers", [])] + if not any(_ciq_ok(v) for v in vers): + continue + li = c.get("launcherIcon", {}) + out.append((d, c["resolution"]["width"], li.get("width"), + bool(c.get("alphaBlendingSupport")))) + return out + + +def _ciq_ok(v): + try: + a = [int(x) for x in v.split(".")[:2]] + return a[0] > 4 or (a[0] == 4 and a[1] >= 2) + except Exception: + return False + + +def write_manifest(devs): + p = os.path.join(ROOT, "manifest.xml") + s = open(p).read() + body = "\n".join(' ' % d for d, _, _, _ in devs) + block = " \n%s\n " % body + s = re.sub(r" .*?", block, s, flags=re.S) + open(p, "w").write(s) + print("manifest.xml: 写入 %d 款机型" % len(devs)) + + +def write_jungle(devs): + L = ["project.manifest = manifest.xml", "", + "base.sourcePath = source", "", + "# resources-chn 是简体中文文案;目录名的 -chn 后缀就是语言标记。", + "base.resourcePath = resources;resources-chn", "", + "# 下面每台设备再挂两棵资源树,都由 tools/gen_icons.py 生成:", + "# resources-icons-<屏宽> 按该屏幕像素网格光栅化的日出/日落/天气图标", + "# resources-launcher-<尺寸> 该机型要求尺寸的启动图标", + "# 这一整段由 tools/gen_devices.py 生成,不要手改。", + ""] + for d, w, li, _ in devs: + L.append("%s.resourcePath = $(base.resourcePath)" + ";resources-icons-%d;resources-launcher-%d" % (d, w, li)) + L += ["", "base.excludeAnnotations = release"] + open(os.path.join(ROOT, "monkey.jungle"), "w").write("\n".join(L) + "\n") + print("monkey.jungle: 写入 %d 条资源路径" % len(devs)) + + +def main(): + devs = eligible() + if not devs: + raise SystemExit("一款符合条件的机型都没找到,请检查 SDK 设备是否已安装") + widths = defaultdict(set) + for _, w, _, alpha in devs: + widths[w].add(alpha) + print("符合条件的机型:%d 款" % len(devs)) + print("需要的图标屏宽:%s" % sorted(widths)) + print("需要的启动图标尺寸:%s" % sorted({li for _, _, li, _ in devs})) + for w in sorted(widths): + if len(widths[w]) > 1: + print(" ⚠️ %dpx 同时存在支持/不支持 alpha 的机型," + "图标按硬边生成" % w) + write_manifest(devs) + write_jungle(devs) + print("\n接着跑:python3 tools/gen_icons.py") + + +if __name__ == "__main__": + main() diff --git a/tools/gen_icons.py b/tools/gen_icons.py index 97b8fd0..9e343eb 100644 --- a/tools/gen_icons.py +++ b/tools/gen_icons.py @@ -41,8 +41,11 @@ ICONS = { "weather": ("weather-icon", 343.55, 89.5, False), } -# screen width -> whether the panel can blend alpha (MIP cannot) -WIDTHS = {240: False, 260: False, 280: False, 390: True, 416: True, 454: True} +# 屏宽 -> 该尺寸下的屏幕能否做 alpha 混合(MIP 屏不能)。 +# 这张表和启动图标尺寸表都由 tools/gen_devices.py 扫描出的机型集合决定, +# 改完设备列表要跟着更新这里 —— 少一个尺寸就会有机型编译不过。 +WIDTHS = {218: False, 240: False, 260: False, 280: False, + 360: True, 390: True, 416: True, 454: True} SS = 8 # supersampling factor @@ -118,8 +121,8 @@ def render(paths, size, cx, cy, blend): return img -# Launcher icon sizes the target devices ask for. -LAUNCHER = [40, 56, 60, 65, 70] +# 各机型要求的启动图标尺寸(Garmin 每款表都有自己的规定值,不能用缩放糊弄)。 +LAUNCHER = [38, 40, 54, 56, 60, 61, 65, 70] def launcher(size): @@ -160,6 +163,46 @@ def launcher(size): return img.resize((size, size), Image.LANCZOS) +def tiny_weather(size): + """给 16px 以下的屏幕专画的天气标记。 + + 设计稿的天气图标是「太阳被云挡住一半」,靠一道暗缝把两者分开。那道缝在 + 500 画布上只有 1.7px,到 218px 屏(图标 14×14)连一个物理像素都占不到, + 光栅化出来就是一坨没有结构的橙块。 + + 所以这个尺寸不再走设计稿路径,改用按目标像素直接摆的简化标记: + 右上一个太阳圆盘,左下一朵云,中间强制留 1 个像素的空隙。 + 形不如原图,但至少还认得出是「天气」。 + """ + from PIL import ImageDraw + ss = 8 + n = size * ss + img = Image.new("L", (n, n), 0) + d = ImageDraw.Draw(img) + u = n / 14.0 # 以 14 格为基准摆放,任何尺寸都等比 + + def disc(cx, cy, r, fill): + d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=fill) + + # 太阳 + 四道光芒(光芒画成小方块,比线段在低分辨率下更容易留下来) + disc(9.2 * u, 4.6 * u, 2.7 * u, 255) + for dx, dy in ((0, -4.4), (4.3, 0), (-3.2, -3.2), (3.2, -3.2)): + d.rectangle([9.2 * u + dx * u - 0.55 * u, 4.6 * u + dy * u - 0.55 * u, + 9.2 * u + dx * u + 0.55 * u, 4.6 * u + dy * u + 0.55 * u], + fill=255) + # 先用黑色把云的轮廓“撑大”一圈,留出与太阳之间的缝,再画回本体 + for grow, fill in ((1.0 * u, 0), (0.0, 255)): + disc(5.0 * u, 8.6 * u, 3.3 * u + grow, fill) + disc(8.6 * u, 9.6 * u, 2.4 * u + grow, fill) + d.rectangle([1.7 * u - grow, 8.8 * u - grow, 11.0 * u + grow, 12.2 * u], + fill=fill) + small = img.resize((size, size), Image.LANCZOS) + small = small.point(lambda v: 255 if v >= 110 else 0) + out = Image.new("RGB", (size, size), (255, 255, 255)).convert("RGBA") + out.putalpha(small) + return out + + def main(): svg = open(SRC).read() for size in LAUNCHER: @@ -182,7 +225,11 @@ def main(): if key not in cache: p = subpaths(svg, pid) cache[key] = flip_chevron(p) if flip else p - img = render(cache[key], size, cx, cy, blend) + # 天气图标在极小尺寸下用专门的简化画法,日出/日落还撑得住。 + if name == "weather" and size < 16: + img = tiny_weather(size) + else: + img = render(cache[key], size, cx, cy, blend) img.save(os.path.join(outdir, "%s.png" % name)) names.append(name) with open(os.path.join(outdir, "drawables.xml"), "w") as f: