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:
@@ -3,24 +3,34 @@ import Toybox.Graphics;
|
||||
import Toybox.WatchUi;
|
||||
import Layout;
|
||||
|
||||
// The glyphs that sit above the two top data slots, plus the battery and the
|
||||
// bluetooth rune.
|
||||
// ============================================================================
|
||||
// 图标
|
||||
// ----------------------------------------------------------------------------
|
||||
// 顶部两个数据位上方的图标,加上电池和蓝牙符号。
|
||||
//
|
||||
// The comps contain three icons -- sunrise, weather, battery. The first two
|
||||
// are traced outlines whose finest features are two or three design pixels
|
||||
// across; rebuilding them from circles and lines looked right at 454 px and
|
||||
// turned to mush at 280. They ship instead as bitmaps rasterised from the
|
||||
// design paths once per screen width (tools/gen_icons.py) and are tinted with
|
||||
// the theme's accent at draw time.
|
||||
// 设计稿里只有三个图标:日出、天气、电池。
|
||||
//
|
||||
// Everything else -- steps, calories, stairs, heart -- has no counterpart in
|
||||
// the comps, so those stay as primitives sized off the same design canvas.
|
||||
// 前两个是位图描出来的矢量轮廓,最细的特征只有 2–3 个设计像素宽。一开始我用
|
||||
// 圆、线段、多边形去「重画」它们,在 454px 上看着还行,一到 280px(Enduro 3)
|
||||
// 就糊成一团 —— 因为 s=0.56 时那些细节不足 1 个物理像素。
|
||||
// 中间还试过做一版「低细节」图元,结果更糟:日出变成三条横杠。
|
||||
//
|
||||
// 现在的做法是:用 tools/gen_icons.py 把**设计稿的路径**按每种屏宽各光栅化一
|
||||
// 张位图,运行时用 drawBitmap2(:tintColor) 按主题色染色。这样每台设备拿到的
|
||||
// 都是为它的像素网格专门渲染的图。
|
||||
//
|
||||
// 其余图标(步数、卡路里、楼梯、心形…)设计稿里根本没有,所以保持图元绘制,
|
||||
// 尺寸同样基于 500 设计画布。
|
||||
// ============================================================================
|
||||
module Icons {
|
||||
|
||||
// 三张位图在 onLayout 时一次性加载(loadResource 开销不小,不能放 onUpdate)
|
||||
var mSunrise = null;
|
||||
var mSunset = null;
|
||||
var mWeather = null;
|
||||
|
||||
// 加载图标位图。资源按屏宽分目录,monkey.jungle 里为每台设备指定了
|
||||
// resources-icons-<屏宽>,所以这里拿到的自动就是对的尺寸。
|
||||
function load() as Void {
|
||||
mSunrise = WatchUi.loadResource(Rez.Drawables.IconSunrise);
|
||||
mSunset = WatchUi.loadResource(Rez.Drawables.IconSunset);
|
||||
@@ -29,8 +39,9 @@ module Icons {
|
||||
|
||||
function ready() as Boolean { return mSunrise != null; }
|
||||
|
||||
// drawBitmap2 refuses a palettised source, which is why the generated
|
||||
// drawables carry packingFormat="png" to keep their alpha channel.
|
||||
// ⚠️ drawBitmap2 的 :tintColor 拒绝**调色板化**的位图,运行时会抛
|
||||
// "Source must not use a color palette"。所以生成的 drawables.xml 里每个
|
||||
// bitmap 都带 packingFormat="png",保住 alpha 通道不被压成调色板。
|
||||
function blit(dc as Graphics.Dc, bmp, cxDesign as Numeric, col as Number) as Void {
|
||||
if (bmp == null) { return; }
|
||||
dc.drawBitmap2(Layout.px(cxDesign) - bmp.getWidth() / 2,
|
||||
@@ -38,8 +49,9 @@ module Icons {
|
||||
bmp, {:tintColor => col});
|
||||
}
|
||||
|
||||
// Outline, charge bar and terminal. The bar tracks the real level, the
|
||||
// way the comp draws it at 40%.
|
||||
// 电池:外框 + 内部电量条 + 右侧触点。
|
||||
// 电量条长度跟随真实电量(设计稿画的是 40% 的状态)。
|
||||
// 画法是「先整块填充,再用黑色掏空内部,最后按比例填回电量条」。
|
||||
function battery(dc as Graphics.Dc, x as Numeric, cyDesign as Numeric,
|
||||
k as Float, col as Number, level as Float) as Void {
|
||||
var x0 = Layout.px(x);
|
||||
@@ -62,7 +74,7 @@ module Icons {
|
||||
}
|
||||
}
|
||||
|
||||
// The standard bluetooth rune, drawn as one polyline.
|
||||
// 标准蓝牙符号,用一条折线一笔画成(六段,中间的竖线会被走两次)。
|
||||
function bluetooth(dc as Graphics.Dc, col as Number) as Void {
|
||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
||||
dc.setPenWidth(Layout.pen(3.2));
|
||||
@@ -79,8 +91,9 @@ module Icons {
|
||||
Layout.line(dc, r, qt, l, qb);
|
||||
}
|
||||
|
||||
// Which glyph, if any, belongs above a given field id. Fields with no
|
||||
// natural icon -- dates, week numbers, custom text -- read fine bare.
|
||||
// 按字段编号决定上方画哪个图标。
|
||||
// 日期、周数、自定义文字这类字段没有合适的图标,就不画 —— 光有数值也读得懂,
|
||||
// 硬配一个反而干扰。
|
||||
function forField(dc as Graphics.Dc, id as Number, xDesign as Numeric,
|
||||
col as Number, level as Float) as Void {
|
||||
if (id == 712 || id == 700) {
|
||||
@@ -105,25 +118,25 @@ module Icons {
|
||||
var cyd = Layout.ICON_CY;
|
||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
||||
dc.setPenWidth(Layout.pen(3.0));
|
||||
if (id == 2 || id == 603) { // calories: flame
|
||||
if (id == 2 || id == 603) { // 卡路里:火焰
|
||||
dc.fillPolygon([[Layout.px(xDesign), Layout.px(cyd - r)],
|
||||
[Layout.px(xDesign + r * 0.72), Layout.px(cyd + r * 0.25)],
|
||||
[Layout.px(xDesign + r * 0.40), Layout.px(cyd + r * 0.85)],
|
||||
[Layout.px(xDesign - r * 0.40), Layout.px(cyd + r * 0.85)],
|
||||
[Layout.px(xDesign - r * 0.72), Layout.px(cyd + r * 0.25)]]);
|
||||
} else if (id == 1 || id == 14 || id == 707) { // steps: two prints
|
||||
} else if (id == 1 || id == 14 || id == 707) { // 步数:两个脚印
|
||||
dc.fillRoundedRectangle(Layout.px(xDesign - r * 0.85), Layout.px(cyd - r * 0.75),
|
||||
Layout.px(r * 0.62), Layout.px(r * 1.15),
|
||||
Layout.px(r * 0.3));
|
||||
dc.fillRoundedRectangle(Layout.px(xDesign + r * 0.22), Layout.px(cyd - r * 0.35),
|
||||
Layout.px(r * 0.62), Layout.px(r * 1.15),
|
||||
Layout.px(r * 0.3));
|
||||
} else if (id == 3 || id == 9 || id == 615) { // distance: arrow
|
||||
} else if (id == 3 || id == 9 || id == 615) { // 距离/海拔:箭头
|
||||
dc.fillPolygon([[Layout.px(xDesign), Layout.px(cyd - r)],
|
||||
[Layout.px(xDesign + r * 0.75), Layout.px(cyd + r * 0.8)],
|
||||
[Layout.px(xDesign), Layout.px(cyd + r * 0.35)],
|
||||
[Layout.px(xDesign - r * 0.75), Layout.px(cyd + r * 0.8)]]);
|
||||
} else if (id == 618 || id == 276 || id == 623) { // heart
|
||||
} else if (id == 618 || id == 276 || id == 623) { // 心率/静息/体能:心形
|
||||
dc.fillCircle(Layout.px(xDesign - r * 0.38), Layout.px(cyd - r * 0.28),
|
||||
Layout.px(r * 0.44));
|
||||
dc.fillCircle(Layout.px(xDesign + r * 0.38), Layout.px(cyd - r * 0.28),
|
||||
@@ -131,7 +144,7 @@ module Icons {
|
||||
dc.fillPolygon([[Layout.px(xDesign - r * 0.78), Layout.px(cyd - r * 0.12)],
|
||||
[Layout.px(xDesign + r * 0.78), Layout.px(cyd - r * 0.12)],
|
||||
[Layout.px(xDesign), Layout.px(cyd + r * 0.85)]]);
|
||||
} else if (id == 4 || id == 5 || id == 6) { // floors: stairs
|
||||
} else if (id == 4 || id == 5 || id == 6) { // 楼层:台阶
|
||||
for (var i = 0; i < 3; i++) {
|
||||
dc.fillRectangle(Layout.px(xDesign - r * 0.85 + r * 0.58 * i),
|
||||
Layout.px(cyd + r * 0.6 - r * 0.55 * i),
|
||||
|
||||
Reference in New Issue
Block a user