initial commit

This commit is contained in:
Jenny Diomidova 2025-12-08 12:21:57 +01:00
commit 643487f4b3
16 changed files with 583 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
node_modules
.venv
.lock-waf_*_build

0
README.md Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

190
flake.lock generated Normal file
View File

@ -0,0 +1,190 @@
{
"nodes": {
"commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"pebble",
"nixpkgs"
]
},
"locked": {
"lastModified": 1737465171,
"narHash": "sha256-R10v2hoJRLq8jcL4syVFag7nIGE7m13qO48wRIukWNg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pebble",
"commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1734649271,
"narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d70bd19e0a38ad4790d3913bf08fcbfc9eeca507",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pebble": {
"inputs": {
"commit-hooks": "commit-hooks",
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"dirtyRev": "3fe382c507afd73a2c6a51ece280c3fb567ac53a-dirty",
"dirtyShortRev": "3fe382c-dirty",
"lastModified": 1764961369,
"narHash": "sha256-TE0XcWkJNwMVKonZ8y/c4kVLEp71A+45T5Fys2Jv2uI=",
"type": "git",
"url": "file:///home/diomidova/Pebble/pebble.nix"
},
"original": {
"type": "git",
"url": "file:///home/diomidova/Pebble/pebble.nix"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"pebble": "pebble"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

17
flake.nix Normal file
View File

@ -0,0 +1,17 @@
{
description = "Cryptic Time";
inputs = {
# pebble.url = "github:pebble-dev/pebble.nix";
pebble.url = "github:diomidov/pebble.nix";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{ pebble, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system: {
devShell = pebble.pebbleEnv.${system} {
withCoreDevices = true;
};
});
}

28
package-lock.json generated Normal file
View File

@ -0,0 +1,28 @@
{
"name": "cryptic-clock",
"version": "1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cryptic-clock",
"version": "1.1",
"dependencies": {
"@smallstoneapps/utils": "^1.3.0",
"pebble-cpp": "^0.3.1"
}
},
"node_modules/@smallstoneapps/utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@smallstoneapps/utils/-/utils-1.3.0.tgz",
"integrity": "sha512-2XSH7umjEtXqdBTil8yhdLRjw259ZgUhn/c6f/4vW5RlM/jo4J+Hoygc+tr4z8Wv5ymnFvxfNq2MpqTqNM+TTg==",
"license": "MIT"
},
"node_modules/pebble-cpp": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/pebble-cpp/-/pebble-cpp-0.3.1.tgz",
"integrity": "sha512-lR6/icexx5ol4JfowONUflfKofR8wl6fQB3U1b4OwH1S9Iei12UNv9s7X+gSDSsOoOLSl1fPyf6CGbooH7rxbg==",
"license": "Apache-2.0"
}
}
}

50
package.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "cryptic-clock",
"author": "jennyaa",
"version": "1.1",
"keywords": [
"pebble-app"
],
"private": true,
"dependencies": {
"@smallstoneapps/utils": "^1.3.0",
"pebble-cpp": "^0.3.1"
},
"pebble": {
"displayName": "Cryptic Time",
"uuid": "53d1d465-fafe-4e7a-8b4f-44382b32e384",
"sdkVersion": "3",
"enableMultiJS": true,
"targetPlatforms": [
"basalt",
"chalk",
"diorite"
],
"watchapp": {
"watchface": true
},
"messageKeys": [
"dummy"
],
"resources": {
"media": [
{
"type": "bitmap",
"name": "DIGITS",
"file": "digits.png"
},
{
"type": "bitmap",
"name": "FRAME",
"file": "frame.png"
},
{
"type": "bitmap",
"name": "MENU_ICON",
"file": "icon.png",
"menuIcon": true
}
]
}
}
}

BIN
resources/digits.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
resources/frame.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

72
src/c/cryptic_clock.cpp Normal file
View File

