Compare commits

...

3 Commits

Author SHA1 Message Date
Jenny Diomidova
531d5bdb6f add a python script to automatic generate all screenshots 2025-12-10 16:06:21 +01:00
Jenny Diomidova
d30dfa5d57 add color settings 2025-12-09 22:44:41 +01:00
Jenny Diomidova
eff33f57a1 add clay 2025-12-09 15:47:01 +01:00
24 changed files with 422 additions and 137 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

32
flake.lock generated
View File

@ -113,6 +113,20 @@
} }
}, },
"nixpkgs": { "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": { "locked": {
"lastModified": 1734649271, "lastModified": 1734649271,
"narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=", "narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=",
@ -133,24 +147,26 @@
"commit-hooks": "commit-hooks", "commit-hooks": "commit-hooks",
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"dirtyRev": "3fe382c507afd73a2c6a51ece280c3fb567ac53a-dirty",
"dirtyShortRev": "3fe382c-dirty",
"lastModified": 1764961369, "lastModified": 1764961369,
"narHash": "sha256-TE0XcWkJNwMVKonZ8y/c4kVLEp71A+45T5Fys2Jv2uI=", "narHash": "sha256-FPMMcBAoeQgA/W92XTOAmTdDMmhbpNgsuSia7BMOBUs=",
"type": "git", "owner": "diomidova",
"url": "file:///home/diomidova/Pebble/pebble.nix" "repo": "pebble.nix",
"rev": "3fe382c507afd73a2c6a51ece280c3fb567ac53a",
"type": "github"
}, },
"original": { "original": {
"type": "git", "owner": "diomidova",
"url": "file:///home/diomidova/Pebble/pebble.nix" "repo": "pebble.nix",
"type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"pebble": "pebble" "pebble": "pebble"
} }
}, },

View File

@ -8,10 +8,19 @@
}; };
outputs = outputs =
{ pebble, flake-utils, ... }: { pebble, flake-utils, nixpkgs, ... }:
flake-utils.lib.eachDefaultSystem (system: { flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs {
overlays = [ ];
inherit system;
};
in
{
devShell = pebble.pebbleEnv.${system} { devShell = pebble.pebbleEnv.${system} {
withCoreDevices = true; withCoreDevices = true;
packages = [
pkgs.python312Packages.ipython
];
}; };
}); });
} }

7
package-lock.json generated
View File

