Compare commits
3 Commits
d20c12fec6
...
531d5bdb6f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
531d5bdb6f | ||
|
|
d30dfa5d57 | ||
|
|
eff33f57a1 |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
32
flake.lock
generated
@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
13
flake.nix
@ -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
@ -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",
|
||||||
|
|||||||
@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
screenshots/basalt/01_white-black-black.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
screenshots/basalt/02_black-white-gray.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
screenshots/basalt/03_yellow-blue-blue.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
screenshots/chalk/01_white-black-black.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
screenshots/chalk/02_black-white-gray.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
screenshots/chalk/03_yellow-blue-blue.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
screenshots/diorite/01_white-black-black.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
screenshots/diorite/02_black-white-gray.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
@ -4,9 +4,10 @@
|
|||||||
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 bitmaps {
|
||||||
namespace {
|
namespace {
|
||||||
GOwnedBitmap digitsBitmap;
|
GOwnedBitmap digitsBitmap;
|
||||||
GOwnedBitmap digitBitmaps[10];
|
GOwnedBitmap digitBitmaps[10];
|
||||||
@ -16,7 +17,7 @@ namespace {
|
|||||||
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;
|
||||||
@ -25,14 +26,14 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GColor* find_color(GBitmapBase& bitmap, GColor color) {
|
GColor* FindColor(GBitmapBase& bitmap, GColor color) {
|
||||||
GColor* palette = bitmap.GetPalette();
|
GColor* palette = bitmap.GetPalette();
|
||||||
if (palette == nullptr) {
|
if (palette == nullptr) {
|
||||||
ERROR("Palette is null");
|
ERROR("Palette is null");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int paletteSize = get_palette_size(bitmap);
|
int paletteSize = GetPaletteSize(bitmap);
|
||||||
if (paletteSize == 0) {
|
if (paletteSize == 0) {
|
||||||
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
|
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -48,7 +49,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_bitmaps() {
|
void Init() {
|
||||||
digitsBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_DIGITS);
|
digitsBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_DIGITS);
|
||||||
auto size = digitsBitmap.GetBounds().size;
|
auto size = digitsBitmap.GetBounds().size;
|
||||||
uint16_t w = size.w / 10;
|
uint16_t w = size.w / 10;
|
||||||
@ -59,11 +60,11 @@ void init_bitmaps() {
|
|||||||
|
|
||||||
frameBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_FRAME);
|
frameBitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_FRAME);
|
||||||
|
|
||||||
digitColor = find_color(digitsBitmap, GColorBlack);
|
digitColor = FindColor(digitsBitmap, GColorBlack);
|
||||||
frameColor = find_color(frameBitmap, GColorBlack);
|
frameColor = FindColor(frameBitmap, GColorBlack);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deinit_bitmaps() {
|
void DeInit() {
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
digitBitmaps[i] = GOwnedBitmap();
|
digitBitmaps[i] = GOwnedBitmap();
|
||||||
}
|
}
|
||||||
@ -73,26 +74,27 @@ void deinit_bitmaps() {
|
|||||||
frameColor = nullptr;
|
frameColor = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
GBitmapBase& get_digit_bitmap(uint16_t digit) {return digitBitmaps[digit];}
|
GBitmapBase& GetDigitBitmap(uint16_t digit) {return digitBitmaps[digit];}
|
||||||
GBitmapBase& get_frame_bitmap() {return frameBitmap;}
|
GBitmapBase& GetFrameBitmap() {return frameBitmap;}
|
||||||
|
|
||||||
|
|
||||||
void set_digit_color(GColor color) {
|
void SetDigitColor(GColor color) {
|
||||||
if (digitColor != nullptr) *digitColor = color;
|
if (digitColor != nullptr) *digitColor = color;
|
||||||
}
|
}
|
||||||
void set_frame_color(GColor color) {
|
void SetFrameColor(GColor color) {
|
||||||
if (frameColor != nullptr) *frameColor = color;
|
if (frameColor != nullptr) *frameColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_palette(GBitmapBase& bitmap) {
|
void PrintPalette(GBitmapBase& bitmap) {
|
||||||
int paletteSize = get_palette_size(bitmap);
|
int paletteSize = GetPaletteSize(bitmap);
|
||||||
if (paletteSize == 0) {
|
if (paletteSize == 0) {
|
||||||
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
|
ERROR("Expected GBitmapFormat[124]BitPalette, got: %d", digitsBitmap.GetFormat());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DEBUG("%d colors:", paletteSize);
|
DEBUG("%d colors:", paletteSize);
|
||||||
GColor8* palette = bitmap.GetPalette();
|
GColor* palette = bitmap.GetPalette();
|
||||||
for (int i = 0; i < paletteSize; i++) {
|
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);
|
DEBUG("* 0x%02X = argb(%d,%d,%d,%d)", palette[i].argb, palette[i].a, palette[i].r, palette[i].g, palette[i].b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
@ -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)) {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,3 @@
|
|||||||
|
var Clay = require('pebble-clay');
|
||||||
|
var clayConfig = require('./config');
|
||||||
|
var clay = new Clay(clayConfig);
|
||||||
141
take_screenshots.py
Normal 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)
|
||||||