Modernize Timely → TimelyNG (Core Devices SDK, complications, adaptive layout) - #2
Merged
Merged
Conversation
- wscript: switch to pbl_build and call the SDK's
pbl_suppress_newer_gcc_warnings so the legacy code compiles under the
modern arm-none-eabi gcc -Werror (truncation/fallthrough/etc).
- extract pure date/timezone formatting into src/timefmt.{c,h} (no Pebble
deps); fix the days-left and timezone snprintf truncation via input clamps.
- tests: utest.h-based host unit tests for timefmt, run via make / just /
nix flake check.
- flake.nix: add just+make to the pebble dev shell, a devShells.test with a
host cc, and checks.default that builds and runs the unit tests; reuse
pebble.nix's pinned nixpkgs.
aplite + basalt build clean and produce a .pbw.
…tches - appinfo.json: new UUID (separate app for republish), name TimelyColor, version 3.0, author Emanuele 'Lele' Calo; targetPlatforms now aplite, basalt, diorite, emery (Pebble Time 2, color) and flint (Pebble 2 Duo / Core 2 Duo, B&W). Round platforms (chalk/gabbro) deferred: the calendar UI is rectangular-centric. - src/Timely.c: size date_string to 64 to remove a real truncation on the localized date string. - flake.nix: default emulator emery (color); downgrade legacy warnings to non-fatal via shellHook CFLAGS=-Wno-error since the new flint platform does not pick up the SDK's in-tree warning suppressions. All five target platforms build a .pbw.
Add setTodayColors(): on color hardware (basalt/emery) today's calendar cell uses an accent fill (GColorJaegerGreen, tunable) with white text; on B&W (aplite/diorite/flint) it falls back byte-for-byte to the classic white-on-black inversion via PBL_IF_COLOR_ELSE. First step of a color theme with graceful monochrome fallback.
…ribution State the modernization changes (GPLv3 sec.5), keep the original Martin Norland / William Heaton / Ben Johnson copyrights, and add the 2026 Emanuele 'Lele' Calo copyright for this fork. Document the Nix dev/build/test flow and the targeted platforms.
src/layout.{c,h}: pure (Pebble-free) layout_compute(w,h) -> TimelyLayout that
derives statusbar/slot/clock/calendar rects from the runtime screen size.
Host tests verify it matches the historical 144x168 geometry and fills emery
200x228. Refactor the test suite into one binary (tests/test_main.c +
per-module tests/test_*.c) so make / just / nix flake check run all modules.
Not yet wired into the renderer (Timely.c still uses the legacy #defines);
that integration is the next step.
Convert the geometry #defines (DEVICE_*/LAYOUT_*/REL_CLOCK_*/CAL_WIDTH/HEIGHT plus right-aligned STAT_BATT_LEFT/STAT_CHRG_ICON_LEFT) to file globals that compute_layout() fills from the tested layout_compute() at window_load. The renderer now fills the screen on every platform: emery (200x228) uses the full width/height with a wider calendar; classic 144x168 is byte-identical (host tests assert the historical geometry). Reference sites are unchanged.
cal_cell_h = slot_bot.h / 4 so the classic 144x168 keeps its historical 18px rows (was regressing to 24px); emery gets 25px rows. Tests updated.
Pure, host-tested calendar_build() replaces the inline grid computation in calendar_layer_update_callback (rendering stays). 3 tests assert the grid against the verified June 2026 emulator output and the start-of-week offset paths. Behavior unchanged (emery render identical).
period_contains() (host-tested: single-day, midnight-wrap, equal start/stop) replaces the inline window check in period_check(); the Pebble wrapper keeps reading currentTime and the debug log. Behavior unchanged.
weather.c owns the weather_data (file-static); the global is gone and all 23 field accesses go through weather_state(). First step of the state-encapsulation redesign (no shared globals).
settings.c owns the persist / persist_adv_settings instances (file-static); the globals are gone and all ~99 field accesses + the persist read/write go through settings_get() / adv_settings_get(). Types moved to settings.h.
locale.c owns the general/months/days language tables (file-static); globals gone, all ~38 accesses + persist read/write go through lang_gen_get() / lang_months_get() / lang_days_get(). Types moved to locale.h.
debug.c owns the persist_debug flags (file-static); the global is gone and all 48 accesses + persist read/write go through debug_get(). Completes the state-encapsulation pass: weather, settings, adv_settings, localization and debug are now module-owned behind accessors (no shared global state).
setColors/setInvColors/setTodayColors move to theme.c (the home for the upcoming color-theme work). Add ui.h for shared UI handles; the window handle is now exposed there (un-static) so view modules can be split out.
weather_layer_update_callback now lives with the weather state it draws; weather.c owns state + render. climacons exposed via ui.h.
weather.c now owns s_weather_layer (private) with a static render proc, exposed via weather_create/destroy/set_frame/set_hidden/mark_dirty. window_load composes it instead of creating the layer directly. First component of the component-ownership view rearchitecture.
- calendar.c stays the pure host-tested model; the Pebble render + layer move to calendar_view.c (calendar_create/destroy/set_hidden/mark_dirty). - splash.c: new component, rebranded 'TimelyColor 3.0', centered via layout. - toggle_slot_bottom rewritten to switch components by id (no raw layer ptrs). - layout module exposes the computed layout via layout_get(); view modules render proportionally from it. Shared read resources (currentTime, calendar fonts) exposed via ui.h. Build green on 5 platforms; 13 host tests pass.
theme.c defines palettes for Mono/Functional/Minimal/Vibrant, each with a light and a dark variant; theme_palette() resolves the active palette from settings.theme + settings.theme_mode (AUTO = night, currently a time-based placeholder pending sunset/sunrise). setColors/setInvColors/setTodayColors are now palette-driven. Default Functional/Dark reproduces the prior look; B&W platforms degrade to mono. Theme selection UI comes with the options milestone.
Uploading to cloudpebble.repebble.com under the TimelyNG name (Timely, Next Generation). Updates appinfo displayName, README, splash text and flake description; UUID/version unchanged.
Build/run-on-emulator/screenshot/logs/kill/sdk plus the host test targets, so common dev commands aren't retyped. Parameterized by EMU (platform) and SHOT (screenshot path). Host-test recipes run in nix develop .#test; pebble recipes in nix develop.
Replaces GNU unifont (bitmap) for the localized-text path (date/day/connection/ calendar on Cyrillic etc.): cleaner letterforms + a real bold (unifont had none). Same character subset (Latin + Cyrillic + Latin-1). Pebble renders text 1-bit (no AA on any platform), so this is a letterform/coverage change, not a smoothness one; Latin languages keep using the crisp system Gothic font.
Big time uses the system FONT_KEY_ROBOTO_BOLD_SUBSET_49 (crisp, modern, fills more) instead of the grainy custom futura TTF. Remove the now-unused futura_condensed and unifont_latest .ttf resources (the unicode path is DejaVu), freeing resource budget.
Pebble top-aligns text in its box, so numbers sat squished to the top of each cell (visible on the taller emery cells). Center them using the measured text height (graphics_text_layout_get_content_size).
content_size height includes the font's line leading, biasing the glyph low; compensate so numbers sit centered in the cell.
Replace the fixed -3px leading correction with ts.h/6 so it scales with the actual text height (normal vs bold, and future per-theme fonts).
Weekend day labels + numbers use palette.weekend (Sat/Sun), distinct from weekday fg; today keeps the accent. Mono theme: weekend == fg (no distinction).
When battery <= 20% the statusbar battery outline uses palette.warn (red on the functional theme; unchanged on mono).
New pure, host-tested suntimes module (solar declination + hour angle, using math.c's trig) computes sunrise/sunset from the configured weather lat/lon + timezone; theme AUTO mode is dark between sunset and sunrise. Verified against Milan/June. Falls back to a 19:00-06:59 window when no location is set, and on aplite (whose 24KB budget can't afford the float trig). Expose timezone_offset via ui.h; wire suntimes+math into the host test suite.
TextLayers (time/date/day/week/ampm/connection/battery%) used a fixed white, invisible on light palettes. apply_palette() sets them to palette.fg and runs each minute tick (and at load) so light/dark + Auto day/night transitions are reflected. The legacy full-screen invert overlay stays hidden by default; the palette now drives light/dark.
Status icons (bluetooth/charging/hourvibe/dnd) were fixed black, invisible on colored/dark backgrounds. tint_icon() recolors their opaque pixels to palette.fg (keeping antialiased alpha) on color platforms, refreshed each tick; bitmap layers use GCompOpSet so the alpha composites over the themed background. B&W platforms keep the 1-bit icons.
- Saving now merges the changed keys into the stored settings instead of overwriting localStorage with just the delta. The config page kept only the last-changed key, so reopening showed every other setting at its default — making it look like changing one complication wiped the others. Merging keeps the full picture for all settings. - Reduce the above-calendar complications from three to two (left/right), each given half the width so longer values (e.g. a location) are no longer cut. The middle slot is retired (hidden); status-bar and above-time complication rows will come next.
PebbleKit JS now sends the weather coordinates (weather_lat/weather_lon) to the watch, which stores and persists them. Two new complications, Sunrise and Sunset, compute local times from those coords via the existing suntimes module (skipped on aplite, whose RAM budget can't afford the float trig — shows --:--). The same coordinates make the Auto theme use the real sunset/sunrise instead of the fixed 19:00-07:00 fallback.
Add a Moon phase complication: a date-only Conway approximation (no coords, no float trig, so it works on every platform) mapped to eight phase labels (New, Wax cres, 1st qtr, Wax gib, Full, Wan gib, Last qtr, Wan cres). Verified against known new/full moons.
Add a configurable second time zone (clock2_tz UTC offset, whole hours) and a '2nd time zone' complication showing that zone's clock. Uses the device's timezone offset to convert; shows --:-- until the timezone is known.
Add small PNG icons (16x16) labelling the two battery readouts: a phone icon by the phone-battery percentage (left) and a watch icon by the watch battery (right). The connection text drops the "Ph " prefix and the Linked/NOLINK word since the icons now convey what each readout is. The watch icon shares the slot used by the charging/DND/hourly-vibe icon, so it is hidden whenever that slot is active to avoid overlap.
The cloud build (cloudpebble.repebble.com) relocates sources into the modern src/c/ layout and moves headers alongside. Angle-bracket includes only search the -I paths, so <Timely.h> was not found there. Quoted includes resolve relative to the including file, matching every other project header in this file; the local flat-src build is unaffected.
On 144px screens the Roboto 49 clock needed almost the full width and collided with the weather to its left. Make the clock font width-aware (Roboto 49 on wide screens like emery, condensed LECO 38 on 144px) and shrink the weather glyph (Climacons 28) and temperature (Gothic 18) on narrow screens so the column is compact. Wide screens are unchanged. Drop aplite from targetPlatforms: the modernized feature set no longer fits its 24KB RAM (code alone ~19KB), so it faulted on launch. basalt, diorite, emery and flint remain; aplite is a future code-diet ToDo.
Extend the unified complication menu (update_slot_text + config SLOTS) with the system-info readouts so they can appear in any slot: Watch battery (15), Phone battery (16), Bluetooth connection (17). They share the same TextLayer renderer as the existing complications.
The status bar is now two configurable complication slots (left/right) instead of a fixed connection + battery display. Each slot picks any content from the shared menu; battery/Bluetooth additionally show a 16px icon on the outer edge, other contents render as text. Defaults preserve the previous look (left = phone battery, right = watch battery). Adds slot_stat_l/slot_stat_r settings + message keys, a 16px Bluetooth glyph, and refreshes the slots on battery/connection/tick/config changes. The graphical battery meter and the fixed BT icon are retired.
Extract the date formatter (format_current_date) from update_date_text so the configured date can render in any complication slot, and add Date (18) to the unified menu. Foundation for placing the date in the CENTER row.
…l rows The band above the time is now two configurable slots (slot_ctr_l/r, default left = Date) drawing from the unified menu, like the status bar and above-calendar rows. A shared layout_two_slots() gives every row the same rule: one slot set renders full-width centered, both set renders two halves, none hides the row. The date is just the default CENTER-left content now; its old fixed positioning is routed through the row layout.
Make the vertical layout adaptive: each optional row (TOP status bar, CENTER above-time, BOTTOM above-calendar) only reserves height when in use, so disabling a row grows the clock and calendar. The clock font scales to the resulting time-band height. layout_compute_rows() takes the enabled rows; compute_layout() derives them from settings. Seat the weather on the time band and top-align it with the clock so it no longer floats above or spills into the calendar. Config: gather all six complication slots into one "Complications" group (was split across Clock and Status bar).
…r slots The hourly-vibration schedule lost its pattern picker — vibe_hour had been reduced to a 0/1 on-off flag. Add a "Vibration pattern" dropdown to the schedule and have augment() store the chosen VIBES pattern in vibe_hour (0 when the mode is Off). Also remove the status-bar slot selectors left duplicated in the Status bar section (they now live only in the grouped Complications section).
Battery complications (watch/phone) now honour a batt_style setting: 0 = battery outline with the % centred inside (battery_layer draws the box), 1 = plain "NN%", 2 = icon + "NN%" (default, prior look). Added the batt_style key (118) and a "Battery style" picker in the Complications group.
…above outline Two review findings: (1) with the status bar fully disabled (showStatus 0) the reclaimed strip is zero-height, so reparenting the date into it clipped it away — keep the date in the center row in that case (only use the strip when it is still reserved, i.e. when-battery-low mode). (2) toggle_statusbar re-raised battery_layer above the slot text; re-add the slot value layers on top so the bar-style outline never buries the percentage.
…ring cloud time_font_key() returned const char* but set_layer_attr_sfont takes char*, which the cloud build rejects with -Werror=discarded-qualifiers (the local build hides it via pbl_suppress_newer_gcc_warnings). Return char* — the FONT_KEY_* values are string literals. Add tools/strict-check.sh: compiles every source with the exact cloud flag set (-Werror + the same -Wno-error= relaxations, -fsyntax-only) per platform, so this class of error is caught locally instead of in the cloud. Verified it reproduces the discarded-qualifiers error and passes once fixed.
…er test gate - Weather: load a 40px Climacons on wide screens and centre the icon+temp block in the time band so on emery it fills the column and lines up with the clock instead of sitting tiny in the corner (48px exceeds Pebble glyph limit, 40 is the max that renders). - Defaults: ship with all three complication rows active (status batteries, Date above the time, Week + AM/PM above the calendar) so the feature is visible out of the box. - Warnings: remove the dead connection_text_buf, size the small snprintf buffers to 16, and clamp sun_time hh/mm — local builds are now warning-free under the cloud flag set. - Tests: strict-check.sh now does a FULL per-platform compile with the cloud -Werror flags and fails on any error OR warning (catches the class of issue the unit tests cannot). Add tools/test.sh (unit + build + strict-check) as the one gate to run before handoff, and extract clock_font_for() into the pure layout module with unit tests + more adaptive-layout coverage.
- Battery "Bar" style now FILLS proportionally: an invert effect per status battery slot, raised above the % text so the value stays readable over the filled part (light-on-dark) and the empty part (dark-on-light). - Status bar row is dynamic like the others: a single status complication is centred (icon+value group / centred text); two stay as halves. (Bar style keeps its side boxes.) - Config: relabel the bar option "Bar (fills) with %".
…+icon, Clocks & Date - Sort the complication menu alphabetically (— pinned first); values keep their mapping so new entries can be appended in any order. - Battery bar fill now spans the full interior height of the box (was a short band that left empty strips and made the % hard to read). - New battery style "Bar + icon": the filling bar plus the watch/phone glyph on the outer edge (box geometry shifts to make room). - Rename the "Clock" settings group to "Clocks & Date".
…s its icon Nudge the bar-style percentage up 2px so descenders are not clipped. Draw the left slot battery with its nib on the left so it meets the adjacent icon and the two read as a single unit (the right slot keeps its nib on the outer edge, toward its icon).
…oday column Raise the Su..Sa header so it no longer rests on the calendar grid line. Also fix the column after today (e.g. Saturday when today is Friday) sitting lower: the special-day branch reset the vertical offset to 0 instead of the header default, so any column drawn after today's kept the old position.
…g date font cal_bold is the 18px date-cell font; using it for today's column header made it taller than the others so it overflowed onto the grid. Use GOTHIC_14_BOLD (same size as the rest, just bold) for the Latin header; RU keeps its unicode font + double-strike.
Recompute the adaptive layout and reposition every band immediately when settings arrive, so enabling/disabling a complication row (or toggling the status bar) redistributes the freed space and rescales the clock font without needing the watchface to reload. Adds calendar_set_frame()/splash_set_frame() and a relayout() that mirrors window_load's geometry (validated: calling it at startup reproduces the same layout).
…e cleanup - Version 0.0.1 (package.json). - README -> README.md (so GitHub renders it); state that TimelyNG is freely inspired by and an homage to the original Timely/PebbleTimely and the Andrew129260/Timely-2 and alan-johnson/PebbleTimely forks; refresh the feature list and drop aplite from the platform list. - Add TODO.md (re-add aplite, round platforms, remove the retired day_layer, feature ideas). - Remove dead code/resources: the always-hidden fixed BT icon (bmp_connection_layer + the 20x20 bluetooth_*.png + IMAGE_BT_LINKED/NOLINK, Bluetooth is a slot icon now) and the unused process_show_* helpers.
- README.md: badges (CI status, version, platforms, C/JS/SDK), a 'Why TimelyNG' section spelling out what it adds over the original Timely and its forks, a platform table, and pointers to the build/test gate and publishing copy. - CI: .github/workflows/ci.yml runs the host unit tests + PebbleKit JS syntax check on every push/PR (validated with actionlint). Full SDK build stays in tools/test.sh (TODO: containerised SDK build in CI). - PUBLISHING.md: appstore listing copy (description, v0.0.1 release notes, category, keywords) and per-platform asset specs, sourced from the Rebble publishing guides. - TODO.md: add LICENSE, CI full build, marketing assets.
…amed) Default watchface captured on emery (200x228), basalt, diorite and flint (144x168) for the appstore listing — unframed, as the store requires. CI workflow validated locally with act (host tests + JS check pass).
…rited TODOs - README platform table now shows every Pebble platform with a status: the four supported ones plus aplite (dropped), chalk (180x180 round) and gabbro (260x260 round) as not-supported/future (round layout needed). - TODO.md: add an 'Inherited TODO (from the original Timely)' section gathering the still-relevant stubbed features (idle reminder, per-slot inversion, show_date, per-weekday hourly vibration, real bold unicode font).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Modernizes the classic Timely / PebbleTimely watchface into TimelyNG:
rebuilt for the current Core Devices Pebble SDK and the 2026 hardware, with a
configurable‑complications system, a theme engine, an adaptive layout, and a
proper test/CI setup. Freely inspired by, and an homage to, the original Timely
and the Andrew129260/Timely‑2 and alan‑johnson/PebbleTimely forks.
Single PR for the whole
feat/modernize-for-modern-stackline of work.Highlights
Build & platforms
emery(Pebble Time 2),flint(Pebble 2 Duo),
basalt,diorite.aplitedropped (24 KB RAM); roundchalk/gabbronoted as future.src/c/layout) andconst‑correct
time_font_key(-Werror=discarded-qualifiers).Complications system
each 1 (centred) or 2 (halves), drawing from one shared, alphabetical
menu (date, day, month, week, day‑of‑year/left, seconds, AM/PM, timezone,
2nd time zone, sunrise, sunset, moon phase, location, watch/phone battery,
Bluetooth).
clock/calendar grow; the clock font scales to the band.
Weather / theme / config
Architecture & quality
layout extracted into testable modules.
tools/test.sh(host tests + build + strictper‑platform compile mirroring the cloud
-Werrorflags); warnings at zero.Release
v0.0.1;README.mdmodernized (badges, platform table, "why" section,homage);
PUBLISHING.md(appstore copy) andTODO.md(incl. inherited TODOs)added; per‑platform store screenshots under
marketing/.Testing
tools/test.shgreen: 18 host unit tests pass; strict per‑platform compile(basalt/diorite/emery/flint) clean with no warnings under the cloud flag
set.
act— job succeeded (host tests + JS check).each battery style, single‑vs‑two complications, status‑bar off, weather
alignment, calendar header.
device.