@ -0,0 +1,72 @@
#include <pebble-cpp/pebble.hpp>
#include "pebble_bitmap_layer.hpp"
#include <@smallstoneapps/utils/macros.h>
#include "digit_layer.hpp"
using namespace Pebble;
#undef DEBUG
class MainWindow : public AppWindow, public TickTimer {
DigitLayer digitLayers[4];
GOwnedBitmap frameBitmap;
Pebble::BitmapLayer frameLayer;
protected:
void OnLoad() {
auto bounds = this->GetBounds();
int16_t w2 = bounds.size.w / 2;
int16_t h = bounds.size.h;
digitLayers[0].SetFrame(Pebble::GRect(w2-68, 0, 32, h));
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]);
}
this->frameBitmap = Pebble::GOwnedBitmap::CreateWithResource(RESOURCE_ID_FRAME);
this->frameLayer.SetBitmap(this->frameBitmap);
this->frameLayer.SetFrame(bounds);
this->frameLayer.SetCompositingMode(GCompOpSet);
this->AddChild(this->frameLayer);
#ifdef DEBUG
TickTimerStart(SECOND_UNIT);
#else
TickTimerStart(MINUTE_UNIT);
#endif
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;
this->digitLayers[0].RotateTo(hour / 10, animate);
this->digitLayers[1].RotateTo(hour, animate);
this->digitLayers[2].RotateTo(minute / 10, animate);
this->digitLayers[3].RotateTo(minute, animate);
}
void OnTick(tm &tick_time, TimeUnits units_changed) {
UpdateTime(true);
}
};
MAIN {
MainWindow window;
window.StackPush(true);
app_event_loop();
return 0;
}

73
src/c/digit_layer.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "digit_layer.hpp"
#include <@smallstoneapps/utils/macros.h>
using namespace Pebble;
namespace {
// NOTE: technically a memory leak, destructor will never be called.
// Can't make it a static variable in draw_digit, because `atexit` is broken on Pebble.
static GOwnedBitmap digitBitmaps[10];
static bool bitmapsInitialized = false;
static void draw_digit(const Pebble::GContext& ctx, const Pebble::GRect& bounds, const uint16_t digit) {
if (!bitmapsInitialized) {
auto bitmap = GOwnedBitmap::CreateWithResource(RESOURCE_ID_DIGITS);
auto size = bitmap.GetBounds().size;
uint16_t w = size.w / 10;
uint16_t h = size.h;
for (int i = 0; i < 10; i++) {
digitBitmaps[i] = GOwnedBitmap::CreateSubBitmap(bitmap, Pebble::GRect(i * w, 0, w, h));
}
bitmapsInitialized = true;
}
auto bitmap = &digitBitmaps[digit];
auto rect = bitmap->GetBounds();
rect.Align(bounds, GAlignCenter, false);
ctx.DrawBitmapInRect(*bitmap, rect);
}
}
void DigitLayerInner::OnUpdate(const Pebble::GContext& ctx) {
auto bounds = this->GetBounds();
ctx.SetFillColor(GColorBlack);
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;
draw_digit(ctx, rect, digit);
}
}
DigitLayer::DigitLayer() : Pebble::SimpleLayer(Pebble::GRect()) {
this->scrollLayer.AddChild(this->innerLayer);
this->scrollLayer.SetShadowHidden(true);
this->AddChild(this->scrollLayer);
}
void DigitLayer::UpdateLayout() {
auto bounds = this->GetBounds();
this->scrollLayer.SetFrame(bounds);
bounds.size.h = (this->innerLayer.digitCount + 2 * this->innerLayer.extraDigits) * this->innerLayer.digitHeight;
this->innerLayer.SetFrame(bounds);
this->scrollLayer.SetContentSize(bounds.size);
}
int16_t DigitLayer::ComputeOffset(uint16_t digit) {
auto bounds = this->GetBounds();
return bounds.size.h / 2 - this->innerLayer.digitHeight / 2
- (digit + this->innerLayer.extraDigits) * this->innerLayer.digitHeight;
}
void DigitLayer::RotateTo(uint16_t digit, bool animate) {
digit %= innerLayer.digitCount;
auto currentOffset = this->scrollLayer.GetContentOffset();
if (digit == 0 && currentOffset.y <= ComputeOffset(this->innerLayer.digitCount / 2)) {
currentOffset.y += this->innerLayer.digitCount * this->innerLayer.digitHeight;
this->scrollLayer.SetContentOffset(currentOffset, false);
}
this->scrollLayer.SetContentOffset(this->ComputeOffset(digit), animate);
}

