Skip to content

Visual system

Garmin Wear OS Apple Watch
Status ✅ ✅ ⏳
Code source/Theme.mc, source/VesselState.mc, tools/genfont.py, resources-round-<w>x<h>/, manifest.xml core/…/VesselState.kt (Palette), wear/…/presentation/gfx/Dc.kt, wear/…/presentation/views/Theme.kt (Layout), wear/…/presentation/GarminCanvas.kt, wear/src/main/res/font/ —

Verified against code: 2026-10-13

Purpose

The colours, fonts, layout rules and screen sizes every WatchApp screen is built from. Garmin is the reference: Wear OS draws the same screens with the same numbers through a Connect IQ-style drawing layer, so one set of fractions and colours holds on both. Screen-specific colours stay in their screen's spec; this file lists the shared ones.

Layout / behaviour

Palette

Colours are opaque 0xRRGGBB. Two sets are used: the app's own theme colours and Connect IQ's named colours. Wear OS holds both in Palette with the same values.

Theme colours (Theme.mc, Palette):

Name Hex Used for
SAIL Sail accent (vessel-state.md) Sailing accent; also Paired, sync success, progress bar, zoom-in badge
ENGINE Engine accent (vessel-state.md) Motoring accent; also errors (sync failure), discard confirm, zoom-out badge
MOTORSAIL Motorsail accent (vessel-state.md) Motorsailing accent
IDLE Stationary accent (vessel-state.md) Accent of every stationary state
BG 0x000000 Background of the black screens
FG 0xFFFFFF Main text on black
DIM 0xAAAAAA Captions and secondary text on black
FAINT 0x555555 Hints, empty progress bar
RULE 0x555555 Divider lines (auto-stats overlay)

The vessel state accent is given by the state itself (VesselState.color()); Theme.accentFor(state) returns IDLE when there is no state.

Connect IQ named colours (Graphics.COLOR_*, Wear OS Palette.*):

Name Hex Used for
WHITE 0xFFFFFF Dashboard background, SOG digits, overlay text
BLACK 0x000000 Dashboard text, rules and SOG block, pause view background
LT_GRAY 0xAAAAAA Unlit GPS bars, battery outlines, GPS off, page indicators
DK_GRAY 0x555555 Wear OS only (defined, same as FAINT)
ORANGE 0xFF5500 Units and labels on the dashboard, max-speed ▲, turn angle and turn segments, paused, Wear OS touch-lock badge
YELLOW 0xFFAA00 Baro arrow, rising or falling
RED 0xFF0000 Baro arrow (strong trend), alert overlay background, battery fill below 20 % on the pause view
BLUE 0x00AAFF Lit GPS bars, battery fill, baro arrow (steady), speed chart fallback

ORANGE (0xFF5500) and the Motorsail accent are different colours; YELLOW has the same value as the Motorsail accent, and BLUE the same as the Sail accent.

Screen-local colours (listed in their screen spec): Environment view card, arc and tide colours (environment.md); the brand colours navy 0x0B1B2B, brand orange 0xED6017 and slate 0x94A3B8 on the splash and the auto-stats overlay (lifecycle.md, alerts-and-auto-stats.md).

Rule for saturated colours: use the Connect IQ named colour, not a custom hex, where a colour must stay strong on MIP screens. MIP displays show 64 colours, and a custom red like 0xD32F2F turns pale salmon there; the alert overlay therefore uses RED.

Fonts

Three custom fonts carry the numbers; Garmin's system fonts carry text.

Font Face Digit height (8) Glyph set Used for
sog (hero) Roboto Condensed Bold 0.180 × screen width 0123456789.: Dashboard SOG, pause-view clock, Wear OS ambient SOG
numerals Roboto Condensed Bold 0.090 × width 0123456789.:-°+Ø Clock, COG, distance, timer, pairing code, auto-stats values
header Roboto Condensed Bold 0.080 × width 0123456789.:-°+\| SABCDGM acefghilnorstuy (note the space) Max and average speed, pressure, CoG / nM labels, vessel state line 1, SOG tens digit, turn angle

A text drawn in a custom font may only use characters from its glyph set. Anything else draws as an empty box on Garmin. The header set covers exactly the eight -ing state labels and the dashboard labels.

System fonts, smallest to largest: XTINY, TINY, SMALL, MEDIUM, LARGE. The theme names two of them: the label font (XTINY) for captions and the small font (TINY).

Garmin

  • The custom fonts are BMFont bitmap sheets (.fnt + greyscale anti-aliased .png), one set per screen family in resources-round-<w>x<h>/fonts/ with a generated fonts.xml (ids SogFont, NumeralsFont, HeaderFont). The SDK picks the family's folder at build time, so a build carries only its own sheets; monkey.jungle needs no entry.
  • tools/genfont.py generates every sheet from resources/fonts/RobotoCondensed-Bold.ttf: for each family it picks the TTF pixel size whose 8 is closest to the target digit height, packs the glyphs into a sheet at most 256 px wide, and rebases the metrics on the glyph ink so that the line height is the drawn height and vertical centring lands on the digits. Edit the script, never the sheets.
  • Bitmap fonts work on every target; vector fonts (Graphics.getVectorFont) are not used because older targets lack them. The glyph sets stay small because sheet size costs memory on 128 KB watches.
  • Line heights of the sheets (px), from the generated .fnt files:
Family sog numerals header
218×218 39 22 23
240×240 43 25 24
260×260 47 25 28
280×280 50 28 30
360×360 64 35 39
390×390 69 38 42
416×416 75 39 45
454×454 82 43 49
466×466 83 44 49
  • System fonts are the device's Graphics.FONT_XTINY … FONT_LARGE; their size differs per watch model.

