classic-lite.c (26775B)
1 /* 2 * Copyright (c) 2015, Natacha Porté 3 * Face design by Łukasz Zalewski 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <pebble.h> 19 20 #define CACHE_BACKGROUND 21 22 /********************** 23 * CONFIGURABLE STATE * 24 **********************/ 25 26 static GColor background_color; 27 static GColor battery_color; 28 static GColor battery_color2; 29 static GColor bluetooth_color; 30 static GColor hour_hand_color; 31 static GColor minute_hand_color; 32 static GColor pin_color; 33 static GColor hour_mark_color; 34 static GColor inner_rectangle_color; 35 static GColor minute_mark_color; 36 static GColor text_color; 37 static unsigned text_font = 0; 38 static char text_format[32] = "%a %d"; 39 static bool bluetooth_vibration = true; 40 static uint8_t show_battery_icon_below = 100; 41 #define PERSIST_BUFFER_SIZE 47 42 #define TEXT_FONT_NUMBER 4 43 44 static const char *const text_fonts[] = { 45 FONT_KEY_GOTHIC_14, 46 FONT_KEY_GOTHIC_18, 47 FONT_KEY_GOTHIC_24, 48 FONT_KEY_GOTHIC_28, 49 }; 50 51 static const uint16_t text_offsets[] = { 52 36 + PBL_IF_RECT_ELSE(5, 15), 53 36 + PBL_IF_RECT_ELSE(2, 12), 54 36 + PBL_IF_RECT_ELSE(-1, 8), 55 36 + PBL_IF_RECT_ELSE(-2, 7), 56 }; 57 58 static const uint16_t text_heights[] = { 59 17, 60 22, 61 29, 62 33, 63 }; 64 65 #ifdef PBL_SDK_3 66 #define IS_VISIBLE(color) ((color).argb != background_color.argb) 67 #define IS_EQUAL(color1, color2) ((color1).argb == (color2).argb) 68 #define READ_COLOR(color, byte) do { (color).argb = (byte); } while (0) 69 #define SAVE_COLOR(byte, color) do { (byte) = (color).argb; } while (0) 70 #elif PBL_SDK_2 71 #define IS_VISIBLE(color) ((color) != background_color) 72 #define IS_EQUAL(color1, color2) ((color1) == (color2)) 73 #define READ_COLOR(color, byte) do { (color) = (byte); } while (0) 74 #define SAVE_COLOR(byte, color) do { (byte) = (color); } while (0) 75 #endif 76 77 static void 78 read_config(void) { 79 uint8_t buffer[PERSIST_BUFFER_SIZE + 1]; 80 int i; 81 82 i = persist_read_data(1, buffer, sizeof buffer); 83 84 if (i == E_DOES_NOT_EXIST) return; 85 86 if (i < 1) { 87 APP_LOG(APP_LOG_LEVEL_ERROR, 88 "invalid persist buffer size %d", i); 89 return; 90 } 91 92 if (buffer[0] < 1) { 93 APP_LOG(APP_LOG_LEVEL_ERROR, 94 "invalid configuration version %u", (unsigned)(buffer[0])); 95 return; 96 } 97 98 if (buffer[0] > 3) { 99 APP_LOG(APP_LOG_LEVEL_WARNING, 100 "loading data from future version %u, " 101 "data will be lost on the next write", 102 (unsigned)(buffer[0])); 103 return; 104 } 105 106 if (i < 43) { 107 APP_LOG(APP_LOG_LEVEL_ERROR, 108 "truncated persistent buffer size at %d, aborting", i); 109 return; 110 } 111 112 READ_COLOR(background_color, buffer[1]); 113 READ_COLOR(battery_color, buffer[2]); 114 READ_COLOR(bluetooth_color, buffer[3]); 115 READ_COLOR(hour_hand_color, buffer[4]); 116 READ_COLOR(hour_mark_color, buffer[5]); 117 READ_COLOR(inner_rectangle_color, buffer[6]); 118 READ_COLOR(minute_mark_color, buffer[7]); 119 READ_COLOR(text_color, buffer[8]); 120 121 bluetooth_vibration = (buffer[9] != 0); 122 show_battery_icon_below = buffer[10]; 123 124 memcpy(text_format, buffer + 11, sizeof text_format); 125 126 battery_color2 = battery_color; 127 pin_color = minute_hand_color = hour_hand_color; 128 129 if (buffer[0] < 2) return; 130 131 if (i < 45) { 132 APP_LOG(APP_LOG_LEVEL_ERROR, 133 "truncated persistent buffer (size %d), using only v1", 134 i); 135 return; 136 } 137 138 READ_COLOR(battery_color2, buffer[43]); 139 text_font = buffer[44]; 140 141 if (buffer[0] < 3) return; 142 143 if (i < 47) { 144 APP_LOG(APP_LOG_LEVEL_ERROR, 145 "truncated persistent buffer (size %d), using only v2", 146 i); 147 return; 148 } 149 150 READ_COLOR(minute_hand_color, buffer[45]); 151 READ_COLOR(pin_color, buffer[46]); 152 } 153 154 static void 155 write_config(void) { 156 uint8_t buffer[PERSIST_BUFFER_SIZE]; 157 int i; 158 159 buffer[0] = 3; 160 SAVE_COLOR(buffer[1], background_color); 161 SAVE_COLOR(buffer[2], battery_color); 162 SAVE_COLOR(buffer[3], bluetooth_color); 163 SAVE_COLOR(buffer[4], hour_hand_color); 164 SAVE_COLOR(buffer[45], minute_hand_color); 165 SAVE_COLOR(buffer[46], pin_color); 166 SAVE_COLOR(buffer[5], hour_mark_color); 167 SAVE_COLOR(buffer[6], inner_rectangle_color); 168 SAVE_COLOR(buffer[7], minute_mark_color); 169 SAVE_COLOR(buffer[8], text_color); 170 171 buffer[9] = bluetooth_vibration ? 1 : 0; 172 buffer[10] = show_battery_icon_below; 173 174 memcpy(buffer + 11, text_format, sizeof text_format); 175 176 SAVE_COLOR(buffer[43], battery_color2); 177 buffer[44] = text_font; 178 179 i = persist_write_data(1, buffer, sizeof buffer); 180 181 if (i < 0 || (size_t)i != sizeof buffer) { 182 APP_LOG(APP_LOG_LEVEL_ERROR, 183 "error while writing to persistent storage (%d)", i); 184 } 185 } 186 187 static GColor 188 color_from_tuple(Tuple *tuple) { 189 uint32_t value = 0; 190 191 if (!tuple) return GColorClear; 192 193 switch (tuple->type) { 194 case TUPLE_UINT: 195 switch (tuple->length) { 196 case 1: 197 value = tuple->value->uint8; 198 break; 199 case 2: 200 value = tuple->value->uint16; 201 break; 202 case 4: 203 value = tuple->value->uint32; 204 break; 205 default: 206 APP_LOG(APP_LOG_LEVEL_ERROR, 207 "bad uint length %u in color", 208 (unsigned)tuple->length); 209 return GColorClear; 210 } 211 break; 212 213 case TUPLE_INT: 214 switch (tuple->length) { 215 case 1: 216 if (tuple->value->int8 >= 0) 217 value = tuple->value->int8; 218 break; 219 case 2: 220 if (tuple->value->int16 >= 0) 221 value = tuple->value->int16; 222 break; 223 case 4: 224 if (tuple->value->int32 >= 0) 225 value = tuple->value->int32; 226 break; 227 default: 228 APP_LOG(APP_LOG_LEVEL_ERROR, 229 "bad int length %u in color", 230 (unsigned)tuple->length); 231 return GColorClear; 232 } 233 break; 234 235 default: 236 APP_LOG(APP_LOG_LEVEL_ERROR, 237 "bad type %d for color", 238 (int)tuple->type); 239 return GColorClear; 240 } 241 242 #ifdef PBL_SDK_3 243 return GColorFromHEX(value); 244 #elif PBL_SDK_2 245 return (value & 0x808080) ? GColorWhite : GColorBlack; 246 #endif 247 } 248 249 /***************** 250 * HELPER MACROS * 251 *****************/ 252 253 #define QUAD_PATH_POINTS(up, left, down, right) {\ 254 4, \ 255 (GPoint []) { \ 256 { 0, (up) }, \ 257 { (left), 0 }, \ 258 { 0, (down) }, \ 259 { (right), 0 } } } 260 261 #ifdef PBL_SDK_3 262 #define INSET_RECT(output, input, amount) do { \ 263 (output) = grect_inset((input), GEdgeInsets((amount))); \ 264 } while(0) 265 #else 266 #define INSET_RECT(output, input, amount) do { \ 267 (output).origin.x = (input).origin.x + (amount); \ 268 (output).origin.y = (input).origin.y + (amount); \ 269 (output).size.w = (input).size.w - 2 * (amount); \ 270 (output).size.h = (input).size.h - 2 * (amount); \ 271 } while(0) 272 #endif 273 274 /********************** 275 * DISPLAY PRIMITIVES * 276 **********************/ 277 278 #ifdef PBL_RECT 279 static const GPathInfo minute_hand_path_points 280 = QUAD_PATH_POINTS(15, 6, -72, -6); 281 static const GPathInfo hour_hand_path_points 282 = QUAD_PATH_POINTS(15, 7, -50, -7); 283 #else 284 static const GPathInfo minute_hand_path_points 285 = QUAD_PATH_POINTS(17, 7, -83, -7); 286 static const GPathInfo hour_hand_path_points 287 = QUAD_PATH_POINTS(17, 8, -58, -8); 288 #endif 289 static const GPathInfo bluetooth_logo_points = { 7, (GPoint[]) { 290 { -3, -3 }, 291 { 3, 3 }, 292 { 0, 6 }, 293 { 0, -6 }, 294 { 3, -3 }, 295 { -3, 3 }, 296 { 0, 0 } } }; 297 static const GPathInfo bluetooth_frame_points = { 3, (GPoint[]) { 298 { -13, 9 }, 299 { 0, -14 }, 300 { 13, 9 } } }; 301 302 static struct tm tm_now; 303 static bool bluetooth_connected = 0; 304 static Window *window; 305 static Layer *background_layer; 306 static Layer *hand_layer; 307 static Layer *icon_layer; 308 static TextLayer *text_layer; 309 static GPath *bluetooth_frame; 310 static GPath *bluetooth_logo; 311 static GPath *hour_hand_path; 312 static GPath *minute_hand_path; 313 static GPoint center; 314 static char text_buffer[64]; 315 static uint8_t current_battery = 100; 316 #define has_battery (current_battery > show_battery_icon_below) 317 #define ICON_LAYER_SET_HIDDEN do { \ 318 layer_set_hidden(icon_layer, \ 319 (bluetooth_connected || !IS_VISIBLE(bluetooth_color)) \ 320 && (has_battery \ 321 || !(IS_VISIBLE(battery_color) || IS_VISIBLE(battery_color2)))); \ 322 } while (0) 323 324 #ifdef CACHE_BACKGROUND 325 #define SCREEN_BUFFER_SIZE \ 326 PBL_IF_RECT_ELSE(PBL_IF_COLOR_ELSE(144 * 168, 144 * 168 / 8), 25868) 327 328 static uint8_t background_cache[SCREEN_BUFFER_SIZE]; 329 static bool use_background_cache = false; 330 331 static size_t 332 gbitmap_get_data_size(GBitmap *bitmap) { 333 GRect bounds; 334 if (!bitmap) return 0; 335 336 bounds = gbitmap_get_bounds(bitmap); 337 #ifdef PBL_SDK_3 338 GBitmapDataRowInfo row_info; 339 switch (gbitmap_get_format(bitmap)) { 340 case GBitmapFormat1Bit: 341 return bounds.size.w * bounds.size.h / 8; 342 case GBitmapFormat8Bit: 343 return bounds.size.w * bounds.size.h; 344 case GBitmapFormat8BitCircular: 345 row_info = gbitmap_get_data_row_info(bitmap, bounds.size.h - 1); 346 return (row_info.data + row_info.max_x + 1) 347 - gbitmap_get_data(bitmap); 348 default: 349 return 0; 350 } 351 #else 352 return bounds.size.w * bounds.size.h / 8; 353 #endif 354 } 355 356 static bool 357 save_frame_buffer(GContext *ctx) { 358 GBitmap *frame_buffer = graphics_capture_frame_buffer(ctx); 359 360 if (!frame_buffer) { 361 APP_LOG(APP_LOG_LEVEL_WARNING, 362 "Unable to capture frame buffer for saving"); 363 return false; 364 } 365 366 if (gbitmap_get_data_size(frame_buffer) != SCREEN_BUFFER_SIZE) { 367 APP_LOG(APP_LOG_LEVEL_WARNING, 368 "Unexpected frame buffer size %u, expected %u", 369 gbitmap_get_data_size(frame_buffer), SCREEN_BUFFER_SIZE); 370 graphics_release_frame_buffer(ctx, frame_buffer); 371 return false; 372 } 373 374 memcpy(background_cache, gbitmap_get_data(frame_buffer), 375 sizeof background_cache); 376 use_background_cache = true; 377 graphics_release_frame_buffer(ctx, frame_buffer); 378 return true; 379 } 380 381 static bool 382 restore_frame_buffer(GContext *ctx) { 383 GBitmap *frame_buffer = graphics_capture_frame_buffer(ctx); 384 385 if (!frame_buffer) { 386 APP_LOG(APP_LOG_LEVEL_WARNING, 387 "Unable to capture frame buffer for restore"); 388 return false; 389 } 390 391 if (gbitmap_get_data_size(frame_buffer) != SCREEN_BUFFER_SIZE) { 392 APP_LOG(APP_LOG_LEVEL_WARNING, 393 "Unexpected frame buffer size %u, expected %u", 394 gbitmap_get_data_size(frame_buffer), SCREEN_BUFFER_SIZE); 395 graphics_release_frame_buffer(ctx, frame_buffer); 396 return false; 397 } 398 399 memcpy(gbitmap_get_data(frame_buffer), background_cache, 400 sizeof background_cache); 401 graphics_release_frame_buffer(ctx, frame_buffer); 402 return true; 403 } 404 #endif 405 406 #ifdef PBL_RECT 407 static void 408 point_at_angle(GRect *rect, int32_t angle, GPoint *output, int *horizontal) { 409 int32_t sin_value = sin_lookup(angle); 410 int32_t cos_value = cos_lookup(angle); 411 int32_t abs_sin = abs(sin_value); 412 int32_t abs_cos = abs(cos_value); 413 int32_t width = rect->size.w - 1; 414 int32_t height = rect->size.h - 1; 415 416 *horizontal = (height * abs_sin < width * abs_cos); 417 418 if (*horizontal) { 419 output->x = (height * sin_value / abs_cos + width) / 2; 420 output->y = (cos_value > 0) ? 0 : height; 421 } 422 else { 423 output->x = (sin_value > 0) ? width : 0; 424 output->y = (height - width * cos_value / abs_sin) / 2; 425 } 426 output->x += rect->origin.x; 427 output->y += rect->origin.y; 428 } 429 430 static void 431 background_layer_draw(Layer *layer, GContext *ctx) { 432 int horiz, i; 433 GRect bounds = layer_get_bounds(layer); 434 GRect rect, rect2; 435 GPoint pt1, pt2; 436 437 (void)layer; 438 439 #ifdef CACHE_BACKGROUND 440 if (use_background_cache && restore_frame_buffer(ctx)) return; 441 #endif 442 443 if (IS_VISIBLE(minute_mark_color)) { 444 graphics_context_set_stroke_color(ctx, minute_mark_color); 445 INSET_RECT(rect, bounds, 5); 446 for (i = 0; i < 60; i += 1) { 447 point_at_angle(&rect, TRIG_MAX_ANGLE * i / 60, 448 &pt1, &horiz); 449 pt2.x = pt1.x + horiz; 450 pt2.y = pt1.y + (1 - horiz); 451 graphics_draw_line(ctx, pt1, pt2); 452 } 453 } 454 455 if (IS_VISIBLE(hour_mark_color)) { 456 #ifndef PBL_BW 457 graphics_context_set_stroke_width(ctx, 3); 458 #endif 459 460 graphics_context_set_stroke_color(ctx, hour_mark_color); 461 INSET_RECT(rect, bounds, 11); 462 INSET_RECT(rect2, bounds, 22); 463 for (i = 0; i < 12; i += 1) { 464 point_at_angle(&rect, TRIG_MAX_ANGLE * i / 12, 465 &pt1, &horiz); 466 point_at_angle(&rect2, TRIG_MAX_ANGLE * i / 12, 467 &pt2, &horiz); 468 469 graphics_draw_line(ctx, pt1, pt2); 470 471 #ifdef PBL_BW 472 pt1.x += horiz; pt2.x += horiz; 473 pt1.y += 1 - horiz; pt2.y += 1 - horiz; 474 graphics_draw_line(ctx, pt1, pt2); 475 476 pt1.x -= 2 * horiz; pt2.x -= 2 * horiz; 477 pt1.y -= 2 * (1 - horiz); pt2.y -= 2 * (1 - horiz); 478 graphics_draw_line(ctx, pt1, pt2); 479 #endif 480 } 481 482 #ifndef PBL_BW 483 graphics_context_set_stroke_width(ctx, 1); 484 #endif 485 } 486 487 if (IS_VISIBLE(inner_rectangle_color)) { 488 INSET_RECT(rect, bounds, 35); 489 graphics_context_set_stroke_color(ctx, inner_rectangle_color); 490 #ifdef PBL_BW 491 pt1.y = rect.origin.y; 492 pt2.y = rect.origin.y + rect.size.h - 1; 493 for (i = rect.origin.x +2; i < rect.origin.x + rect.size.w; i += 3) { 494 pt1.x = pt2.x = i; 495 graphics_draw_pixel(ctx, pt1); 496 graphics_draw_pixel(ctx, pt2); 497 } 498 499 pt1.x = rect.origin.x; 500 pt2.x = rect.origin.x + rect.size.w - 1; 501 for (i = rect.origin.y +2; 502 i < rect.origin.y + rect.size.h; 503 i += 3) { 504 pt1.y = pt2.y = i; 505 graphics_draw_pixel(ctx, pt1); 506 graphics_draw_pixel(ctx, pt2); 507 } 508 #else 509 graphics_draw_rect(ctx, rect); 510 #endif 511 } 512 513 #ifdef CACHE_BACKGROUND 514 save_frame_buffer(ctx); 515 #endif 516 } 517 #else 518 static GPoint 519 point_at_angle(GRect bounds, int32_t angle) { 520 GPoint ret; 521 uint32_t width = bounds.size.w; 522 uint32_t height = bounds.size.h; 523 ret.x = bounds.origin.x 524 + (TRIG_MAX_RATIO * (width + 1) + width * sin_lookup(angle)) 525 / (2 * TRIG_MAX_RATIO); 526 ret.y = bounds.origin.y 527 + (TRIG_MAX_RATIO * (height + 1) - height * cos_lookup(angle)) 528 / (2 * TRIG_MAX_RATIO); 529 return ret; 530 } 531 532 static void 533 background_layer_draw(Layer *layer, GContext *ctx) { 534 const GRect bounds = layer_get_bounds(layer); 535 const GPoint center = grect_center_point(&bounds); 536 const int32_t radius = (bounds.size.w + bounds.size.h) / 4; 537 const int32_t angle_delta = TRIG_MAX_ANGLE / (6 * radius); 538 int32_t angle, x, y; 539 GRect rect; 540 GPoint pt1, pt2; 541 int i; 542 543 (void)layer; 544 545 #ifdef CACHE_BACKGROUND 546 if (use_background_cache && restore_frame_buffer(ctx)) return; 547 #endif 548 549 if (IS_VISIBLE(minute_mark_color)) { 550 graphics_context_set_stroke_color(ctx, minute_mark_color); 551 INSET_RECT(rect, bounds, 5); 552 for (i = 0; i < 60; i += 1) { 553 angle = TRIG_MAX_ANGLE * i / 60; 554 graphics_draw_line(ctx, 555 point_at_angle(rect, angle - angle_delta), 556 point_at_angle(rect, angle + angle_delta)); 557 } 558 } 559 560 if (IS_VISIBLE(hour_mark_color)) { 561 graphics_context_set_stroke_width(ctx, 3); 562 graphics_context_set_stroke_color(ctx, hour_mark_color); 563 for (i = 0; i < 12; i += 1) { 564 angle = TRIG_MAX_ANGLE * i / 12; 565 x = sin_lookup(angle); 566 y = -cos_lookup(angle); 567 pt1.x = center.x + (radius - 11) * x / TRIG_MAX_RATIO; 568 pt1.y = center.y + (radius - 11) * y / TRIG_MAX_RATIO; 569 pt2.x = center.x + (radius - 22) * x / TRIG_MAX_RATIO; 570 pt2.y = center.y + (radius - 22) * y / TRIG_MAX_RATIO; 571 graphics_draw_line(ctx, pt1, pt2); 572 } 573 } 574 575 if (IS_VISIBLE(inner_rectangle_color)) { 576 graphics_context_set_stroke_width(ctx, 1); 577 graphics_context_set_stroke_color(ctx, inner_rectangle_color); 578 graphics_draw_circle(ctx, center, radius - 35); 579 } 580 581 #ifdef CACHE_BACKGROUND 582 save_frame_buffer(ctx); 583 #endif 584 } 585 #endif 586 587 static void 588 hand_layer_draw(Layer *layer, GContext *ctx) { 589 (void)layer; 590 591 graphics_context_set_fill_color(ctx, hour_hand_color); 592 graphics_context_set_stroke_color(ctx, background_color); 593 594 gpath_rotate_to(minute_hand_path, TRIG_MAX_ANGLE * tm_now.tm_min / 60); 595 gpath_draw_filled(ctx, minute_hand_path); 596 gpath_draw_outline(ctx, minute_hand_path); 597 598 graphics_context_set_fill_color(ctx, minute_hand_color); 599 600 gpath_rotate_to(hour_hand_path, 601 TRIG_MAX_ANGLE * (tm_now.tm_hour * 60 + tm_now.tm_min) / 720); 602 gpath_draw_filled(ctx, hour_hand_path); 603 gpath_draw_outline(ctx, hour_hand_path); 604 605 #ifdef CACHE_BACKGROUND 606 if (!use_background_cache) return; 607 #endif 608 609 graphics_context_set_fill_color(ctx, background_color); 610 graphics_fill_circle(ctx, center, 2); 611 graphics_context_set_fill_color(ctx, pin_color); 612 graphics_fill_circle(ctx, center, 1); 613 } 614 615 static void 616 icon_layer_draw(Layer *layer, GContext *ctx) { 617 GRect bounds = layer_get_bounds(layer); 618 GPoint center = grect_center_point(&bounds); 619 GPoint pt; 620 621 if (!bluetooth_connected && IS_VISIBLE(bluetooth_color)) { 622 pt.x = center.x; 623 pt.y = center.y + (has_battery ? +1 : -2); 624 gpath_move_to(bluetooth_frame, pt); 625 gpath_move_to(bluetooth_logo, pt); 626 graphics_context_set_stroke_color(ctx, bluetooth_color); 627 gpath_draw_outline(ctx, bluetooth_frame); 628 #ifdef PBL_SDK_3 629 gpath_draw_outline_open(ctx, bluetooth_logo); 630 #else 631 gpath_draw_outline(ctx, bluetooth_logo); 632 #endif 633 } 634 635 if (!has_battery 636 && (IS_VISIBLE(battery_color) || IS_VISIBLE(battery_color2))) { 637 pt.x = center.x - 11; 638 pt.y = center.y 639 + (bluetooth_connected ? 0 : PBL_IF_RECT_ELSE(9, 11)); 640 graphics_context_set_fill_color(ctx, battery_color); 641 graphics_fill_rect(ctx, 642 GRect(pt.x, pt.y, 22, 7), 643 0, GCornerNone); 644 graphics_fill_rect(ctx, 645 GRect(pt.x + 22, pt.y + 2, 2, 3), 646 0, GCornerNone); 647 if (!IS_EQUAL(battery_color2, battery_color)) { 648 graphics_context_set_fill_color(ctx, battery_color2); 649 graphics_fill_rect(ctx, 650 GRect(pt.x + 5, pt.y + 1, 4, 5), 651 0, GCornerNone); 652 graphics_fill_rect(ctx, 653 GRect(pt.x + 13, pt.y + 1, 4, 5), 654 0, GCornerNone); 655 } 656 graphics_context_set_fill_color(ctx, background_color); 657 if (current_battery < 100) 658 graphics_fill_rect(ctx, 659 GRect(pt.x + 1 + current_battery / 5, pt.y + 1, 660 20 - current_battery / 5, 5), 661 0, GCornerNone); 662 } 663 } 664 665 static void 666 update_text_layer(struct tm *time) { 667 strftime(text_buffer, sizeof text_buffer, text_format, time); 668 text_layer_set_text(text_layer, text_buffer); 669 } 670 671 static void 672 update_text_font(unsigned new_text_font) { 673 Layer *window_layer = window_get_root_layer(window); 674 GRect bounds = layer_get_bounds(window_layer); 675 676 if (new_text_font >= TEXT_FONT_NUMBER) return; 677 678 if (text_layer) { 679 if (new_text_font == text_font) return; 680 text_layer_destroy(text_layer); 681 } 682 683 text_font = new_text_font; 684 685 text_layer = text_layer_create(GRect( 686 bounds.origin.x, 687 bounds.origin.y + text_offsets[text_font], 688 bounds.size.w, 689 text_heights[text_font])); 690 text_layer_set_text_color(text_layer, text_color); 691 text_layer_set_background_color(text_layer, GColorClear); 692 text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); 693 text_layer_set_font(text_layer, 694 fonts_get_system_font(text_fonts[text_font])); 695 layer_set_hidden(text_layer_get_layer(text_layer), 696 !text_format[0] || !IS_VISIBLE(text_color)); 697 layer_insert_below_sibling(text_layer_get_layer(text_layer), 698 icon_layer); 699 } 700 701 /******************** 702 * SERVICE HANDLERS * 703 ********************/ 704 705 static void 706 battery_handler(BatteryChargeState charge) { 707 if (current_battery == charge.charge_percent) return; 708 current_battery = charge.charge_percent; 709 ICON_LAYER_SET_HIDDEN; 710 if (!has_battery) layer_mark_dirty(icon_layer); 711 } 712 713 static void 714 bluetooth_handler(bool connected) { 715 bluetooth_connected = connected; 716 ICON_LAYER_SET_HIDDEN; 717 layer_mark_dirty(icon_layer); 718 719 if (bluetooth_vibration && !connected) vibes_long_pulse(); 720 } 721 722 static void 723 inbox_received_handler(DictionaryIterator *iterator, void *context) { 724 Tuple *tuple; 725 726 (void)context; 727 728 for (tuple = dict_read_first(iterator); 729 tuple; 730 tuple = dict_read_next(iterator)) { 731 switch (tuple->key) { 732 case 1: 733 background_color = color_from_tuple(tuple); 734 window_set_background_color(window, background_color); 735 break; 736 case 2: 737 battery_color = color_from_tuple(tuple); 738 layer_mark_dirty(icon_layer); 739 break; 740 case 3: 741 bluetooth_color = color_from_tuple(tuple); 742 layer_mark_dirty(icon_layer); 743 break; 744 case 4: 745 hour_hand_color = color_from_tuple(tuple); 746 pin_color = minute_hand_color = hour_hand_color; 747 layer_mark_dirty(hand_layer); 748 break; 749 case 5: 750 hour_mark_color = color_from_tuple(tuple); 751 layer_mark_dirty(background_layer); 752 break; 753 case 6: 754 inner_rectangle_color = color_from_tuple(tuple); 755 layer_mark_dirty(background_layer); 756 break; 757 case 7: 758 minute_mark_color = color_from_tuple(tuple); 759 layer_mark_dirty(background_layer); 760 break; 761 case 8: 762 text_color = color_from_tuple(tuple); 763 text_layer_set_text_color(text_layer, text_color); 764 break; 765 case 9: 766 battery_color2 = color_from_tuple(tuple); 767 layer_mark_dirty(icon_layer); 768 break; 769 case 10: 770 if (tuple->type != TUPLE_INT 771 && tuple->type != TUPLE_UINT) 772 APP_LOG(APP_LOG_LEVEL_ERROR, 773 "bad type %d for text_font entry", 774 (int)tuple->type); 775 else if (tuple->value->uint8 == 0 776 || tuple->value->uint8 > TEXT_FONT_NUMBER) 777 APP_LOG(APP_LOG_LEVEL_ERROR, 778 "bad value %u for text_font entry", 779 (unsigned)tuple->value->uint8); 780 else 781 update_text_font(tuple->value->uint8 - 1); 782 update_text_layer(&tm_now); 783 break; 784 case 11: 785 if (tuple->type == TUPLE_CSTRING) { 786 strncpy(text_format, tuple->value->cstring, 787 sizeof text_format); 788 update_text_layer(&tm_now); 789 } else 790 APP_LOG(APP_LOG_LEVEL_ERROR, 791 "bad type %d for text_format entry", 792 (int)tuple->type); 793 break; 794 case 12: 795 if (tuple->type == TUPLE_INT 796 || tuple->type == TUPLE_UINT) 797 bluetooth_vibration 798 = tuple->value->data[0] != 0; 799 else 800 APP_LOG(APP_LOG_LEVEL_ERROR, 801 "bad type %d for bluetooth_vibration entry", 802 (int)tuple->type); 803 break; 804 case 13: 805 if (tuple->type == TUPLE_INT) 806 show_battery_icon_below 807 = (tuple->value->int8 < 0) ? 0 808 : tuple->value->int8; 809 else if (tuple->type == TUPLE_UINT) 810 show_battery_icon_below = tuple->value->uint8; 811 else 812 APP_LOG(APP_LOG_LEVEL_ERROR, "bad type %d for " 813 "show_battery_icon_below entry", 814 (int)tuple->type); 815 break; 816 case 20: 817 hour_hand_color = color_from_tuple(tuple); 818 layer_mark_dirty(hand_layer); 819 break; 820 case 21: 821 minute_hand_color = color_from_tuple(tuple); 822 layer_mark_dirty(hand_layer); 823 break; 824 case 22: 825 pin_color = color_from_tuple(tuple); 826 layer_mark_dirty(hand_layer); 827 break; 828 default: 829 APP_LOG(APP_LOG_LEVEL_ERROR, 830 "unknown configuration key %lu", 831 (unsigned long)tuple->key); 832 } 833 } 834 835 #ifdef CACHE_BACKGROUND 836 use_background_cache = !IS_VISIBLE(inner_rectangle_color) 837 && !IS_VISIBLE(hour_mark_color) 838 && !IS_VISIBLE(minute_mark_color); 839 layer_set_hidden(background_layer, use_background_cache); 840 #else 841 layer_set_hidden(background_layer, 842 !IS_VISIBLE(inner_rectangle_color) 843 && !IS_VISIBLE(hour_mark_color) 844 && !IS_VISIBLE(minute_mark_color)); 845 #endif 846 847 ICON_LAYER_SET_HIDDEN; 848 849 layer_set_hidden(text_layer_get_layer(text_layer), 850 !text_format[0] || !IS_VISIBLE(text_color)); 851 852 write_config(); 853 } 854 855 static void 856 tick_handler(struct tm* tick_time, TimeUnits units_changed) { 857 if (tm_now.tm_mday != tick_time->tm_mday) update_text_layer(tick_time); 858 tm_now = *tick_time; 859 layer_mark_dirty(hand_layer); 860 } 861 862 /*********************************** 863 * INITIALIZATION AND FINALIZATION * 864 ***********************************/ 865 866 static void 867 window_load(Window *window) { 868 Layer *window_layer = window_get_root_layer(window); 869 GRect bounds = layer_get_bounds(window_layer); 870 window_set_background_color(window, background_color); 871 872 center = grect_center_point(&bounds); 873 gpath_move_to(hour_hand_path, center); 874 gpath_move_to(minute_hand_path, center); 875 876 background_layer = layer_create(bounds); 877 layer_set_update_proc(background_layer, &background_layer_draw); 878 #ifdef CACHE_BACKGROUND 879 use_background_cache = !IS_VISIBLE(inner_rectangle_color) 880 && !IS_VISIBLE(hour_mark_color) 881 && !IS_VISIBLE(minute_mark_color); 882 layer_set_hidden(background_layer, use_background_cache); 883 #else 884 layer_set_hidden(background_layer, 885 !IS_VISIBLE(inner_rectangle_color) 886 && !IS_VISIBLE(hour_mark_color) 887 && !IS_VISIBLE(minute_mark_color)); 888 #endif 889 layer_add_child(window_layer, background_layer); 890 891 icon_layer = layer_create(GRect(bounds.origin.x 892 + (bounds.size.w - 33) / 2, PBL_IF_RECT_ELSE(97, 105), 33, 36)); 893 layer_set_update_proc(icon_layer, &icon_layer_draw); 894 ICON_LAYER_SET_HIDDEN; 895 layer_add_child(window_layer, icon_layer); 896 897 update_text_font(text_font); 898 update_text_layer(&tm_now); 899 900 hand_layer = layer_create(bounds); 901 layer_set_update_proc(hand_layer, &hand_layer_draw); 902 layer_add_child(window_layer, hand_layer); 903 } 904 905 static void 906 window_unload(Window *window) { 907 layer_destroy(background_layer); 908 layer_destroy(hand_layer); 909 layer_destroy(icon_layer); 910 text_layer_destroy(text_layer); 911 } 912 913 static void 914 init(void) { 915 time_t current_time = time(0); 916 tm_now = *localtime(¤t_time); 917 918 background_color = GColorWhite; 919 bluetooth_color = GColorBlack; 920 hour_hand_color = GColorBlack; 921 minute_hand_color = GColorBlack; 922 pin_color = GColorBlack; 923 hour_mark_color = GColorBlack; 924 minute_mark_color = GColorBlack; 925 text_color = GColorBlack; 926 927 #ifdef PBL_BW 928 battery_color = GColorBlack; 929 inner_rectangle_color = GColorBlack; 930 #else 931 battery_color = GColorDarkGray; 932 inner_rectangle_color = GColorLightGray; 933 #endif 934 battery_color2 = battery_color; 935 936 bluetooth_connected = connection_service_peek_pebble_app_connection(); 937 current_battery = battery_state_service_peek().charge_percent; 938 read_config(); 939 940 window = window_create(); 941 window_set_window_handlers(window, (WindowHandlers) { 942 .load = window_load, 943 .unload = window_unload, 944 }); 945 946 bluetooth_frame = gpath_create(&bluetooth_frame_points); 947 bluetooth_logo = gpath_create(&bluetooth_logo_points); 948 hour_hand_path = gpath_create(&hour_hand_path_points); 949 minute_hand_path = gpath_create(&minute_hand_path_points); 950 951 battery_state_service_subscribe(&battery_handler); 952 connection_service_subscribe(((ConnectionHandlers){ 953 .pebble_app_connection_handler = &bluetooth_handler, 954 .pebblekit_connection_handler = 0})); 955 tick_timer_service_subscribe(MINUTE_UNIT, &tick_handler); 956 window_stack_push(window, true); 957 958 app_message_register_inbox_received(inbox_received_handler); 959 app_message_open(1024, 0); 960 } 961 962 static void 963 deinit(void) { 964 battery_state_service_unsubscribe(); 965 connection_service_unsubscribe(); 966 tick_timer_service_unsubscribe(); 967 gpath_destroy(bluetooth_frame); 968 gpath_destroy(bluetooth_logo); 969 gpath_destroy(hour_hand_path); 970 gpath_destroy(minute_hand_path); 971 window_destroy(window); 972 } 973 974 int 975 main(void) { 976 init(); 977 app_event_loop(); 978 deinit(); 979 }