├── .gitignore ├── simple-analog-screenshot.png ├── README.md ├── appinfo.json ├── LICENSE ├── wscript └── src ├── simple_analog.h └── simple_analog.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /simple-analog-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/simple-analog/HEAD/simple-analog-screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-analog 2 | 3 | ![screenshot](simple-analog-screenshot.png) 4 | 5 | Example analog watchface that uses hands instead of numbers. -------------------------------------------------------------------------------- /appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "ecc8f112-318f-4ee2-be11-53c63b8a1139", 3 | "shortName": "My Simple Analog", 4 | "longName": "My Simple Analog", 5 | "companyName": "Pebble Technology", 6 | "versionCode": 1, 7 | "versionLabel": "1.0", 8 | "watchapp": { 9 | "watchface": true 10 | }, 11 | "appKeys": {}, 12 | "resources": { 13 | "media": [] 14 | }, 15 | "targetPlatforms": [ 16 | "aplite", 17 | "basalt", 18 | "chalk" 19 | ], 20 | "sdkVersion": "3" 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Pebble Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This file is the default set of rules to compile a Pebble project. 4 | # 5 | # Feel free to customize this to your needs. 6 | # 7 | 8 | import os.path 9 | 10 | top = '.' 11 | out = 'build' 12 | 13 | def options(ctx): 14 | ctx.load('pebble_sdk') 15 | 16 | def configure(ctx): 17 | ctx.load('pebble_sdk') 18 | 19 | def build(ctx): 20 | ctx.load('pebble_sdk') 21 | 22 | build_worker = os.path.exists('worker_src') 23 | binaries = [] 24 | 25 | for p in ctx.env.TARGET_PLATFORMS: 26 | ctx.set_env(ctx.all_envs[p]) 27 | ctx.set_group(ctx.env.PLATFORM_NAME) 28 | app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 29 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 30 | target=app_elf) 31 | 32 | if build_worker: 33 | worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 34 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 35 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 36 | target=worker_elf) 37 | else: 38 | binaries.append({'platform': p, 'app_elf': app_elf}) 39 | 40 | ctx.set_group('bundle') 41 | ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js')) 42 | -------------------------------------------------------------------------------- /src/simple_analog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pebble.h" 4 | 5 | #define NUM_CLOCK_TICKS 11 6 | 7 | static const struct GPathInfo ANALOG_BG_POINTS[] = { 8 | { 4, 9 | (GPoint []) { 10 | {68, 0}, 11 | {71, 0}, 12 | {71, 12}, 13 | {68, 12} 14 | } 15 | }, 16 | { 4, (GPoint []){ 17 | {72, 0}, 18 | {75, 0}, 19 | {75, 12}, 20 | {72, 12} 21 | } 22 | }, 23 | { 4, (GPoint []){ 24 | {112, 10}, 25 | {114, 12}, 26 | {108, 23}, 27 | {106, 21} 28 | } 29 | }, 30 | { 4, (GPoint []){ 31 | {132, 47}, 32 | {144, 40}, 33 | {144, 44}, 34 | {135, 49} 35 | } 36 | }, 37 | { 4, (GPoint []){ 38 | {135, 118}, 39 | {144, 123}, 40 | {144, 126}, 41 | {132, 120} 42 | } 43 | }, 44 | { 4, (GPoint []){ 45 | {108, 144}, 46 | {114, 154}, 47 | {112, 157}, 48 | {106, 147} 49 | } 50 | }, 51 | { 4, (GPoint []){ 52 | {70, 155}, 53 | {73, 155}, 54 | {73, 167}, 55 | {70, 167} 56 | } 57 | }, 58 | { 4, (GPoint []){ 59 | {32, 10}, 60 | {30, 12}, 61 | {36, 23}, 62 | {38, 21} 63 | } 64 | }, 65 | { 4, (GPoint []){ 66 | {12, 47}, 67 | {-1, 40}, 68 | {-1, 44}, 69 | {9, 49} 70 | } 71 | }, 72 | { 4, (GPoint []){ 73 | {9, 118}, 74 | {-1, 123}, 75 | {-1, 126}, 76 | {12, 120} 77 | } 78 | }, 79 | { 4, (GPoint []){ 80 | {36, 144}, 81 | {30, 154}, 82 | {32, 157}, 83 | {38, 147} 84 | } 85 | }, 86 | 87 | }; 88 | 89 | static const GPathInfo MINUTE_HAND_POINTS = { 90 | 3, (GPoint []) { 91 | { -8, 20 }, 92 | { 8, 20 }, 93 | { 0, -80 } 94 | } 95 | }; 96 | 97 | static const GPathInfo HOUR_HAND_POINTS = { 98 | 3, (GPoint []){ 99 | {-6, 20}, 100 | {6, 20}, 101 | {0, -60} 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /src/simple_analog.c: -------------------------------------------------------------------------------- 1 | #include "simple_analog.h" 2 | 3 | #include "pebble.h" 4 | 5 | static Window *s_window; 6 | static Layer *s_simple_bg_layer, *s_date_layer, *s_hands_layer; 7 | static TextLayer *s_day_label, *s_num_label; 8 | 9 | static GPath *s_tick_paths[NUM_CLOCK_TICKS]; 10 | static GPath *s_minute_arrow, *s_hour_arrow; 11 | static char s_num_buffer[4], s_day_buffer[6]; 12 | 13 | static void bg_update_proc(Layer *layer, GContext *ctx) { 14 | graphics_context_set_fill_color(ctx, GColorBlack); 15 | graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); 16 | graphics_context_set_fill_color(ctx, GColorWhite); 17 | for (int i = 0; i < NUM_CLOCK_TICKS; ++i) { 18 | const int x_offset = PBL_IF_ROUND_ELSE(18, 0); 19 | const int y_offset = PBL_IF_ROUND_ELSE(6, 0); 20 | gpath_move_to(s_tick_paths[i], GPoint(x_offset, y_offset)); 21 | gpath_draw_filled(ctx, s_tick_paths[i]); 22 | } 23 | } 24 | 25 | static void hands_update_proc(Layer *layer, GContext *ctx) { 26 | GRect bounds = layer_get_bounds(layer); 27 | GPoint center = grect_center_point(&bounds); 28 | 29 | const int16_t second_hand_length = PBL_IF_ROUND_ELSE((bounds.size.w / 2) - 19, bounds.size.w / 2); 30 | 31 | time_t now = time(NULL); 32 | struct tm *t = localtime(&now); 33 | int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60; 34 | GPoint second_hand = { 35 | .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x, 36 | .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y, 37 | }; 38 | 39 | // second hand 40 | graphics_context_set_stroke_color(ctx, GColorWhite); 41 | graphics_draw_line(ctx, second_hand, center); 42 | 43 | // minute/hour hand 44 | graphics_context_set_fill_color(ctx, GColorWhite); 45 | graphics_context_set_stroke_color(ctx, GColorBlack); 46 | 47 | gpath_rotate_to(s_minute_arrow, TRIG_MAX_ANGLE * t->tm_min / 60); 48 | gpath_draw_filled(ctx, s_minute_arrow); 49 | gpath_draw_outline(ctx, s_minute_arrow); 50 | 51 | gpath_rotate_to(s_hour_arrow, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) + (t->tm_min / 10))) / (12 * 6)); 52 | gpath_draw_filled(ctx, s_hour_arrow); 53 | gpath_draw_outline(ctx, s_hour_arrow); 54 | 55 | // dot in the middle 56 | graphics_context_set_fill_color(ctx, GColorBlack); 57 | graphics_fill_rect(ctx, GRect(bounds.size.w / 2 - 1, bounds.size.h / 2 - 1, 3, 3), 0, GCornerNone); 58 | } 59 | 60 | static void date_update_proc(Layer *layer, GContext *ctx) { 61 | time_t now = time(NULL); 62 | struct tm *t = localtime(&now); 63 | 64 | strftime(s_day_buffer, sizeof(s_day_buffer), "%a", t); 65 | text_layer_set_text(s_day_label, s_day_buffer); 66 | 67 | strftime(s_num_buffer, sizeof(s_num_buffer), "%d", t); 68 | text_layer_set_text(s_num_label, s_num_buffer); 69 | } 70 | 71 | static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) { 72 | layer_mark_dirty(window_get_root_layer(s_window)); 73 | } 74 | 75 | static void window_load(Window *window) { 76 | Layer *window_layer = window_get_root_layer(window); 77 | GRect bounds = layer_get_bounds(window_layer); 78 | 79 | s_simple_bg_layer = layer_create(bounds); 80 | layer_set_update_proc(s_simple_bg_layer, bg_update_proc); 81 | layer_add_child(window_layer, s_simple_bg_layer); 82 | 83 | s_date_layer = layer_create(bounds); 84 | layer_set_update_proc(s_date_layer, date_update_proc); 85 | layer_add_child(window_layer, s_date_layer); 86 | 87 | s_day_label = text_layer_create(PBL_IF_ROUND_ELSE( 88 | GRect(63, 114, 27, 20), 89 | GRect(46, 114, 27, 20))); 90 | text_layer_set_text(s_day_label, s_day_buffer); 91 | text_layer_set_background_color(s_day_label, GColorBlack); 92 | text_layer_set_text_color(s_day_label, GColorWhite); 93 | text_layer_set_font(s_day_label, fonts_get_system_font(FONT_KEY_GOTHIC_18)); 94 | 95 | layer_add_child(s_date_layer, text_layer_get_layer(s_day_label)); 96 | 97 | s_num_label = text_layer_create(PBL_IF_ROUND_ELSE( 98 | GRect(90, 114, 18, 20), 99 | GRect(73, 114, 18, 20))); 100 | text_layer_set_text(s_num_label, s_num_buffer); 101 | text_layer_set_background_color(s_num_label, GColorBlack); 102 | text_layer_set_text_color(s_num_label, GColorWhite); 103 | text_layer_set_font(s_num_label, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); 104 | 105 | layer_add_child(s_date_layer, text_layer_get_layer(s_num_label)); 106 | 107 | s_hands_layer = layer_create(bounds); 108 | layer_set_update_proc(s_hands_layer, hands_update_proc); 109 | layer_add_child(window_layer, s_hands_layer); 110 | } 111 | 112 | static void window_unload(Window *window) { 113 | layer_destroy(s_simple_bg_layer); 114 | layer_destroy(s_date_layer); 115 | 116 | text_layer_destroy(s_day_label); 117 | text_layer_destroy(s_num_label); 118 | 119 | layer_destroy(s_hands_layer); 120 | } 121 | 122 | static void init() { 123 | s_window = window_create(); 124 | window_set_window_handlers(s_window, (WindowHandlers) { 125 | .load = window_load, 126 | .unload = window_unload, 127 | }); 128 | window_stack_push(s_window, true); 129 | 130 | s_day_buffer[0] = '\0'; 131 | s_num_buffer[0] = '\0'; 132 | 133 | // init hand paths 134 | s_minute_arrow = gpath_create(&MINUTE_HAND_POINTS); 135 | s_hour_arrow = gpath_create(&HOUR_HAND_POINTS); 136 | 137 | Layer *window_layer = window_get_root_layer(s_window); 138 | GRect bounds = layer_get_bounds(window_layer); 139 | GPoint center = grect_center_point(&bounds); 140 | gpath_move_to(s_minute_arrow, center); 141 | gpath_move_to(s_hour_arrow, center); 142 | 143 | for (int i = 0; i < NUM_CLOCK_TICKS; ++i) { 144 | s_tick_paths[i] = gpath_create(&ANALOG_BG_POINTS[i]); 145 | } 146 | 147 | tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick); 148 | } 149 | 150 | static void deinit() { 151 | gpath_destroy(s_minute_arrow); 152 | gpath_destroy(s_hour_arrow); 153 | 154 | for (int i = 0; i < NUM_CLOCK_TICKS; ++i) { 155 | gpath_destroy(s_tick_paths[i]); 156 | } 157 | 158 | tick_timer_service_unsubscribe(); 159 | window_destroy(s_window); 160 | } 161 | 162 | int main() { 163 | init(); 164 | app_event_loop(); 165 | deinit(); 166 | } 167 | --------------------------------------------------------------------------------