Wear OS

  • The same face ships as res/font/roboto_condensed_bold.ttf (and _regular.ttf). Fonts.forWidth builds the three custom fonts by scaling the face so its 8 is 18 / 9 / 8 % of the screen width, and copies the line height and baseline of the BMFont sheets as ratios of the digit height (sog 1.003 / 0.991, numerals 1.051 / 0.978, header 1.350 / 1.047). Vertically centred text therefore lands where it does on Garmin. Sizes scale continuously with the width; there are no families. Fonts are cached per width.
  • The TTF has every glyph, so the glyph-set rule doesn't bite on Wear OS; ported strings must still follow it so Garmin renders them.
  • System fonts are Roboto Condensed at fixed fractions of the width: XTINY 7.5 % bold, TINY 8.5 % bold, SMALL 9.5 % bold, MEDIUM 11 % bold, LARGE 13 % bold; the label font is 7.5 % regular.
  • Menus and lists (start list, picker, Controls, settings) are Wear Material 3 components with their own typography (menus.md); only the canvas screens use these fonts.

Apple Watch

⏳ Not decided in the port plan.

Layout rules

  • Positions are fractions of the screen, never fixed pixels: Theme.x(f) = width × f, Theme.y(f) = height × f (truncated). The dashboard uses its own grid of quarters and fifths (dashboard.md).
  • Round screens: Theme.chordHalfWidth(y) gives the half-width of the visible circle at height y, for rows that must fit inside the bezel (used by the Environment view).
  • Fixed pixel minimums only where a shape would vanish on small screens (e.g. the max-speed ▲ at least 6 px, the zoom badges at least 8 px).
  • Text is vertically centred on its y (TEXT_JUSTIFY_VCENTER) unless a spec says otherwise; horizontal justification is left, centre or right at the given x.
  • One Theme is created per track from the first screen's size and shared by every view.
  • Strings are English literals; the UI is not localised.

Wear OS: Connect IQ drawing on Android

All instrument screens, overlays, the pause, sync, pairing and splash screens are drawn on a full-screen Compose canvas (GarminCanvas) through Dc, a port of Connect IQ's Graphics.Dc. The view code is a line-by-line port of the Monkey C view with the same fractions, colours, fonts and justification. Dc keeps Connect IQ semantics:

Topic Connect IQ behaviour kept by Dc
Coordinates Pixels from the top-left, same as Garmin
Colours setColor(0xRRGGBB), always opaque; there is no transparent background colour
Justification Flags RIGHT 0, CENTER 1, LEFT 2, VCENTER 4. VCENTER centres the font's line box on y; without it y is the top of the line box
drawRectangle 1 px outline drawn inside the box
Arcs Degrees counter-clockwise from 3 o'clock; a clockwise arc runs from start down to end
getTextWidthInPixels / getFontHeight Measured width rounded to whole px / the font's line height
Redraw The canvas redraws when the engine's tick counter changes (1 Hz while recording)

Wear OS draws everything anti-aliased; Garmin draws lines and polygons without anti-aliasing (only the font sheets are smoothed).

Supported screens

Garmin

  • Round screens only, 9 families from 218×218 to 466×466. The manifest lists 103 products (minimum API level 3.1.0). Base fenix 5 / 5S are not targets (128 KB memory limit); fenix 5 Plus / 5X are.
  • Families and display type (from the SDK device definitions):
Family Display Products Examples
218×218 MIP 5 Forerunner 255S, vívoactive 4S
240×240 MIP 28 fenix 5 Plus / 5X, fenix 6S / 7S, Forerunner 945, MARQ Gen 1
260×260 MIP 13 fenix 6 / 7, Forerunner 955 / 255, fenix 8 Solar 47 mm
280×280 MIP 9 fenix 6X / 7X, Enduro, fenix 8 Solar 51 mm
360×360 AMOLED 2 Forerunner 265S, Venu 2S
390×390 AMOLED 19 Venu / Venu 3S, Forerunner 165 / 570 42 mm, Instinct 3 AMOLED 45 mm
416×416 AMOLED 12 epix 2, fenix 8 43 mm, Forerunner 265, Venu 2
454×454 AMOLED 14 fenix 8 47 mm, Forerunner 965 / 970, Venu 3
466×466 AMOLED 1 fenix 9 Pro 51 mm
  • MIP (55 products): sunlight-readable, 64 colours; see the rule for saturated colours above.
  • AMOLED (48 products): the same screens are drawn; there is no separate AMOLED layout, no burn-in shifting and no low-power layout. The white dashboard lights the whole screen; the other instrument screens and the pause view are black.

Wear OS

  • Any round Wear OS watch from Wear OS 3 (minSdk 30); checked on the 384 px emulator. Layout and fonts scale with the screen width.
  • All Wear OS screens are OLED. While recording, the screen stays on and the low-power look is the low-power layout (live-surfaces.md).
  • Watches without a barometer or stem buttons still run the app; the barometer values show -- and the stem shortcuts are absent.

Apple Watch

⏳ Planned: watchOS 11+, usable on 41 mm and 49 mm (apple_watch_port_plan.md §2–3).

States and edge cases

  • Text outside a glyph set (Garmin): empty boxes. Known case: PAUSED on the dashboard timer (dashboard.md).
  • Smallest screen (218 px): the reference for collisions. Every screen spec names its tightest spots for this size.
  • Unknown future Garmin family: the base resources/ folder declares no fonts, so without a matching resources-round-<w>x<h>/ folder Rez.Fonts.* is missing and the build fails. A new family needs an entry in tools/genfont.py and a manifest product.

Known deviations

None.