diff --git a/assets/screenshots/chalk.png b/assets/screenshots/chalk.png deleted file mode 100644 index 12c2228..0000000 Binary files a/assets/screenshots/chalk.png and /dev/null differ diff --git a/assets/screenshots/diorite.png b/assets/screenshots/diorite.png deleted file mode 100644 index d477ad4..0000000 Binary files a/assets/screenshots/diorite.png and /dev/null differ diff --git a/flake.lock b/flake.lock index d02e5fb..dea507e 100644 --- a/flake.lock +++ b/flake.lock @@ -113,6 +113,20 @@ } }, "nixpkgs": { + "locked": { + "lastModified": 1765270179, + "narHash": "sha256-g2a4MhRKu4ymR4xwo+I+auTknXt/+j37Lnf0Mvfl1rE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "677fbe97984e7af3175b6c121f3c39ee5c8d62c9", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1734649271, "narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=", @@ -133,24 +147,26 @@ "commit-hooks": "commit-hooks", "flake-compat": "flake-compat_2", "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs_2" }, "locked": { - "dirtyRev": "3fe382c507afd73a2c6a51ece280c3fb567ac53a-dirty", - "dirtyShortRev": "3fe382c-dirty", "lastModified": 1764961369, - "narHash": "sha256-TE0XcWkJNwMVKonZ8y/c4kVLEp71A+45T5Fys2Jv2uI=", - "type": "git", - "url": "file:///home/diomidova/Pebble/pebble.nix" + "narHash": "sha256-FPMMcBAoeQgA/W92XTOAmTdDMmhbpNgsuSia7BMOBUs=", + "owner": "diomidova", + "repo": "pebble.nix", + "rev": "3fe382c507afd73a2c6a51ece280c3fb567ac53a", + "type": "github" }, "original": { - "type": "git", - "url": "file:///home/diomidova/Pebble/pebble.nix" + "owner": "diomidova", + "repo": "pebble.nix", + "type": "github" } }, "root": { "inputs": { "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", "pebble": "pebble" } }, diff --git a/flake.nix b/flake.nix index f00e9b3..901232c 100644 --- a/flake.nix +++ b/flake.nix @@ -8,10 +8,19 @@ }; outputs = - { pebble, flake-utils, ... }: - flake-utils.lib.eachDefaultSystem (system: { + { pebble, flake-utils, nixpkgs, ... }: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = import nixpkgs { + overlays = [ ]; + inherit system; + }; + in + { devShell = pebble.pebbleEnv.${system} { withCoreDevices = true; + packages = [ + pkgs.python312Packages.ipython + ]; }; }); } diff --git a/screenshots/basalt/01_white-black-black.png b/screenshots/basalt/01_white-black-black.png new file mode 100644 index 0000000..dbb0773 Binary files /dev/null and b/screenshots/basalt/01_white-black-black.png differ diff --git a/screenshots/basalt/02_black-white-gray.png b/screenshots/basalt/02_black-white-gray.png new file mode 100644 index 0000000..69e1338 Binary files /dev/null and b/screenshots/basalt/02_black-white-gray.png differ diff --git a/screenshots/basalt/03_yellow-blue-blue.png b/screenshots/basalt/03_yellow-blue-blue.png new file mode 100644 index 0000000..f7f9927 Binary files /dev/null and b/screenshots/basalt/03_yellow-blue-blue.png differ diff --git a/screenshots/chalk/01_white-black-black.png b/screenshots/chalk/01_white-black-black.png new file mode 100644 index 0000000..6327866 Binary files /dev/null and b/screenshots/chalk/01_white-black-black.png differ diff --git a/screenshots/chalk/02_black-white-gray.png b/screenshots/chalk/02_black-white-gray.png new file mode 100644 index 0000000..bd90cae Binary files /dev/null and b/screenshots/chalk/02_black-white-gray.png differ diff --git a/screenshots/chalk/03_yellow-blue-blue.png b/screenshots/chalk/03_yellow-blue-blue.png new file mode 100644 index 0000000..3829334 Binary files /dev/null and b/screenshots/chalk/03_yellow-blue-blue.png differ diff --git a/screenshots/diorite/01_white-black-black.png b/screenshots/diorite/01_white-black-black.png new file mode 100644 index 0000000..dbb0773 Binary files /dev/null and b/screenshots/diorite/01_white-black-black.png differ diff --git a/screenshots/diorite/02_black-white-gray.png b/screenshots/diorite/02_black-white-gray.png new file mode 100644 index 0000000..4f969cc Binary files /dev/null and b/screenshots/diorite/02_black-white-gray.png differ diff --git a/src/c/digit_layer.cpp b/src/c/digit_layer.cpp index b8addca..126b89f 100644 --- a/src/c/digit_layer.cpp +++ b/src/c/digit_layer.cpp @@ -15,11 +15,13 @@ namespace { void DigitLayerInner::OnUpdate(const Pebble::GContext& ctx) { auto bounds = this->GetBounds(); + // LOG("Digit layer update. %d digits", this->digitCount); ctx.SetCompositingMode(GCompOpSet); for (int i = 0; i <= this->digitCount + 2 * this->extraDigits; i++) { Pebble::GRect rect(0, i * this->digitHeight, bounds.size.w, this->digitHeight); uint16_t digit = ((i - this->extraDigits) % this->digitCount + this->digitCount) % this->digitCount % 10; + // LOG("Digit %d -> %d", i - this->extraDigits, digit); draw_digit(ctx, rect, digit); } } @@ -46,6 +48,7 @@ int16_t DigitLayer::ComputeOffset(uint16_t digit) { } void DigitLayer::RotateTo(uint16_t digit, bool animate) { + // LOG("Digit %d, out of %d", digit, innerLayer.digitCount); digit %= innerLayer.digitCount; auto currentOffset = this->scrollLayer.GetContentOffset(); if (digit == 0 && currentOffset.y <= ComputeOffset(this->innerLayer.digitCount / 2)) { diff --git a/src/c/digit_layer.hpp b/src/c/digit_layer.hpp index d3d3a32..82c68bd 100644 --- a/src/c/digit_layer.hpp +++ b/src/c/digit_layer.hpp @@ -9,11 +9,6 @@ class DigitLayerInner : public Pebble::CustomLayer { DigitLayerInner() : Pebble::CustomLayer(Pebble::GRect()) {} - void SetDigitCount(uint16_t cnt = 10) { - this->digitCount = cnt; - this->MarkDirty(); - } - static void DrawDigit(const Pebble::GContext& ctx, const Pebble::GRect& bounds, const uint16_t digit); friend class DigitLayer; @@ -29,8 +24,10 @@ public: DigitLayer(); void SetDigitCount(uint16_t cnt = 10) { - this->innerLayer.SetDigitCount(cnt); - this->MarkDirty(); + if (this->innerLayer.digitCount != cnt) { + this->innerLayer.digitCount = cnt; + this->UpdateLayout(); + } } void UpdateLayout(); void RotateTo(uint16_t digit, bool animate=true); diff --git a/src/c/main.cpp b/src/c/main.cpp index e55c761..2ae0a86 100644 --- a/src/c/main.cpp +++ b/src/c/main.cpp @@ -7,7 +7,8 @@ using namespace Pebble; -#undef DEBUG +// For debugging +const bool USE_SECONDS_INSTEAD_OF_MINUTES = false; class MainWindow : public AppWindow, public TickTimer { DigitLayer digitLayers[4]; @@ -22,10 +23,6 @@ protected: digitLayers[1].SetFrame(Pebble::GRect(w2-36, 0, 32, h)); digitLayers[2].SetFrame(Pebble::GRect(w2+4, 0, 32, h)); digitLayers[3].SetFrame(Pebble::GRect(w2+36, 0, 32, h)); - digitLayers[0].SetDigitCount(clock_is_24h_style() ? 3 : 2); - digitLayers[1].SetDigitCount(clock_is_24h_style() ? 24 : 12); - digitLayers[2].SetDigitCount(6); - digitLayers[3].SetDigitCount(10); for (size_t i = 0; i < 4; i++) { this->digitLayers[i].UpdateLayout(); this->AddChild(this->digitLayers[i]); @@ -36,23 +33,25 @@ protected: this->frameLayer.SetCompositingMode(GCompOpSet); this->AddChild(this->frameLayer); -#ifdef DEBUG - TickTimerStart(SECOND_UNIT); -#else - TickTimerStart(MINUTE_UNIT); -#endif + // MINUTE_UNIT doesn't seem to be called if only the hours change + // This can happen when time zone changes or in tests + // So we also update time on HOUR_UNIT + TickTimerStart(HOUR_UNIT); + TickTimerStart(USE_SECONDS_INSTEAD_OF_MINUTES ? SECOND_UNIT : MINUTE_UNIT); UpdateTime(false); } void UpdateTime(bool animate=true) { - DateTime date = DateTime::Now(); - int hour = date.GetHour(); -#ifdef DEBUG - int minute = date.GetSecond(); -#else - int minute = date.GetMinute(); -#endif - if (!clock_is_24h_style()) hour %= 12; + DateTime dateTime = DateTime::Now(); + int hour = dateTime.GetHour(); + int minute = USE_SECONDS_INSTEAD_OF_MINUTES ? dateTime.GetSecond() : dateTime.GetMinute(); + bool is_24h = clock_is_24h_style(); + if (!is_24h) hour %= 12; + + digitLayers[0].SetDigitCount(is_24h ? 3 : 2); + digitLayers[1].SetDigitCount(is_24h ? 24 : 12); + digitLayers[2].SetDigitCount(6); + digitLayers[3].SetDigitCount(10); this->digitLayers[0].RotateTo(hour / 10, animate); this->digitLayers[1].RotateTo(hour, animate); this->digitLayers[2].RotateTo(minute / 10, animate); @@ -66,7 +65,6 @@ protected: namespace { void ApplySettings() { - Pebble::Window window = window_stack_get_top_window(); window.SetBackgroundColor(settings::GetBackgroundColor()); window.MarkDirty(); diff --git a/src/c/settings.cpp b/src/c/settings.cpp index 08eabb1..1bccebb 100644 --- a/src/c/settings.cpp +++ b/src/c/settings.cpp @@ -31,14 +31,13 @@ namespace settings { } void HandleMsg(DictionaryIterator *iter) { - if (auto tuple = dict_find(iter, MESSAGE_KEY_BACKGROUND_COLOR)) backgroundColor = GColorFromHEX(tuple->value->int32); if (auto tuple = dict_find(iter, MESSAGE_KEY_DIGIT_COLOR)) digitColor = GColorFromHEX(tuple->value->int32); if (auto tuple = dict_find(iter, MESSAGE_KEY_FRAME_COLOR)) frameColor = GColorFromHEX(tuple->value->int32); Save(); } - GColor GetBackgroundColor() {return backgroundColor; } - GColor GetDigitColor() {return digitColor; } - GColor GetFrameColor() {return frameColor; } + GColor GetBackgroundColor() {return backgroundColor;} + GColor GetDigitColor() {return digitColor;} + GColor GetFrameColor() {return frameColor;} } \ No newline at end of file diff --git a/take_screenshots.py b/take_screenshots.py new file mode 100644 index 0000000..ccfe0c7 --- /dev/null +++ b/take_screenshots.py @@ -0,0 +1,141 @@ +# A simple script to automatically take app screenshots +# Hopefully some day it'll grow into an actual testing framework + +from uuid import UUID +from datetime import datetime, timezone +from time import sleep +import os +import png +import json + +from pebble_tool.util.logs import PebbleLogPrinter +from pebble_tool.sdk.emulator import ManagedEmulatorTransport +from pebble_tool.commands.emucontrol import send_data_to_qemu +from libpebble2.communication import PebbleConnection +from libpebble2.communication.transports.qemu.protocol import QemuButton, QemuTimeFormat +from libpebble2.services.install import AppInstaller +from libpebble2.services.appmessage import AppMessageService, Uint32 +from libpebble2.services.screenshot import Screenshot +from libpebble2.protocol.system import TimeMessage, SetUTC + +package_json = None +with open('package.json', 'r') as file: + package_json = json.load(file) + +app_uuid = UUID(package_json['pebble']['uuid']) +appname = package_json['name'] +target_platforms = package_json['pebble']['targetPlatforms'] +msg_keys = { + key: 10000 + i + for i, key in enumerate(package_json['pebble']['messageKeys']) +} + +def emulator(platform): + print(f'Launching `{platform}` emulator') + pebble = PebbleConnection(ManagedEmulatorTransport(platform)) + pebble.connect() + pebble.run_async() + print(f'Installing `{appname}.pbw`') + AppInstaller(pebble, f'./build/{appname}.pbw').install() + sleep(1) + return pebble + +def for_all_platforms(fn, logs=True): + for platform in target_platforms: + pebble = emulator(platform) + if logs: + logger = PebbleLogPrinter(pebble) + fn(pebble) + logger.stop() + else: + fn(pebble) + + +def is_color(pebble): + return pebble.watch_platform in ['basalt', 'chalk', 'emery'] + +def send_app_message(pebble, msg): + ams = AppMessageService(pebble) + tid = ams.send_message(app_uuid, msg) + + # unfortunately the following doesn't work + # wait_for_event assumes only one value is returned + # but ack returns 2 :( + + # tid2, uuid = ams.wait_for_event('ack') + # assert uuid == app_uuid + # assert tid2 == tid + +def set_time(pebble, hour, minute, year=2013, month=1, day=23, tzinfo=timezone.utc): + dt = int(datetime(year, month, day, hour, minute, tzinfo=tzinfo).timestamp()) + pebble.send_packet(TimeMessage(message=SetUTC(unix_time=dt, utc_offset=0, tz_name="UTC"))) + +def press_buttons(pebble, buttons, duration=0.1): + send_data_to_qemu(pebble.transport, QemuButton(state=buttons)) + sleep(duration) + send_data_to_qemu(pebble.transport, QemuButton(state=0)) + sleep(0.1) + +def press_back(pebble, duration=0.1): + press_buttons(pebble, QemuButton.Button.Back, duration) +def press_up(pebble, duration=0.1): + press_buttons(pebble, QemuButton.Button.Up, duration) +def press_select(pebble, duration=0.1): + press_buttons(pebble, QemuButton.Button.Select, duration) +def press_down(pebble, duration=0.1): + press_buttons(pebble, QemuButton.Button.Down, duration) + +def set_24_hour_mode(pebble, is_24_hour=True): + send_data_to_qemu(pebble.transport, QemuTimeFormat(is_24_hour=is_24_hour)) + +def screenshot(pebble, name): + filename = f'{pebble.watch_platform}/{name}.png' + path = './screenshots/' + filename + print(f'Taking screenshot `{filename}`') + os.makedirs(os.path.dirname(path), exist_ok=True) + image = Screenshot(pebble).grab_image() + png.from_array(image, 'RGB').save(path) + +# ============================================================ +# Code above this line is not specific to the watchface and +# should probably be eventually split off into its own library +# ============================================================ + +def set_colors(pebble, background, digits, frame): + def wrap_color(color): + if isinstance(color, int): + color = hex(color) + color = color.removeprefix('0x').removeprefix('#') + if len(color) == 3: + r, g, b = color + color = r + r + g + g + b + b + return Uint32(int(color, 16)) + send_app_message(pebble, { + msg_keys["BACKGROUND_COLOR"]: wrap_color(background), + msg_keys["DIGIT_COLOR"]: wrap_color(digits), + msg_keys["FRAME_COLOR"]: wrap_color(frame), + }) + +def run(pebble): + set_24_hour_mode(pebble, True) + set_time(pebble, 12, 34) + set_colors(pebble, 'FFF', '000', '000') + sleep(1.25) + screenshot(pebble, '01_white-black-black') + + set_24_hour_mode(pebble, False) + set_time(pebble, 16, 59) + set_colors(pebble, '000', 'FFF', 'AAA') + sleep(1.25) + screenshot(pebble, '02_black-white-gray') + + if is_color(pebble): + set_24_hour_mode(pebble, True) + set_time(pebble, 4, 20) + set_colors(pebble, 'FF5', '00F', '00A') + sleep(1.25) + screenshot(pebble, '03_yellow-blue-blue') + + +if __name__ == '__main__': + for_all_platforms(run) \ No newline at end of file