@ -10,6 +10,7 @@
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@rebble/sdk-c-utils": "^1.2.3", "@rebble/sdk-c-utils": "^1.2.3",
"pebble-clay": "^1.0.4",
"pebble-cpp": "^0.3.1" "pebble-cpp": "^0.3.1"
} }
}, },
@ -19,6 +20,12 @@
"integrity": "sha512-24b/qtVpviGJyVhvQYTsFojRn30YcVSepeCVbi7AqrNPfitGNC2EqwLLpKZuH1QTnE6UcTgjLrh6yda7p6UvAg==", "integrity": "sha512-24b/qtVpviGJyVhvQYTsFojRn30YcVSepeCVbi7AqrNPfitGNC2EqwLLpKZuH1QTnE6UcTgjLrh6yda7p6UvAg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/pebble-clay": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pebble-clay/-/pebble-clay-1.0.4.tgz",
"integrity": "sha512-/rXxmltdW8JyohDzXINdea+d2wnFJVNFiTXfuZsKpySURZSCFMMucX9sZPZvbHnEA4xFINM4iicyhBbvY4ALfw==",
"license": "MIT"
},
"node_modules/pebble-cpp": { "node_modules/pebble-cpp": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/pebble-cpp/-/pebble-cpp-0.3.1.tgz", "resolved": "https://registry.npmjs.org/pebble-cpp/-/pebble-cpp-0.3.1.tgz",

View File

@ -9,6 +9,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@rebble/sdk-c-utils": "^1.2.3", "@rebble/sdk-c-utils": "^1.2.3",
"pebble-clay": "^1.0.4",
"pebble-cpp": "^0.3.1" "pebble-cpp": "^0.3.1"
}, },
"pebble": { "pebble": {
@ -25,8 +26,11 @@
"watchface": true "watchface": true
}, },
"messageKeys": [ "messageKeys": [
"dummy" "BACKGROUND_COLOR",
"DIGIT_COLOR",
"FRAME_COLOR"
], ],
"capabilities": [ "configurable" ],
"resources": { "resources": {
"media": [ "media": [
{ {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -4,95 +4,97 @@
using namespace Pebble; using namespace Pebble;
// TODO move this to pebble-cpp // TODO move this to pebble-cpp
bool operator==(const GColor8& lhs, const GColor8& rhs) { return lhs.argb == rhs.argb; } bool operator==(const GColor& lhs, const GColor& rhs) { return gcolor_equal(lhs, rhs); }
bool operator!=(const GColor8& lhs, const GColor8& rhs) { return lhs.argb != rhs.argb; } bool operator!=(const GColor& lhs, const GColor& rhs) { return !gcolor_equal(lhs, rhs); }
namespace { namespace bitmaps {
GOwnedBitmap digitsBitmap; namespace {
GOwnedBitmap digitBitmaps[10]; GOwnedBitmap digitsBitmap;
GOwnedBitmap frameBitmap; GOwnedBitmap digitBitmaps[10];
GOwnedBitmap frameBitmap;
GColor* digitColor; GColor* digitColor;
GColor* frameColor; GColor* frameColor;
int get_palette_size(GBitmapBase& bitmap){ int GetPaletteSize(GBitmapBase& bitmap){
switch (bitmap.GetFormat()) { switch (bitmap.GetFormat()) {
case GBitmapFormat1BitPalette: return 2; case GBitmapFormat1BitPalette: return 2;
case GBitmapFormat2BitPalette: return 4; case GBitmapFormat2BitPalette: return 4;
case GBitmapFormat4BitPalette: return 16; case GBitmapFormat4BitPalette: return 16;
default: return 0; default: return 0;
}
}
GColor* find_color(GBitmapBase& bitmap, GColor color) {
GColor* palette = bitmap.GetPalette();
if (palette == nullptr) {
ERROR("Palette is null");
return nullptr;
}
int paletteSize = get_palette_size(bitmap);
if (paletteSize == 0) {
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
return nullptr;
}
for (int i = 0; i < paletteSize; i++) {
if (palette[i] == color) {
DEBUG("Found color 0x%02X at index %d", color, i);
return &palette[i];
} }
} }
ERROR("Color 0x%02X not found in the palette", color);
return nullptr;
}
}
void init_bitmaps() { GColor* FindColor(GBitmapBase& bitmap, GColor color) {
digitsBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_DIGITS); GColor* palette = bitmap.GetPalette();
auto size = digitsBitmap.GetBounds().size; if (palette == nullptr) {
uint16_t w = size.w / 10; ERROR("Palette is null");
uint16_t h = size.h; return nullptr;
for (int i = 0; i < 10; i++) { }
digitBitmaps[i] = GOwnedBitmap::CreateSubBitmap(digitsBitmap, Pebble::GRect(i * w, 0, w, h));
int paletteSize = GetPaletteSize(bitmap);
if (paletteSize == 0) {
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
return nullptr;
}
for (int i = 0; i < paletteSize; i++) {
if (palette[i] == color) {
DEBUG("Found color 0x%02X at index %d", color, i);
return &palette[i];
}
}
ERROR("Color 0x%02X not found in the palette", color);
return nullptr;
}
} }
frameBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_FRAME); void Init() {
digitsBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_DIGITS);
auto size = digitsBitmap.GetBounds().size;
uint16_t w = size.w / 10;
uint16_t h = size.h;
for (int i = 0; i < 10; i++) {
digitBitmaps[i] = GOwnedBitmap::CreateSubBitmap(digitsBitmap, Pebble::GRect(i * w, 0, w, h));
}
digitColor = find_color(digitsBitmap, GColorBlack); frameBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_FRAME);
frameColor = find_color(frameBitmap, GColorBlack);
}
void deinit_bitmaps() { digitColor = FindColor(digitsBitmap, GColorBlack);
for (int i = 0; i < 10; i++) { frameColor = FindColor(frameBitmap, GColorBlack);
digitBitmaps[i] = GOwnedBitmap();
} }
digitsBitmap = GOwnedBitmap();
frameBitmap = GOwnedBitmap();
digitColor = nullptr;
frameColor = nullptr;
}
GBitmapBase& get_digit_bitmap(uint16_t digit) {return digitBitmaps[digit];} void DeInit() {
GBitmapBase& get_frame_bitmap() {return frameBitmap;} for (int i = 0; i < 10; i++) {
digitBitmaps[i] = GOwnedBitmap();
}
void set_digit_color(GColor color) { digitsBitmap = GOwnedBitmap();
if (digitColor != nullptr) *digitColor = color; frameBitmap = GOwnedBitmap();
} digitColor = nullptr;
void set_frame_color(GColor color) { frameColor = nullptr;
if (frameColor != nullptr) *frameColor = color;
}
void print_palette(GBitmapBase& bitmap) {
int paletteSize = get_palette_size(bitmap);
if (paletteSize == 0) {
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
return;
} }
DEBUG("%d colors:", paletteSize);
GColor8* palette = bitmap.GetPalette(); GBitmapBase& GetDigitBitmap(uint16_t digit) {return digitBitmaps[digit];}
for (int i = 0; i < paletteSize; i++) { GBitmapBase& GetFrameBitmap() {return frameBitmap;}
DEBUG("* 0x%02X = argb(%d,%d,%d,%d)", palette[i].argb, palette[i].a, palette[i].r, palette[i].g, palette[i].b);
void SetDigitColor(GColor color) {
if (digitColor != nullptr) *digitColor = color;
}
void SetFrameColor(GColor color) {
if (frameColor != nullptr) *frameColor = color;
}
void PrintPalette(GBitmapBase& bitmap) {
int paletteSize = GetPaletteSize(bitmap);
if (paletteSize == 0) {
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
return;
}
DEBUG("%d colors:", paletteSize);
GColor* palette = bitmap.GetPalette();
for (int i = 0; i < paletteSize; i++) {
DEBUG("* 0x%02X = argb(%d,%d,%d,%d)", palette[i].argb, palette[i].a, palette[i].r, palette[i].g, palette[i].b);
}
} }
} }

View File

@ -2,10 +2,12 @@
#include <pebble-cpp/pebble.hpp> #include <pebble-cpp/pebble.hpp>
void init_bitmaps(); namespace bitmaps {
void deinit_bitmaps(); void Init();
Pebble::GBitmapBase& get_digit_bitmap(uint16_t digit); void DeInit();
Pebble::GBitmapBase& get_frame_bitmap(); Pebble::GBitmapBase& GetDigitBitmap(uint16_t digit);
void set_digit_color(GColor color); Pebble::GBitmapBase& GetFrameBitmap();
void set_frame_color(GColor color); void SetDigitColor(GColor color);
void print_palette(Pebble::GBitmapBase& bitmap); void SetFrameColor(GColor color);
void PrintPalette(Pebble::GBitmapBase& bitmap);
}

View File

@ -1,12 +1,12 @@
#include "digit_layer.hpp" #include "digit_layer.hpp"
#include "bitmaps.hpp"
#include <@rebble/sdk-c-utils/macros.h> #include <@rebble/sdk-c-utils/macros.h>
#include "bitmaps.hpp"
using namespace Pebble; using namespace Pebble;
namespace { namespace {
void draw_digit(const Pebble::GContext& ctx, const Pebble::GRect& bounds, const uint16_t digit) { void draw_digit(const Pebble::GContext& ctx, const Pebble::GRect& bounds, const uint16_t digit) {
Pebble::GBitmapBase& bitmap = get_digit_bitmap(digit); Pebble::GBitmapBase& bitmap = bitmaps::GetDigitBitmap(digit);
auto rect = bitmap.GetBounds(); auto rect = bitmap.GetBounds();
rect.Align(bounds, GAlignCenter, false); rect.Align(bounds, GAlignCenter, false);
ctx.DrawBitmapInRect(bitmap, rect); ctx.DrawBitmapInRect(bitmap, rect);
@ -15,11 +15,13 @@ namespace {
void DigitLayerInner::OnUpdate(const Pebble::GContext& ctx) { void DigitLayerInner::OnUpdate(const Pebble::GContext& ctx) {
auto bounds = this->GetBounds(); auto bounds = this->GetBounds();
// LOG("Digit layer update. %d digits", this->digitCount);
ctx.SetCompositingMode(GCompOpSet); ctx.SetCompositingMode(GCompOpSet);
for (int i = 0; i <= this->digitCount + 2 * this->extraDigits; i++) { for (int i = 0; i <= this->digitCount + 2 * this->extraDigits; i++) {
Pebble::GRect rect(0, i * this->digitHeight, bounds.size.w, this->digitHeight); 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; 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); draw_digit(ctx, rect, digit);
} }
} }
@ -46,6 +48,7 @@ int16_t DigitLayer::ComputeOffset(uint16_t digit) {
} }
void DigitLayer::RotateTo(uint16_t digit, bool animate) { void DigitLayer::RotateTo(uint16_t digit, bool animate) {
// LOG("Digit %d, out of %d", digit, innerLayer.digitCount);
digit %= innerLayer.digitCount; digit %= innerLayer.digitCount;
auto currentOffset = this->scrollLayer.GetContentOffset(); auto currentOffset = this->scrollLayer.GetContentOffset();
if (digit == 0 && currentOffset.y <= ComputeOffset(this->innerLayer.digitCount / 2)) { if (digit == 0 && currentOffset.y <= ComputeOffset(this->innerLayer.digitCount / 2)) {

View File

@ -9,11 +9,6 @@ class DigitLayerInner : public Pebble::CustomLayer {
DigitLayerInner() : Pebble::CustomLayer(Pebble::GRect()) {} 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); static void DrawDigit(const Pebble::GContext& ctx, const Pebble::GRect& bounds, const uint16_t digit);
friend class DigitLayer; friend class DigitLayer;
@ -29,8 +24,10 @@ public:
DigitLayer(); DigitLayer();
void SetDigitCount(uint16_t cnt = 10) { void SetDigitCount(uint16_t cnt = 10) {
this->innerLayer.SetDigitCount(cnt); if (this->innerLayer.digitCount != cnt) {
this->MarkDirty(); this->innerLayer.digitCount = cnt;
this->UpdateLayout();
}
} }
void UpdateLayout(); void UpdateLayout();
void RotateTo(uint16_t digit, bool animate=true); void RotateTo(uint16_t digit, bool animate=true);

View File

@ -3,18 +3,18 @@
#include "pebble_bitmap_layer.hpp" #include "pebble_bitmap_layer.hpp"
#include "digit_layer.hpp" #include "digit_layer.hpp"
#include "bitmaps.hpp" #include "bitmaps.hpp"
#include "settings.hpp"
using namespace Pebble; using namespace Pebble;
#undef DEBUG // For debugging
const bool USE_SECONDS_INSTEAD_OF_MINUTES = false;
class MainWindow : public AppWindow, public TickTimer { class MainWindow : public AppWindow, public TickTimer {
DigitLayer digitLayers[4]; DigitLayer digitLayers[4];
Pebble::BitmapLayer frameLayer; Pebble::BitmapLayer frameLayer;
protected: protected:
void OnLoad() { void OnLoad() {
init_bitmaps();
auto bounds = this->GetBounds(); auto bounds = this->GetBounds();
int16_t w2 = bounds.size.w / 2; int16_t w2 = bounds.size.w / 2;
int16_t h = bounds.size.h; int16_t h = bounds.size.h;
@ -23,50 +23,35 @@ protected:
digitLayers[1].SetFrame(Pebble::GRect(w2-36, 0, 32, h)); digitLayers[1].SetFrame(Pebble::GRect(w2-36, 0, 32, h));
digitLayers[2].SetFrame(Pebble::GRect(w2+4, 0, 32, h)); digitLayers[2].SetFrame(Pebble::GRect(w2+4, 0, 32, h));
digitLayers[3].SetFrame(Pebble::GRect(w2+36, 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++) { for (size_t i = 0; i < 4; i++) {
this->digitLayers[i].UpdateLayout(); this->digitLayers[i].UpdateLayout();
this->AddChild(this->digitLayers[i]); this->AddChild(this->digitLayers[i]);
} }
// print_palette(get_digit_bitmap(0)); this->frameLayer.SetBitmap(bitmaps::GetFrameBitmap());
// print_palette(get_frame_bitmap());
// set_frame_color(GColorPictonBlue);
// print_palette(get_digit_bitmap(0));
// print_palette(get_frame_bitmap());
this->frameLayer.SetBitmap(get_frame_bitmap());
this->frameLayer.SetFrame(bounds); this->frameLayer.SetFrame(bounds);
this->frameLayer.SetCompositingMode(GCompOpSet); this->frameLayer.SetCompositingMode(GCompOpSet);
this->AddChild(this->frameLayer); this->AddChild(this->frameLayer);
#ifdef DEBUG // MINUTE_UNIT doesn't seem to be called if only the hours change
TickTimerStart(SECOND_UNIT); // This can happen when time zone changes or in tests
#else // So we also update time on HOUR_UNIT
TickTimerStart(MINUTE_UNIT); TickTimerStart(HOUR_UNIT);
#endif TickTimerStart(USE_SECONDS_INSTEAD_OF_MINUTES ? SECOND_UNIT : MINUTE_UNIT);
UpdateTime(false); UpdateTime(false);
this->SetBackgroundColor(GColorBlack);
set_digit_color(GColorVividCerulean);
set_frame_color(GColorPictonBlue);
}
void OnUnload() {
deinit_bitmaps();
} }
void UpdateTime(bool animate=true) { void UpdateTime(bool animate=true) {
DateTime date = DateTime::Now(); DateTime dateTime = DateTime::Now();
int hour = date.GetHour(); int hour = dateTime.GetHour();
#ifdef DEBUG int minute = USE_SECONDS_INSTEAD_OF_MINUTES ? dateTime.GetSecond() : dateTime.GetMinute();
int minute = date.GetSecond(); bool is_24h = clock_is_24h_style();
#else if (!is_24h) hour %= 12;
int minute = date.GetMinute();
#endif digitLayers[0].SetDigitCount(is_24h ? 3 : 2);
if (!clock_is_24h_style()) hour %= 12; digitLayers[1].SetDigitCount(is_24h ? 24 : 12);
digitLayers[2].SetDigitCount(6);
digitLayers[3].SetDigitCount(10);
this->digitLayers[0].RotateTo(hour / 10, animate); this->digitLayers[0].RotateTo(hour / 10, animate);
this->digitLayers[1].RotateTo(hour, animate); this->digitLayers[1].RotateTo(hour, animate);
this->digitLayers[2].RotateTo(minute / 10, animate); this->digitLayers[2].RotateTo(minute / 10, animate);
@ -78,9 +63,34 @@ protected:
} }
}; };
namespace {
void ApplySettings() {
Pebble::Window window = window_stack_get_top_window();
window.SetBackgroundColor(settings::GetBackgroundColor());
window.MarkDirty();
bitmaps::SetDigitColor(settings::GetDigitColor());
bitmaps::SetFrameColor(settings::GetFrameColor());
}
void HandleMsg(DictionaryIterator *iter, void *context) {
settings::HandleMsg(iter);
ApplySettings();
}
}
MAIN { MAIN {
bitmaps::Init();
settings::Load();
app_message_register_inbox_received(HandleMsg);
app_message_open(64, 64); // inbox, outbox
MainWindow window; MainWindow window;
window.StackPush(true); window.StackPush(true);
ApplySettings();
app_event_loop(); app_event_loop();
bitmaps::DeInit();
return 0; return 0;
} }

43
src/c/settings.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "settings.hpp"
// Built-in GColorFromRGBA doesn't seem to work in C++11 :(
#undef GColorFromRGBA
GColor8 GColorFromRGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
GColor8 res;
res.a = alpha >> 6;
res.r = red >> 6;
res.g = green >> 6;
res.b = blue >> 6;
return res;
}
namespace settings {
namespace {
GColor backgroundColor = GColorWhite;
GColor digitColor = GColorBlack;
GColor frameColor = GColorBlack;
}
void Save() {
persist_write_data(MESSAGE_KEY_BACKGROUND_COLOR, &backgroundColor, sizeof(GColor));
persist_write_data(MESSAGE_KEY_DIGIT_COLOR, &digitColor, sizeof(GColor));
persist_write_data(MESSAGE_KEY_FRAME_COLOR, &frameColor, sizeof(GColor));
}
void Load() {
persist_read_data(MESSAGE_KEY_BACKGROUND_COLOR, &backgroundColor, sizeof(GColor));
persist_read_data(MESSAGE_KEY_DIGIT_COLOR, &digitColor, sizeof(GColor));
persist_read_data(MESSAGE_KEY_FRAME_COLOR, &frameColor, sizeof(GColor));
}
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;}
}

12
src/c/settings.hpp Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <pebble-cpp/pebble.hpp>
namespace settings {
void Save();
void Load();
void HandleMsg(DictionaryIterator *iter);
GColor GetBackgroundColor();
GColor GetDigitColor();
GColor GetFrameColor();
}

36
src/pkjs/config.js Normal file
View File

@ -0,0 +1,36 @@
module.exports = [
{
type: "section",
items: [
{
type: "heading",
defaultValue: "Colors",
},
{
label: "Background Color",
messageKey: "BACKGROUND_COLOR",
type: "color",
defaultValue: "#FFFFFF",
allowGray: true,
},
{
label: "Digit Color",
messageKey: "DIGIT_COLOR",
type: "color",
defaultValue: "#000000",
allowGray: true,
},
{
label: "Frame Color",
messageKey: "FRAME_COLOR",
type: "color",
defaultValue: "#00000",
allowGray: true,
},
]
},
{
type: "submit",
defaultValue: "Save Settings",
},
];

3
src/pkjs/index.js Normal file
View File

@ -0,0 +1,3 @@
var Clay = require('pebble-clay');
var clayConfig = require('./config');
var clay = new Clay(clayConfig);

141
take_screenshots.py Normal file
View File

@ -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)