@olegkoval/agent-skills 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +3 -2
- package/.cursor-plugin/index.json +5 -0
- package/.grok-plugin/index.json +5 -0
- package/.kiro/steering/garmin-watchface.md +201 -0
- package/.kiro/steering/qodoloop.md +13 -1
- package/.windsurf/rules/garmin-watchface.md +200 -0
- package/.windsurf/rules/qodoloop.md +13 -1
- package/README.md +7 -1
- package/catalog/skills.json +28 -4
- package/collections/wearables.json +7 -0
- package/package.json +1 -1
- package/packages/marketing/viral-launch/adapters/claude/skills/viral-launch/references/external-skills.md +12 -0
- package/packages/marketing/viral-launch/adapters/claude/skills/viral-launch/references/launch-checklist.md +42 -0
- package/packages/marketing/viral-launch/adapters/cursor/skills/viral-launch/references/external-skills.md +12 -0
- package/packages/marketing/viral-launch/adapters/cursor/skills/viral-launch/references/launch-checklist.md +42 -0
- package/packages/marketing/viral-launch/adapters/grok/skills/viral-launch/references/external-skills.md +12 -0
- package/packages/marketing/viral-launch/adapters/grok/skills/viral-launch/references/launch-checklist.md +42 -0
- package/packages/software-development/qodoloop/SKILL.md +13 -1
- package/packages/software-development/qodoloop/adapters/claude/skills/qodoloop/SKILL.md +13 -1
- package/packages/software-development/qodoloop/adapters/claude/skills/qodoloop/references/graphql-queries.md +151 -0
- package/packages/software-development/qodoloop/adapters/cursor/skills/qodoloop/SKILL.md +13 -1
- package/packages/software-development/qodoloop/adapters/cursor/skills/qodoloop/references/graphql-queries.md +151 -0
- package/packages/software-development/qodoloop/adapters/grok/skills/qodoloop/SKILL.md +13 -1
- package/packages/software-development/qodoloop/adapters/grok/skills/qodoloop/references/graphql-queries.md +151 -0
- package/packages/software-development/qodoloop/adapters/kiro/steering/qodoloop.md +13 -1
- package/packages/software-development/qodoloop/adapters/windsurf/rules/qodoloop.md +13 -1
- package/packages/software-development/semantic-release-beta/adapters/claude/skills/semantic-release-beta/references/github-actions.md +16 -0
- package/packages/software-development/semantic-release-beta/adapters/claude/skills/semantic-release-beta/references/node-semantic-release.md +13 -0
- package/packages/software-development/semantic-release-beta/adapters/claude/skills/semantic-release-beta/references/preset.md +47 -0
- package/packages/software-development/semantic-release-beta/adapters/claude/skills/semantic-release-beta/references/validation.md +17 -0
- package/packages/software-development/semantic-release-beta/adapters/cursor/skills/semantic-release-beta/references/github-actions.md +16 -0
- package/packages/software-development/semantic-release-beta/adapters/cursor/skills/semantic-release-beta/references/node-semantic-release.md +13 -0
- package/packages/software-development/semantic-release-beta/adapters/cursor/skills/semantic-release-beta/references/preset.md +47 -0
- package/packages/software-development/semantic-release-beta/adapters/cursor/skills/semantic-release-beta/references/validation.md +17 -0
- package/packages/software-development/semantic-release-beta/adapters/grok/skills/semantic-release-beta/references/github-actions.md +16 -0
- package/packages/software-development/semantic-release-beta/adapters/grok/skills/semantic-release-beta/references/node-semantic-release.md +13 -0
- package/packages/software-development/semantic-release-beta/adapters/grok/skills/semantic-release-beta/references/preset.md +47 -0
- package/packages/software-development/semantic-release-beta/adapters/grok/skills/semantic-release-beta/references/validation.md +17 -0
- package/packages/wearables/garmin-watchface/SKILL.md +204 -0
- package/packages/wearables/garmin-watchface/adapters/claude/plugin.json +5 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/SKILL.md +205 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/references/devices.md +98 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/references/display.md +120 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/references/layout.md +224 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/references/simulator.md +146 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/references/store.md +151 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/references/testing.md +154 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/scripts/ciq-calibrate +184 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/scripts/ciq-capture +215 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/scripts/ciq-devices +177 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/scripts/ciq-doctor +85 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/templates/Makefile +72 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/templates/monkey.jungle +13 -0
- package/packages/wearables/garmin-watchface/adapters/claude/skills/garmin-watchface/templates/monkey.test.jungle +7 -0
- package/packages/wearables/garmin-watchface/adapters/codex/README.md +3 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/plugin.json +6 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/SKILL.md +205 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/references/devices.md +98 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/references/display.md +120 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/references/layout.md +224 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/references/simulator.md +146 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/references/store.md +151 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/references/testing.md +154 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/scripts/ciq-calibrate +184 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/scripts/ciq-capture +215 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/scripts/ciq-devices +177 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/scripts/ciq-doctor +85 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/templates/Makefile +72 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/templates/monkey.jungle +13 -0
- package/packages/wearables/garmin-watchface/adapters/cursor/skills/garmin-watchface/templates/monkey.test.jungle +7 -0
- package/packages/wearables/garmin-watchface/adapters/grok/plugin.json +6 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/SKILL.md +205 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/references/devices.md +98 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/references/display.md +120 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/references/layout.md +224 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/references/simulator.md +146 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/references/store.md +151 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/references/testing.md +154 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/scripts/ciq-calibrate +184 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/scripts/ciq-capture +215 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/scripts/ciq-devices +177 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/scripts/ciq-doctor +85 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/templates/Makefile +72 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/templates/monkey.jungle +13 -0
- package/packages/wearables/garmin-watchface/adapters/grok/skills/garmin-watchface/templates/monkey.test.jungle +7 -0
- package/packages/wearables/garmin-watchface/adapters/kiro/steering/garmin-watchface.md +201 -0
- package/packages/wearables/garmin-watchface/adapters/windsurf/rules/garmin-watchface.md +200 -0
- package/packages/wearables/garmin-watchface/references/devices.md +98 -0
- package/packages/wearables/garmin-watchface/references/display.md +120 -0
- package/packages/wearables/garmin-watchface/references/layout.md +224 -0
- package/packages/wearables/garmin-watchface/references/simulator.md +146 -0
- package/packages/wearables/garmin-watchface/references/store.md +151 -0
- package/packages/wearables/garmin-watchface/references/testing.md +154 -0
- package/packages/wearables/garmin-watchface/scripts/ciq-calibrate +184 -0
- package/packages/wearables/garmin-watchface/scripts/ciq-capture +215 -0
- package/packages/wearables/garmin-watchface/scripts/ciq-devices +177 -0
- package/packages/wearables/garmin-watchface/scripts/ciq-doctor +85 -0
- package/packages/wearables/garmin-watchface/templates/Makefile +72 -0
- package/packages/wearables/garmin-watchface/templates/monkey.jungle +13 -0
- package/packages/wearables/garmin-watchface/templates/monkey.test.jungle +7 -0
- package/scripts/build-adapters.sh +1 -1
- package/scripts/validate-catalog.sh +2 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: garmin-watchface
|
|
3
|
+
description: Build, test, screenshot and publish Garmin Connect IQ watch faces in Monkey C. Use when working on a Connect IQ watch face or app - creating one, fixing layout that clips or overlaps, capturing simulator screenshots, adding app settings, widening device support, or preparing a Connect IQ Store submission. Encodes the traps that silently produce a passing build and a broken face.
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Bash, Read, Write, Edit, Grep, Glob
|
|
6
|
+
compatibility: Codex, Claude Code, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Requires macOS, the Connect IQ SDK, a JDK, and Python 3 (Pillow for screenshot cropping).
|
|
7
|
+
metadata:
|
|
8
|
+
targets: [_source-only]
|
|
9
|
+
---
|
|
10
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
11
|
+
|
|
12
|
+
# Garmin Connect IQ watch faces
|
|
13
|
+
|
|
14
|
+
Monkey C gives you almost no safety net. The compiler is happy to build a face
|
|
15
|
+
that draws off the bottom of the screen, a test suite that contains no tests, and
|
|
16
|
+
a colour that is not the colour you get. Everything below is a failure mode that
|
|
17
|
+
looked like success first.
|
|
18
|
+
|
|
19
|
+
**Read `references/` files as needed — do not read them all up front.**
|
|
20
|
+
|
|
21
|
+
| File | When |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `references/display.md` | Choosing colours, anything about brightness or legibility |
|
|
24
|
+
| `references/layout.md` | Positioning anything; content clipped, overlapping, or off-screen |
|
|
25
|
+
| `references/testing.md` | Writing tests; a suspiciously clean test run |
|
|
26
|
+
| `references/simulator.md` | Screenshots, settings not applying, monkeydo hanging |
|
|
27
|
+
| `references/devices.md` | Adding device support, launcher icons, API levels |
|
|
28
|
+
| `references/store.md` | Publishing, listing copy, IP questions |
|
|
29
|
+
|
|
30
|
+
## Tools
|
|
31
|
+
|
|
32
|
+
Run these rather than reinventing them. All are standalone.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
<skill-dir>/scripts/ciq-doctor # toolchain check: SDK, JDK, key, devices
|
|
36
|
+
<skill-dir>/scripts/ciq-devices # survey installed devices by resolution
|
|
37
|
+
<skill-dir>/scripts/ciq-devices --same-as fenix6pro # products you can add with no code change
|
|
38
|
+
<skill-dir>/scripts/ciq-capture out.png # calibrated simulator screenshot
|
|
39
|
+
<skill-dir>/scripts/ciq-capture out.png --face --size 260 # cropped + masked to the round display
|
|
40
|
+
<skill-dir>/scripts/ciq-calibrate # re-derive the display rect if capture looks wrong
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`<skill-dir>/scripts/ciq-capture` exists because `screencapture -R` grabs a screen *region*, not
|
|
44
|
+
a window: without a frontmost check it silently photographs whatever is on top,
|
|
45
|
+
and the window moves between simulator restarts. Both failure modes produce a
|
|
46
|
+
plausible PNG of the wrong thing.
|
|
47
|
+
|
|
48
|
+
## The five things that will bite you
|
|
49
|
+
|
|
50
|
+
### 1. `make test` can compile zero tests and report success
|
|
51
|
+
|
|
52
|
+
If `monkey.jungle` sets `base.excludeAnnotations = test`, every `(:test)`
|
|
53
|
+
function is stripped from **every** target, including the unit-test build. You
|
|
54
|
+
get `BUILD SUCCESSFUL` and a green run with nothing compiled — even when the test
|
|
55
|
+
files reference symbols that no longer exist.
|
|
56
|
+
|
|
57
|
+
Fix: a second jungle passed as an additional `-f`. See
|
|
58
|
+
`<skill-dir>/templates/monkey.test.jungle` and the `test` target in `<skill-dir>/templates/Makefile`.
|
|
59
|
+
|
|
60
|
+
**Prove it before you trust it.** Put a deliberately unresolvable symbol in one
|
|
61
|
+
test and confirm the build fails:
|
|
62
|
+
|
|
63
|
+
```monkeyc
|
|
64
|
+
return ThisDoesNotExist.definitelyNotAFunction();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Never hardcode a Y coordinate or assume a font height
|
|
68
|
+
|
|
69
|
+
`dc.getFontHeight()` is the only source of truth, and the numbers are larger than
|
|
70
|
+
they look. Measured on fenix 6 Pro:
|
|
71
|
+
|
|
72
|
+
| Font | Height |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `FONT_XTINY` | 19 |
|
|
75
|
+
| `FONT_TINY` | 29 |
|
|
76
|
+
| `FONT_SMALL` | 32 |
|
|
77
|
+
| `FONT_NUMBER_MEDIUM` | 74 |
|
|
78
|
+
|
|
79
|
+
Content drawn past the screen height is simply invisible — no error, no warning,
|
|
80
|
+
no clipping indicator. A two-line `FONT_NUMBER_MEDIUM` block is 148px of a 260px
|
|
81
|
+
face.
|
|
82
|
+
|
|
83
|
+
Derive every Y at draw time, and **clamp** so a bottom row cannot be pushed off:
|
|
84
|
+
|
|
85
|
+
```monkeyc
|
|
86
|
+
function clampRuleY(derived as Number, footerH as Number) as Number {
|
|
87
|
+
var reserved = (footerH * 2) + GAP_BELOW_RULE + GAP_BETWEEN_ROWS;
|
|
88
|
+
var maxRuleY = SCREEN - BOTTOM_MARGIN - reserved;
|
|
89
|
+
return derived > maxRuleY ? maxRuleY : derived;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
See `references/layout.md`.
|
|
94
|
+
|
|
95
|
+
### 3. The screen is round; your layout is not
|
|
96
|
+
|
|
97
|
+
Check content against the bezel curve, not the bounding square. A rectangle
|
|
98
|
+
inscribed in a circle fails at its **corners** long before its sides.
|
|
99
|
+
|
|
100
|
+
```monkeyc
|
|
101
|
+
function halfWidthAt(y as Number) as Number {
|
|
102
|
+
var dy = y - CENTER;
|
|
103
|
+
var inside = (CENTER * CENTER) - (dy * dy);
|
|
104
|
+
return inside <= 0 ? 0 : Math.sqrt(inside).toNumber();
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 4. Colours are quantised, and brightness is reflectance
|
|
109
|
+
|
|
110
|
+
Many devices snap to a 4×4×4 lattice: `0x00 / 0x55 / 0xAA / 0xFF` per channel. A
|
|
111
|
+
colour not already on it is not the colour that ships.
|
|
112
|
+
|
|
113
|
+
More important: MIP screens are **transflective with no backlight**. A colour's
|
|
114
|
+
brightness *is* how much ambient light it returns. Grey reflects about two-thirds
|
|
115
|
+
of what white does. Choosing a photographically accurate grey for a large area
|
|
116
|
+
makes the whole face read as dim on the wrist while looking fine in the
|
|
117
|
+
simulator's backlit LCD.
|
|
118
|
+
|
|
119
|
+
Corollary: a saturated colour on a glyph reflects only its own channel, making
|
|
120
|
+
the thing you want to read the *dimmest* thing on screen.
|
|
121
|
+
|
|
122
|
+
See `references/display.md`.
|
|
123
|
+
|
|
124
|
+
### 5. The simulator lies about settings
|
|
125
|
+
|
|
126
|
+
It persists app settings and **ignores changed defaults in `properties.xml`**
|
|
127
|
+
until you do File → Reset All App Data. A property will keep reading its old
|
|
128
|
+
value while the XML plainly says otherwise. This looks exactly like broken
|
|
129
|
+
settings code.
|
|
130
|
+
|
|
131
|
+
Reset also drops the loaded device, so relaunch and re-push afterwards.
|
|
132
|
+
|
|
133
|
+
See `references/simulator.md`.
|
|
134
|
+
|
|
135
|
+
## Workflow
|
|
136
|
+
|
|
137
|
+
### Starting a face
|
|
138
|
+
|
|
139
|
+
1. `<skill-dir>/scripts/ciq-doctor` — confirm SDK, JDK, developer key, target device installed.
|
|
140
|
+
2. Copy `<skill-dir>/templates/Makefile`, `<skill-dir>/templates/monkey.jungle`, `<skill-dir>/templates/monkey.test.jungle`.
|
|
141
|
+
3. Generate a fresh app id: `python3 -c "import uuid;print(uuid.uuid4().hex)"`.
|
|
142
|
+
4. Split source by responsibility. This structure has held up well:
|
|
143
|
+
|
|
144
|
+
| Module | Holds |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `Palette.mc` | Colours only, all lattice-exact |
|
|
147
|
+
| `Layout.mc` | Geometry, derivations, bezel maths — no drawing |
|
|
148
|
+
| `<Widget>.mc` | Drawing primitives (rows, bars, segments) |
|
|
149
|
+
| `Fields.mc` | Formatting and thresholds — pure, easy to test |
|
|
150
|
+
| `Sensors.mc` | Sensor reads with null handling |
|
|
151
|
+
| `<Name>View.mc` | Composition only |
|
|
152
|
+
|
|
153
|
+
Keeping `Layout` free of `Dc` calls is what makes geometry unit-testable.
|
|
154
|
+
|
|
155
|
+
### Every change
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
make build && make test && make lint
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`make lint` is level 3 and catches real problems the level 2 build allows.
|
|
162
|
+
|
|
163
|
+
### Before committing
|
|
164
|
+
|
|
165
|
+
Capture and *look at it*. Layout bugs are invisible in a passing test run:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
make build && make sim
|
|
169
|
+
<skill-dir>/scripts/ciq-capture /tmp/face.png --face --size 260
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Then Read the PNG. Every layout bug in this skill's history was found by looking,
|
|
173
|
+
not by testing.
|
|
174
|
+
|
|
175
|
+
## Monkey C gotchas
|
|
176
|
+
|
|
177
|
+
- Module-level functions take **no** visibility modifier. `private` and `hidden`
|
|
178
|
+
are class-only; at module scope they are syntax errors.
|
|
179
|
+
- `dc.fillPolygon` wants `Array<[Numeric, Numeric]>`. Cast the literal:
|
|
180
|
+
`[[x, y], ...] as Array<[Numeric, Numeric]>`.
|
|
181
|
+
- `getInitialView()` returns
|
|
182
|
+
`[WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates]`.
|
|
183
|
+
- `Application.Properties.getValue()` **throws** when a key is absent, which
|
|
184
|
+
happens on a fresh install before settings have ever been written. Wrap every
|
|
185
|
+
read with a default.
|
|
186
|
+
- Integer division everywhere. Centring an odd width leaves a 1px skew — write
|
|
187
|
+
tests that allow it rather than forcing constants to stay even.
|
|
188
|
+
|
|
189
|
+
## Reference watches and homages
|
|
190
|
+
|
|
191
|
+
If reproducing a real object, get these right before anything else, because they
|
|
192
|
+
are what makes it recognisable:
|
|
193
|
+
|
|
194
|
+
- **Display polarity.** A 1970s LCD is *positive*: dark segments on a pale panel,
|
|
195
|
+
the inverse of every modern face. Getting this backwards turns a crystal into a
|
|
196
|
+
screen.
|
|
197
|
+
- **Proportion.** Bezels on vintage digitals are thick; the window is smaller
|
|
198
|
+
than memory suggests.
|
|
199
|
+
- **Where the legends sit** — maker's name on the bezel, not the glass.
|
|
200
|
+
|
|
201
|
+
No system font is a segment display. If you need one, draw it: see
|
|
202
|
+
`references/layout.md` for a working seven-segment renderer, including the ghost
|
|
203
|
+
(unlit) segments that are most of what sells the effect.
|
|
204
|
+
|
|
205
|
+
**Do not print a real brand on the face.** See `references/store.md`.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Device support
|
|
2
|
+
|
|
3
|
+
## Products, devices, and what "5 out of 5 built" means
|
|
4
|
+
|
|
5
|
+
A `<iq:product>` in the manifest often covers several marketing variants that
|
|
6
|
+
share hardware. Declaring `fenix6pro` alone produces `5 OUT OF 5 DEVICES BUILT`
|
|
7
|
+
— fenix 6 Pro, 6 Sapphire, 6 Pro Solar, 6 Pro Dual Power, quatix 6. That is one
|
|
8
|
+
layout, not five. Do not read the count as breadth.
|
|
9
|
+
|
|
10
|
+
## Widening cheaply
|
|
11
|
+
|
|
12
|
+
A face written against one resolution ports for free to every device sharing it —
|
|
13
|
+
a manifest edit, no code change.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bin/ciq-devices --same-as fenix6pro
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That prints the `<iq:product>` lines to paste, flags devices below your
|
|
20
|
+
`minApiLevel`, and warns when launcher icon sizes differ.
|
|
21
|
+
|
|
22
|
+
Representative clusters (installed SDK, mid-2026):
|
|
23
|
+
|
|
24
|
+
| Resolution | Devices |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| 240x240 | 38 |
|
|
27
|
+
| 390x390 | 22 |
|
|
28
|
+
| 260x260 | 13 |
|
|
29
|
+
| 454x454 | 12 |
|
|
30
|
+
| 218x218 | 11 |
|
|
31
|
+
| 240x400 | 9 (rectangular) |
|
|
32
|
+
|
|
33
|
+
## Then actually build each one
|
|
34
|
+
|
|
35
|
+
Compiling is the cheapest verification and it catches real problems:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
for d in fenix6 fenix7 fr955 vivoactive4; do
|
|
39
|
+
monkeyc -f monkey.jungle -d $d -o bin/t-$d.prg -y $KEY -w -l 3 \
|
|
40
|
+
| sed "s/^/$d: /"
|
|
41
|
+
done
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Two things that surface here and nowhere else:
|
|
45
|
+
|
|
46
|
+
**API level.** `ERROR: Device 'approachs62' does not support API Level '3.1.0'.`
|
|
47
|
+
A device can share your resolution and still be excluded. Drop it, or lower
|
|
48
|
+
`minApiLevel` and give up the APIs you were using.
|
|
49
|
+
|
|
50
|
+
**Launcher icon size.** `WARNING: The launcher icon (40x40) isn't compatible with
|
|
51
|
+
the specified launcher icon size of the device 'vivoactive4' (35x35). The image
|
|
52
|
+
will be scaled.` Scaling blurs detail at a size where it is already marginal.
|
|
53
|
+
Give those devices their own resource path:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
# monkey.jungle
|
|
57
|
+
base.resourcePath = resources
|
|
58
|
+
|
|
59
|
+
# resources-35 is listed second so its drawables win.
|
|
60
|
+
vivoactive4.resourcePath = $(base.resourcePath);resources-35
|
|
61
|
+
legacyherofirstavenger.resourcePath = $(base.resourcePath);resources-35
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
with `resources-35/drawables/` holding both `drawables.xml` and the 35x35 PNG.
|
|
65
|
+
|
|
66
|
+
## What does not port
|
|
67
|
+
|
|
68
|
+
Compiling clean does **not** mean it looks right. It will compile and look wrong,
|
|
69
|
+
which is worse than failing.
|
|
70
|
+
|
|
71
|
+
Before declaring a device of a different size or shape:
|
|
72
|
+
|
|
73
|
+
1. **Absolute pixel constants.** Anything like `const PANEL_X = 30` needs to
|
|
74
|
+
become a fraction of `dc.getWidth()`.
|
|
75
|
+
2. **Round-bezel assumptions.** `halfWidthAt()` computes a curve that does not
|
|
76
|
+
exist on a rectangular screen and needlessly squeezes content; on 240x400 it
|
|
77
|
+
is badly wrong. Branch on `System.getDeviceSettings().screenShape`.
|
|
78
|
+
3. **Font heights.** They differ between devices at the *same* resolution. Derive
|
|
79
|
+
everything (see `reference/layout.md`); a fixed Y that fits a 19px glyph
|
|
80
|
+
overflows a 24px one.
|
|
81
|
+
4. **Sensor availability.** Body Battery and stress are absent on older and
|
|
82
|
+
cheaper devices. Guard with `Toybox has :SensorHistory` and degrade to `--`.
|
|
83
|
+
|
|
84
|
+
If the layout is not resolution-aware, say so in the manifest so the next person
|
|
85
|
+
does not "helpfully" add products:
|
|
86
|
+
|
|
87
|
+
```xml
|
|
88
|
+
<!-- Every product here is 260x260, the only resolution this face supports:
|
|
89
|
+
Layout uses absolute pixel constants and halfWidthAt() assumes a round
|
|
90
|
+
bezel. Another size or shape will compile and look wrong, not fail. -->
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Recommended order
|
|
94
|
+
|
|
95
|
+
1. Ship one resolution, verified on your own wrist.
|
|
96
|
+
2. Add the same-resolution family — biggest reach per unit of risk.
|
|
97
|
+
3. Make `Layout` resolution-aware only if it gets traction. It is real work and
|
|
98
|
+
it risks the pixel-exact look you tuned.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# The display
|
|
2
|
+
|
|
3
|
+
Two hardware facts drive every colour decision. Both are invisible in the
|
|
4
|
+
simulator, which renders on a backlit LCD and flatters everything.
|
|
5
|
+
|
|
6
|
+
## Transflective MIP: brightness is reflectance
|
|
7
|
+
|
|
8
|
+
Most fenix/Forerunner/Instinct screens are memory-in-pixel, **transflective, with
|
|
9
|
+
no backlight**. Pixels reflect ambient light rather than emitting it. So a
|
|
10
|
+
colour's brightness *is* how much light it returns to the eye.
|
|
11
|
+
|
|
12
|
+
| Colour | Reflects |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `0xFFFFFF` | all three channels — brightest available |
|
|
15
|
+
| `0xAAAAAA` | about two-thirds of white |
|
|
16
|
+
| `0x555555` | about a third |
|
|
17
|
+
| `0x00FF00` | green only — roughly a third of white |
|
|
18
|
+
|
|
19
|
+
**Consequence 1: large areas must be bright.** Choosing a photographically
|
|
20
|
+
accurate mid-grey for the biggest region on the face makes the whole watch read
|
|
21
|
+
as dim on the wrist while looking correct in the simulator. If a design has a
|
|
22
|
+
large panel, `0xFFFFFF` is usually the right answer even when a reference
|
|
23
|
+
photograph says otherwise.
|
|
24
|
+
|
|
25
|
+
**Consequence 2: never colour the thing you need to read.** A saturated colour on
|
|
26
|
+
a glyph reflects only its own channel, making the number you are trying to read
|
|
27
|
+
the *dimmest* thing on screen. Put status colour on a chip, bar, or rule beside
|
|
28
|
+
the value and keep the value white.
|
|
29
|
+
|
|
30
|
+
If a user insists on coloured values, say once that it costs outdoor legibility,
|
|
31
|
+
then do it. It is their watch.
|
|
32
|
+
|
|
33
|
+
**Consequence 3: big saturated fills shout.** A colour that reads as informative
|
|
34
|
+
at glyph size is overwhelming across a progress bar. Keep a separate pale tint
|
|
35
|
+
for large fills:
|
|
36
|
+
|
|
37
|
+
```monkeyc
|
|
38
|
+
const READY = 0x55FF55; // status, at glyph size
|
|
39
|
+
const FILL_OK = 0xAAFFAA; // the same meaning, across a large area
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The 64-colour lattice
|
|
43
|
+
|
|
44
|
+
Many devices quantise to 4 levels per channel: `0x00 / 0x55 / 0xAA / 0xFF`. A
|
|
45
|
+
colour not already on the lattice is snapped at draw time, so **what you picked
|
|
46
|
+
is not what ships**. Greys and golds drift most visibly.
|
|
47
|
+
|
|
48
|
+
Useful quantisations:
|
|
49
|
+
|
|
50
|
+
| Intent | Nearest lattice point | Reads as |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| Gold `(212,175,90)` | `0xAAAA55` | olive/khaki |
|
|
53
|
+
| Brighter gold | `0xFFAA55` | orange |
|
|
54
|
+
| Amber warning | `0xFFAA00` | strong orange |
|
|
55
|
+
| Sky blue | `0x55AAFF` | good |
|
|
56
|
+
| Mid grey | `0xAAAAAA` | good |
|
|
57
|
+
|
|
58
|
+
There is no good gold. Say so rather than iterating.
|
|
59
|
+
|
|
60
|
+
Pin every palette constant with a test:
|
|
61
|
+
|
|
62
|
+
```monkeyc
|
|
63
|
+
const LEVELS = [0x00, 0x55, 0xAA, 0xFF];
|
|
64
|
+
|
|
65
|
+
function quantizeChannel(value as Number) as Number {
|
|
66
|
+
var nearest = LEVELS[0];
|
|
67
|
+
var best = 256;
|
|
68
|
+
for (var i = 0; i < LEVELS.size(); i += 1) {
|
|
69
|
+
var d = (value - LEVELS[i]).abs();
|
|
70
|
+
if (d < best) { best = d; nearest = LEVELS[i]; }
|
|
71
|
+
}
|
|
72
|
+
return nearest;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function quantize(color as Number) as Number {
|
|
76
|
+
return (quantizeChannel((color >> 16) & 0xFF) << 16)
|
|
77
|
+
| (quantizeChannel((color >> 8) & 0xFF) << 8)
|
|
78
|
+
| quantizeChannel(color & 0xFF);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isLatticeExact(color as Number) as Boolean {
|
|
82
|
+
return quantize(color) == color;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```monkeyc
|
|
87
|
+
(:test)
|
|
88
|
+
function everyPaletteColourSurvivesQuantization(logger as Logger) as Boolean {
|
|
89
|
+
var colors = [Palette.BG, Palette.VALUE, Palette.LABEL /* ... */];
|
|
90
|
+
for (var i = 0; i < colors.size(); i += 1) {
|
|
91
|
+
if (!Palette.isLatticeExact(colors[i])) { return false; }
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Also pin the *relationships*, which is what actually breaks:
|
|
98
|
+
|
|
99
|
+
```monkeyc
|
|
100
|
+
(:test)
|
|
101
|
+
function panelIsLighterThanItsSegments(logger as Logger) as Boolean {
|
|
102
|
+
return Palette.PANEL > Palette.SEGMENT;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Colour discipline that survives contact with a wrist
|
|
107
|
+
|
|
108
|
+
- Reserve colour for states worth interrupting a glance for. A green "50%"
|
|
109
|
+
battery is decoration that costs luminance and teaches the eye to ignore
|
|
110
|
+
colour.
|
|
111
|
+
- Set thresholds against the device, not phone habits. A 14-day watch at 50% has
|
|
112
|
+
a week left; that is not a warning.
|
|
113
|
+
- Prefer luminance hierarchy — label grey, value white — over hue.
|
|
114
|
+
- One accent colour on the whole face, used once.
|
|
115
|
+
|
|
116
|
+
## AMOLED devices
|
|
117
|
+
|
|
118
|
+
Venu and newer devices are emissive and do not share these constraints, but they
|
|
119
|
+
do have burn-in protection requirements and an always-on mode with a pixel budget.
|
|
120
|
+
Do not carry MIP palette reasoning onto them unexamined.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Layout
|
|
2
|
+
|
|
3
|
+
Monkey C will happily draw at y=400 on a 260px screen. Nothing warns you; the
|
|
4
|
+
pixels just do not exist. Almost every layout bug in this skill's history is a
|
|
5
|
+
constant that was written down instead of measured.
|
|
6
|
+
|
|
7
|
+
## Measure, never assume, font heights
|
|
8
|
+
|
|
9
|
+
Real heights on fenix 6 Pro. They are larger than they look:
|
|
10
|
+
|
|
11
|
+
| Font | Height |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `FONT_XTINY` | 19 |
|
|
14
|
+
| `FONT_TINY` | 29 |
|
|
15
|
+
| `FONT_SMALL` | 32 |
|
|
16
|
+
| `FONT_NUMBER_MEDIUM` | 74 |
|
|
17
|
+
|
|
18
|
+
`FONT_NUMBER_MEDIUM` at 74px means a two-line time block is **148px of a 260px
|
|
19
|
+
face** — 57% of the screen for four digits.
|
|
20
|
+
|
|
21
|
+
These differ per device, so treat the table as an order-of-magnitude guide and
|
|
22
|
+
call `dc.getFontHeight()` at draw time.
|
|
23
|
+
|
|
24
|
+
To measure on a device you have not used, drop a `System.println` into
|
|
25
|
+
`onUpdate`, build, push, and read `monkeydo`'s stdout:
|
|
26
|
+
|
|
27
|
+
```monkeyc
|
|
28
|
+
System.println("H xtiny=" + dc.getFontHeight(Graphics.FONT_XTINY)
|
|
29
|
+
+ " small=" + dc.getFontHeight(Graphics.FONT_SMALL)
|
|
30
|
+
+ " num=" + dc.getFontHeight(Graphics.FONT_NUMBER_MEDIUM));
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Do this instead of guessing. It costs one build.
|
|
34
|
+
|
|
35
|
+
## Derive the vertical stack at draw time
|
|
36
|
+
|
|
37
|
+
```monkeyc
|
|
38
|
+
function onUpdate(dc as Dc) as Void {
|
|
39
|
+
var labelH = dc.getFontHeight(Graphics.FONT_XTINY);
|
|
40
|
+
var valueH = dc.getFontHeight(Graphics.FONT_TINY);
|
|
41
|
+
|
|
42
|
+
var rowTop = HEADER_Y + labelH + GAP;
|
|
43
|
+
var columnBottom = rowTop + (rows * (valueH + GAP));
|
|
44
|
+
var ruleY = clampRuleY(columnBottom + GAP_ABOVE_RULE, labelH);
|
|
45
|
+
var footer1Y = ruleY + GAP_BELOW_RULE;
|
|
46
|
+
var footer2Y = footer1Y + labelH + GAP_BETWEEN_ROWS;
|
|
47
|
+
...
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`Layout` holds gaps and ratios; the view holds the arithmetic. Keep `Dc` out of
|
|
52
|
+
`Layout` so the geometry stays unit-testable.
|
|
53
|
+
|
|
54
|
+
## Clamp, so the bottom cannot fall off
|
|
55
|
+
|
|
56
|
+
Deriving is not enough. If everything above grows, the footer walks off the
|
|
57
|
+
screen and vanishes silently. Reserve its space first and let the stack above
|
|
58
|
+
give way:
|
|
59
|
+
|
|
60
|
+
```monkeyc
|
|
61
|
+
function clampRuleY(derived as Number, footerH as Number) as Number {
|
|
62
|
+
var reserved = (footerH * 2) + GAP_BELOW_RULE + GAP_BETWEEN_ROWS;
|
|
63
|
+
var maxRuleY = SCREEN - BOTTOM_MARGIN - reserved;
|
|
64
|
+
return derived > maxRuleY ? maxRuleY : derived;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Pin it with a sweep, not a single case:
|
|
69
|
+
|
|
70
|
+
```monkeyc
|
|
71
|
+
(:test)
|
|
72
|
+
function footerStaysOnScreenForAnyPlausibleFont(logger as Logger) as Boolean {
|
|
73
|
+
for (var labelH = 14; labelH <= 40; labelH += 1) {
|
|
74
|
+
for (var timeH = 40; timeH <= 120; timeH += 4) {
|
|
75
|
+
if (footerBottom(labelH, timeH / 2, timeH) >= Layout.SCREEN) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A single-value test passes with the font height you happened to assume. The sweep
|
|
85
|
+
is what catches the device you have not tried.
|
|
86
|
+
|
|
87
|
+
## Round screens
|
|
88
|
+
|
|
89
|
+
The screen is a circle; the bounding box is a lie.
|
|
90
|
+
|
|
91
|
+
```monkeyc
|
|
92
|
+
// Widest half-chord at a given y.
|
|
93
|
+
function halfWidthAt(y as Number) as Number {
|
|
94
|
+
var dy = y - CENTER;
|
|
95
|
+
var inside = (CENTER * CENTER) - (dy * dy);
|
|
96
|
+
return inside <= 0 ? 0 : Math.sqrt(inside).toNumber();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Usable X corridor for a row, measured at whichever edge the curve cuts first.
|
|
100
|
+
function rowXBounds(topY as Number, height as Number) as Array<Number> {
|
|
101
|
+
var worstY = topY < CENTER ? topY : topY + height;
|
|
102
|
+
var half = halfWidthAt(worstY) - MARGIN;
|
|
103
|
+
if (half < 0) { half = 0; }
|
|
104
|
+
return [CENTER - half, CENTER + half] as Array<Number>;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**A row is cut at its lowest edge if below centre, its highest if above.** Using
|
|
109
|
+
the row's top for a bottom row is the classic error — it reports space that is
|
|
110
|
+
not there.
|
|
111
|
+
|
|
112
|
+
**Rectangles fail at their corners, not their edges.** Checking the sides of a
|
|
113
|
+
panel against `halfWidthAt` passes while the corners hang outside the bezel:
|
|
114
|
+
|
|
115
|
+
```monkeyc
|
|
116
|
+
function cornerInsideBezel(x as Number, y as Number) as Boolean {
|
|
117
|
+
var dx = x - CENTER;
|
|
118
|
+
var dy = y - CENTER;
|
|
119
|
+
return ((dx * dx) + (dy * dy)) < (CENTER * CENTER);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**The bottom of a round face is narrow.** At y=246 on a 260px screen only ~110px
|
|
124
|
+
of width remains. Two label/value pairs do not fit there; one does. Test for real
|
|
125
|
+
width, not merely a non-negative corridor:
|
|
126
|
+
|
|
127
|
+
```monkeyc
|
|
128
|
+
(:test)
|
|
129
|
+
function footerKeepsEnoughWidthForItsPair(logger as Logger) as Boolean {
|
|
130
|
+
var bounds = Layout.rowXBounds(footerY, footerH);
|
|
131
|
+
return (bounds[1] - bounds[0]) >= 90;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Seven-segment digits
|
|
136
|
+
|
|
137
|
+
No system font is a segment display, and a rounded sans-serif `4` is the single
|
|
138
|
+
thing that gives away a digital-watch homage. Draw them.
|
|
139
|
+
|
|
140
|
+
```monkeyc
|
|
141
|
+
module Seg7 {
|
|
142
|
+
const SEG_A = 0x01; const SEG_B = 0x02; const SEG_C = 0x04; const SEG_D = 0x08;
|
|
143
|
+
const SEG_E = 0x10; const SEG_F = 0x20; const SEG_G = 0x40;
|
|
144
|
+
const ALL = 0x7F;
|
|
145
|
+
|
|
146
|
+
// aaaa
|
|
147
|
+
// f b
|
|
148
|
+
// gggg
|
|
149
|
+
// e c
|
|
150
|
+
// dddd
|
|
151
|
+
const DIGITS = [
|
|
152
|
+
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // 0
|
|
153
|
+
SEG_B|SEG_C, // 1
|
|
154
|
+
SEG_A|SEG_B|SEG_G|SEG_E|SEG_D, // 2
|
|
155
|
+
SEG_A|SEG_B|SEG_G|SEG_C|SEG_D, // 3
|
|
156
|
+
SEG_F|SEG_G|SEG_B|SEG_C, // 4
|
|
157
|
+
SEG_A|SEG_F|SEG_G|SEG_C|SEG_D, // 5
|
|
158
|
+
SEG_A|SEG_F|SEG_G|SEG_E|SEG_C|SEG_D, // 6
|
|
159
|
+
SEG_A|SEG_B|SEG_C, // 7
|
|
160
|
+
ALL, // 8
|
|
161
|
+
SEG_A|SEG_B|SEG_C|SEG_D|SEG_F|SEG_G // 9
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
// Mitred ends, so segments interlock at the corners instead of merging
|
|
165
|
+
// into a solid block.
|
|
166
|
+
function horizontal(dc as Dc, x as Number, y as Number, w as Number, t as Number) as Void {
|
|
167
|
+
var h = t / 2;
|
|
168
|
+
dc.fillPolygon([
|
|
169
|
+
[x + h, y], [x + w - h, y], [x + w, y + h],
|
|
170
|
+
[x + w - h, y + t], [x + h, y + t], [x, y + h]
|
|
171
|
+
] as Array<[Numeric, Numeric]>);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function vertical(dc as Dc, x as Number, y as Number, len as Number, t as Number) as Void {
|
|
175
|
+
var h = t / 2;
|
|
176
|
+
dc.fillPolygon([
|
|
177
|
+
[x, y + h], [x + h, y], [x + t, y + h],
|
|
178
|
+
[x + t, y + len - h], [x + h, y + len], [x, y + len - h]
|
|
179
|
+
] as Array<[Numeric, Numeric]>);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Both vertical runs span from their end of the cell to the centred middle
|
|
183
|
+
// bar: half the cell plus half a bar thickness.
|
|
184
|
+
function drawMask(dc as Dc, mask as Number, x as Number, y as Number,
|
|
185
|
+
w as Number, h as Number, t as Number) as Void {
|
|
186
|
+
var mid = y + (h / 2) - (t / 2);
|
|
187
|
+
var right = x + w - t;
|
|
188
|
+
var vlen = (h / 2) + (t / 2);
|
|
189
|
+
|
|
190
|
+
if (mask & SEG_A) { horizontal(dc, x, y, w, t); }
|
|
191
|
+
if (mask & SEG_G) { horizontal(dc, x, mid, w, t); }
|
|
192
|
+
if (mask & SEG_D) { horizontal(dc, x, y + h - t, w, t); }
|
|
193
|
+
if (mask & SEG_F) { vertical(dc, x, y, vlen, t); }
|
|
194
|
+
if (mask & SEG_B) { vertical(dc, right, y, vlen, t); }
|
|
195
|
+
if (mask & SEG_E) { vertical(dc, x, mid, vlen, t); }
|
|
196
|
+
if (mask & SEG_C) { vertical(dc, right, mid, vlen, t); }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Draw the unlit segments.** A real module shows them faintly, so a `1` sits
|
|
202
|
+
inside a visible `8`. This single detail is most of what makes drawn digits read
|
|
203
|
+
as a crystal rather than a font. Draw the ghost with `ALL` first, then the value
|
|
204
|
+
on top.
|
|
205
|
+
|
|
206
|
+
Two things this costs you:
|
|
207
|
+
|
|
208
|
+
- Ghosts destroy legibility below about 60px. At icon size, the faint `8` behind
|
|
209
|
+
a `1` wins and every digit reads as `8`. Omit them there.
|
|
210
|
+
- Ghost colour must sit between panel and segment. Against a white panel,
|
|
211
|
+
`0x555555` ghosts compete with lit digits — use `0xAAAAAA`.
|
|
212
|
+
|
|
213
|
+
Blank a leading zero by drawing only the ghost, not by drawing `0`. Return
|
|
214
|
+
`null` from the digit provider so the drawing code can tell "blank" from "the
|
|
215
|
+
digit nought" — they are different pictures.
|
|
216
|
+
|
|
217
|
+
## Miscellaneous
|
|
218
|
+
|
|
219
|
+
- Centring an odd width leaves a 1px skew from integer division. Write the test
|
|
220
|
+
to allow `<= 1` rather than forcing every constant to stay even.
|
|
221
|
+
- Draw filled shapes rather than stroked outlines when the geometry has been
|
|
222
|
+
checked against a boundary: a stroke straddles its path, putting half its width
|
|
223
|
+
outside what you verified.
|
|
224
|
+
- Order matters when compositing. Pasting a square image over a ring erases it.
|