40
src/c/digit_layer.hpp Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <pebble-cpp/pebble.hpp>
#include <@smallstoneapps/utils/macros.h>
class DigitLayerInner : public Pebble::CustomLayer {
static constexpr int16_t extraDigits = 2;
static constexpr int16_t digitHeight = 56;
int16_t digitCount = 10;
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;
protected:
void OnUpdate(const Pebble::GContext& ctx);
};
class DigitLayer : public Pebble::SimpleLayer {
DigitLayerInner innerLayer;
Pebble::ScrollLayer scrollLayer;
public:
DigitLayer();
void SetDigitCount(uint16_t cnt = 10) {
this->innerLayer.SetDigitCount(cnt);
this->MarkDirty();
}
void UpdateLayout();
void RotateTo(uint16_t digit, bool animate=true);
private:
int16_t ComputeOffset(uint16_t digit);
};

View File

@ -0,0 +1,34 @@
#pragma once
namespace Pebble {
class BitmapLayer : public Layer {
public:
BitmapLayer(GRect frame) : Layer((bitmap_layer_ = bitmap_layer_create(frame), bitmap_layer_get_layer(bitmap_layer_))) { }
BitmapLayer() : BitmapLayer(GRect(0, 0, 0, 0)) {}
//! Destroys the underlying BitmapLayer
virtual ~BitmapLayer() {
bitmap_layer_destroy(bitmap_layer_);
}
//! Instances of this class can be automatically converted to Pebble BitmapLayers
inline operator ::BitmapLayer*() { return bitmap_layer_; }
inline const GBitmap GetBitmap() { return GBitmap(const_cast< ::GBitmap*>(bitmap_layer_get_bitmap(bitmap_layer_))); }
inline void SetBitmap(GBitmap bitmap) { bitmap_layer_set_bitmap(bitmap_layer_, bitmap); }
inline void SetBackgroundColor(GColor color) { bitmap_layer_set_background_color(bitmap_layer_, color); }
inline void SetAlignment(GAlign alignment) { bitmap_layer_set_alignment(bitmap_layer_, alignment); }
inline void SetCompositingMode(GCompOp mode) { bitmap_layer_set_compositing_mode(bitmap_layer_, mode); }
protected:
// The layer pointer
::BitmapLayer *bitmap_layer_;
};
}

67
wscript Normal file
View File

@ -0,0 +1,67 @@
#
# This file is the default set of rules to compile a Pebble application.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
"""
This method is used to configure your build. ctx.load(`pebble_sdk`) automatically configures
a build for each valid platform in `targetPlatforms`. Platform-specific configuration: add your
change after calling ctx.load('pebble_sdk') and make sure to set the correct environment first.
Universal configuration: add your change prior to calling ctx.load('pebble_sdk').
"""
ctx.load('pebble_sdk')
print(ctx.env.INCLUDES)
ctx.env.CXX = 'arm-none-eabi-g++'
ctx.env.AR = 'arm-none-eabi-ar'
ctx.load('g++')
print(ctx.env.INCLUDES)
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
cached_env = ctx.env
for platform in ctx.env.TARGET_PLATFORMS:
ctx.env = ctx.all_envs[platform]
ctx.set_group(ctx.env.PLATFORM_NAME)
# -- C++ Reconfiguration --
ctx.env.CXXFLAGS = list(ctx.env.CFLAGS)
ctx.env.CXXFLAGS.remove('-std=c99')
ctx.env.CXXFLAGS.extend(['-std=c++11', '-fPIE', '-fno-unwind-tables', '-fno-exceptions', '-fno-rtti', '-fno-threadsafe-statics'])
# print(ctx.env.CXXFLAGS)
# -------------------------
app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_build(source=ctx.path.ant_glob(['src/c/**/*.c','src/c/**/*.cpp']), target=app_elf, bin_type='app')
if build_worker:
worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_build(source=ctx.path.ant_glob(['worker_src/c/**/*.c''worker_src/c/**/*.cpp',]),
target=worker_elf,
bin_type='worker')
else:
binaries.append({'platform': platform, 'app_elf': app_elf})
ctx.env = cached_env
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries,
js=ctx.path.ant_glob(['src/pkjs/**/*.js',
'src/pkjs/**/*.json',
'src/common/**/*.js']),
js_entry_file='src/pkjs/index.js')