initial commit.
authorjweigele <jweigele@local>
Mon, 21 Jul 2025 03:45:54 +0000 (20:45 -0700)
committerjweigele <jweigele@local>
Mon, 21 Jul 2025 03:45:54 +0000 (20:45 -0700)
displays the metro logo (in orange) with an analog clock around it
clock hands are updated each second (there is a second hand)

things included but not currently used:
 * an extra font which is bolder (helvetica-black)
 * a commented out section for updating and displaying the time
   digitally

.gitignore [new file with mode: 0644]
package.json [new file with mode: 0644]
resources/helvetica-black.ttf [new file with mode: 0755]
resources/helvetica.ttf [new file with mode: 0644]
resources/images/metro.png [new file with mode: 0644]
src/c/main.c [new file with mode: 0644]
wscript [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..1794078
--- /dev/null
@@ -0,0 +1,2 @@
+build/
+.lock**
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..33f63e5
--- /dev/null
@@ -0,0 +1,42 @@
+{
+  "name": "retro-metro",
+  "author": "MakeAwesomeHappen",
+  "version": "1.0.0",
+  "keywords": ["pebble-app"],
+  "private": true,
+  "dependencies": {},
+  "pebble": {
+    "displayName": "retro-metro",
+    "uuid": "fb8764a5-4a96-4626-9c89-3ca4bdac24b1",
+    "sdkVersion": "3",
+    "enableMultiJS": true,
+    "targetPlatforms": [
+      "aplite",
+      "basalt",
+      "chalk",
+      "diorite"
+    ],
+    "watchapp": {
+      "watchface": true 
+    },
+    "messageKeys": [
+      "dummy"
+    ],
+    "resources": {
+      "media": [
+          {
+              "type": "bitmap",
+              "name": "IMAGE_METRO",
+              "file": "images/metro.png",
+              "memoryFormat": "Smallest",
+              "spaceOptimization": "memory"
+          },
+          {
+              "type": "font",
+              "name": "FONT_HELVETICA_BLACK_48",
+              "file": "helvetica.ttf"
+          }
+      ]
+    }
+  }
+}
diff --git a/resources/helvetica-black.ttf b/resources/helvetica-black.ttf
new file mode 100755 (executable)
index 0000000..6ee3aef
Binary files /dev/null and b/resources/helvetica-black.ttf differ
diff --git a/resources/helvetica.ttf b/resources/helvetica.ttf
new file mode 100644 (file)
index 0000000..7aec6f3
Binary files /dev/null and b/resources/helvetica.ttf differ
diff --git a/resources/images/metro.png b/resources/images/metro.png
new file mode 100644 (file)
index 0000000..6700346
Binary files /dev/null and b/resources/images/metro.png differ
diff --git a/src/c/main.c b/src/c/main.c
new file mode 100644 (file)
index 0000000..223b1dc
--- /dev/null
@@ -0,0 +1,198 @@
+#include <pebble.h>
+
+static Window *s_main_window;
+static Layer *s_hands_layer;
+static TextLayer *s_time_layer;
+
+static GPath *s_minute_arrow, *s_hour_arrow;
+
+// background stuff
+static BitmapLayer *s_background_layer;
+static GBitmap *s_background_bitmap;
+
+static GPoint center = {
+        .x = (int16_t)70,
+        .y = (int16_t)68,
+    };
+
+static void draw_ticks(Layer *layer, GContext *ctx){
+    const int16_t starting_tick_length = 66;
+    const int16_t ending_tick_length = 68;
+
+    graphics_context_set_stroke_color(ctx, GColorWhite);
+    // for each tick
+    for (int i = 0; i < 12; i++){
+        int32_t tick_angle = TRIG_MAX_ANGLE * i/12;
+        // from the the starting point (predetermined angle with starting_tick_length)..
+        GPoint starting_point = {
+            .x = (int16_t)(sin_lookup(tick_angle) * (int32_t)starting_tick_length/TRIG_MAX_RATIO) + center.x,
+            .y = (int16_t)(-cos_lookup(tick_angle) * (int32_t)starting_tick_length/TRIG_MAX_RATIO) + center.y,
+        };
+        // .. to the ending point (same angle, slightly more length)
+        GPoint ending_point = {
+            .x = (int16_t)(sin_lookup(tick_angle) * (int32_t)ending_tick_length/TRIG_MAX_RATIO) + center.x,
+            .y = (int16_t)(-cos_lookup(tick_angle) * (int32_t)ending_tick_length/TRIG_MAX_RATIO) + center.y,
+        };
+
+        // draw it, then wrap around to next
+        graphics_context_set_stroke_width(ctx, 2);
+        graphics_draw_line(ctx, starting_point, ending_point);
+    }
+
+
+}
+
+
+static void hands_update_proc(Layer *layer, GContext *ctx) {
+    GRect bounds = layer_get_bounds(layer);
+  
+    const int16_t second_hand_length = 64; 
+    const int16_t minute_hand_length = 64;
+    const int16_t hour_hand_length = 32;
+    const uint8_t minute_stroke_width = 2;
+    const uint8_t hour_stroke_width = 4;
+
+    draw_ticks(layer, ctx);
+  
+    time_t now = time(NULL);
+    struct tm *t = localtime(&now);
+    int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
+    GPoint second_hand = {
+        .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x,
+        .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y,
+    };
+  
+    // second hand
+    graphics_context_set_stroke_color(ctx, GColorWhite);
+    graphics_context_set_stroke_width(ctx, 1);
+    graphics_draw_line(ctx, second_hand, center);
+  
+    // minute hand drawing 
+    int32_t minute_angle = TRIG_MAX_ANGLE * t->tm_min/60;
+    GPoint minute_hand = {
+        .x = (int16_t)(sin_lookup(minute_angle) * (int32_t)minute_hand_length / TRIG_MAX_RATIO) + center.x,
+        .y = (int16_t)(-cos_lookup(minute_angle) * (int32_t)minute_hand_length / TRIG_MAX_RATIO) + center.y,
+    };        
+    graphics_context_set_stroke_width(ctx, minute_stroke_width);
+
+    graphics_draw_line(ctx, minute_hand, center);
+
+    // hour hand drawing 
+    int32_t hour_angle = TRIG_MAX_ANGLE * (((t->tm_hour % 12 ) * 6) + (t->tm_min/10)) / (12*6);
+    GPoint hour_hand = {
+        .x = (int16_t)(sin_lookup(hour_angle) * (int32_t)hour_hand_length / TRIG_MAX_RATIO) + center.x,
+        .y = (int16_t)(-cos_lookup(hour_angle) * (int32_t)hour_hand_length / TRIG_MAX_RATIO) + center.y,
+    };
+    graphics_context_set_stroke_width(ctx, hour_stroke_width);
+
+    graphics_draw_line(ctx, hour_hand, center);    
+  
+    // dot in the middle
+    //graphics_context_set_fill_color(ctx, GColorBlack);
+    //graphics_fill_rect(ctx, GRect(bounds.size.w / 2 - 1, bounds.size.h / 2 - 1, 3, 3), 0, GCornerNone);
+}
+
+
+
+
+static void main_window_load(Window *window) {
+
+    // Get information about the Window
+    Layer *window_layer = window_get_root_layer(window);
+    GRect bounds = layer_get_bounds(window_layer);  
+
+    // Create GBitmap
+    s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_METRO);
+
+    // Create BitmapLayer to display the GBitmap
+    s_background_layer = bitmap_layer_create(bounds);
+
+    // Set the bitmap onto the layer and add to the window
+    bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
+    layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));
+/*
+    // Create the TextLayer with specific bounds
+    s_time_layer = text_layer_create(
+        GRect(0, PBL_IF_ROUND_ELSE(36, 36), bounds.size.w, 50));
+
+    // Improve the layout to be more like a watchface
+    text_layer_set_background_color(s_time_layer, GColorClear);
+    text_layer_set_text_color(s_time_layer, GColorWhite);
+    text_layer_set_text(s_time_layer, "00:00");
+    //text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
+    text_layer_set_font(s_time_layer, fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_HELVETICA_BLACK_48)));
+    text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
+
+    // Add it as a child layer to the Window's root layer
+    layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
+*/
+
+
+    s_hands_layer = layer_create(bounds);
+    layer_set_update_proc(s_hands_layer, hands_update_proc);
+    layer_add_child(window_layer, s_hands_layer);    
+}
+
+static void main_window_unload(Window *window) {
+    // Destroy GBitmap
+    gbitmap_destroy(s_background_bitmap);
+
+    // Destroy BitmapLayer
+    bitmap_layer_destroy(s_background_layer);    
+    // Destroy TextLayer
+    text_layer_destroy(s_time_layer);
+    // Destroy hands layer
+    layer_destroy(s_hands_layer);
+}
+
+static void update_time() {
+  // Get a tm structure
+  time_t temp = time(NULL);
+  struct tm *tick_time = localtime(&temp);
+
+  // Write the current hours and minutes into a buffer
+  static char s_buffer[8];
+  strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
+                                          "%H:%M" : "%I:%M", tick_time);
+
+  // Display this time on the TextLayer
+  //text_layer_set_text(s_time_layer, s_buffer);
+}
+
+static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) {
+    layer_mark_dirty(window_get_root_layer(s_main_window));
+}
+
+static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
+  update_time();
+}
+
+static void init() {
+    // Create main Window element and assign to pointer
+    s_main_window = window_create();
+  
+    // Set handlers to manage the elements inside the Window
+    window_set_window_handlers(s_main_window, (WindowHandlers) {
+      .load = main_window_load,
+      .unload = main_window_unload
+    });
+  
+    // Show the Window on the watch, with animated=true
+    window_stack_push(s_main_window, true);
+    // Make sure the time is displayed from the start
+    tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
+    tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);
+    update_time();
+}
+
+static void deinit() {
+  tick_timer_service_unsubscribe();
+  // Destroy Window
+  window_destroy(s_main_window);
+}
+
+int main(void) {
+  init();
+  app_event_loop();
+  deinit();
+}
diff --git a/wscript b/wscript
new file mode 100644 (file)
index 0000000..5238bc8
--- /dev/null
+++ b/wscript
@@ -0,0 +1,54 @@
+#
+# 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')
+
+
+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)
+        app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
+        ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), 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'),
+                          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')