intuiclock.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /**
  2. * Copyright (c) 2015 Tomek Wójcik <tomek@bthlabs.pl>
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. * THE SOFTWARE.
  21. */
  22. #include <pebble.h>
  23. static Window *s_main_window;
  24. static GBitmap *s_background_bitmap;
  25. static GBitmap *s_battery_bitmap;
  26. static GFont s_text_font;
  27. static GPath *s_minute_hand;
  28. static GPath *s_hour_hand;
  29. static BitmapLayer *s_background_layer;
  30. static TextLayer *s_title_layer;
  31. static Layer *s_hands_layer;
  32. static TextLayer *s_date_layer;
  33. static TextLayer *s_ampm_layer;
  34. static BitmapLayer *s_battery_icon_layer;
  35. static Layer *s_battery_indicator_layer;
  36. static struct tm *t = NULL;
  37. static uint8_t battery_charge = 0;
  38. static bool hide_seconds_hand = false;
  39. static bool hide_battery_indicator = false;
  40. static char date_format[2];
  41. static const GPathInfo MINUTE_HAND_POINTS = {
  42. 4,
  43. (GPoint []) {
  44. { 0, 0 },
  45. { -4.5, -26 },
  46. { 0, -38 },
  47. { 4.5, -26 }
  48. }
  49. };
  50. static const GPathInfo HOUR_HAND_POINTS = {
  51. 4,
  52. (GPoint []) {
  53. { 0, 0 },
  54. { -4.5, -22 },
  55. { 0, -29 },
  56. { 4.5, -22 }
  57. }
  58. };
  59. #define AMPM_TEXT_SIZE 3
  60. #define DUMMY_AMPM_TEXT ""
  61. #define DATE_TEXT_SIZE 12
  62. #define DUMMY_DATE_TEXT "23 Jul 1985"
  63. #define KEY_SECONDS_HAND 0
  64. #define KEY_BATTERY_INDICATOR 1
  65. #define KEY_DATE_FORMAT 2
  66. static void update_date_text_layer() {
  67. static char date_text[DATE_TEXT_SIZE];
  68. size_t result = 0;
  69. if (strcmp(date_format, "d") == 0) {
  70. result = strftime(date_text, DATE_TEXT_SIZE, "%d/%m/%y", t);
  71. } else if (strcmp(date_format, "m") == 0) {
  72. result = strftime(date_text, DATE_TEXT_SIZE, "%m/%d/%y", t);
  73. } else {
  74. result = strftime(date_text, DATE_TEXT_SIZE, "%e %b %Y", t);
  75. }
  76. if (result != 0) {
  77. text_layer_set_text(s_date_layer, date_text);
  78. } else {
  79. text_layer_set_text(s_date_layer, DUMMY_DATE_TEXT);
  80. }
  81. }
  82. static void update_ampm_text_layer() {
  83. static char ampm_text[AMPM_TEXT_SIZE];
  84. if (clock_is_24h_style()) {
  85. text_layer_set_text(s_ampm_layer, DUMMY_AMPM_TEXT);
  86. } else {
  87. size_t result = strftime(ampm_text, AMPM_TEXT_SIZE, "%p", t);
  88. if (result != 0) {
  89. text_layer_set_text(s_ampm_layer, ampm_text);
  90. } else {
  91. text_layer_set_text(s_ampm_layer, DUMMY_AMPM_TEXT);
  92. }
  93. }
  94. }
  95. static void tick_handler(struct tm *tick_time, TimeUnits changed_units) {
  96. // Getting the current time.
  97. time_t now = time(NULL);
  98. t = localtime(&now);
  99. // Updating views.
  100. layer_mark_dirty(s_hands_layer);
  101. update_date_text_layer();
  102. update_ampm_text_layer();
  103. }
  104. static void s_hands_layer_update(Layer *layer, GContext *ctx) {
  105. GRect bounds = layer_get_bounds(layer);
  106. GPoint center = grect_center_point(&bounds);
  107. // Drawing the minute hand.
  108. graphics_context_set_fill_color(ctx, GColorBlack);
  109. graphics_context_set_stroke_color(ctx, GColorBlack);
  110. gpath_move_to(s_minute_hand, (GPoint){39.5, 39});
  111. gpath_rotate_to(s_minute_hand, TRIG_MAX_ANGLE * t->tm_min / 60);
  112. gpath_draw_outline(ctx, s_minute_hand);
  113. gpath_draw_filled(ctx, s_minute_hand);
  114. // Drawing the hour hand.
  115. graphics_context_set_fill_color(ctx, GColorBlack);
  116. graphics_context_set_stroke_color(ctx, GColorBlack);
  117. gpath_move_to(s_hour_hand, (GPoint){39.5, 39});
  118. gpath_rotate_to(s_hour_hand, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) + (t->tm_min / 10))) / (12 * 6));
  119. gpath_draw_outline(ctx, s_hour_hand);
  120. gpath_draw_filled(ctx, s_hour_hand);
  121. // Drawing the second hand.
  122. if (hide_seconds_hand == false) {
  123. graphics_context_set_antialiased(ctx, false);
  124. int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
  125. int16_t second_hand_length = (bounds.size.w / 2) - 1;
  126. GPoint second_hand = {
  127. .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x,
  128. .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y,
  129. };
  130. graphics_context_set_stroke_color(ctx, GColorPictonBlue);
  131. graphics_draw_line(ctx, second_hand, center);
  132. graphics_context_set_antialiased(ctx, true);
  133. }
  134. }
  135. static void s_battery_indicator_layer_update(Layer *layer, GContext *ctx) {
  136. GRect bounds = layer_get_bounds(layer);
  137. graphics_context_set_fill_color(ctx, GColorGreen);
  138. if (battery_charge <= 10) {
  139. graphics_context_set_fill_color(ctx, GColorRed);
  140. } else if (battery_charge <= 60) {
  141. graphics_context_set_fill_color(ctx, GColorYellow);
  142. }
  143. graphics_fill_rect(ctx, bounds, 0, GCornerNone);
  144. }
  145. static void battery_state_handler(BatteryChargeState charge) {
  146. if (charge.charge_percent != battery_charge) {
  147. battery_charge = charge.charge_percent;
  148. layer_mark_dirty(s_battery_indicator_layer);
  149. }
  150. }
  151. static void start_time_tracking() {
  152. TimeUnits unit = MINUTE_UNIT;
  153. if (hide_seconds_hand == false) {
  154. unit = SECOND_UNIT;
  155. }
  156. tick_handler(NULL, unit);
  157. tick_timer_service_subscribe(unit, tick_handler);
  158. }
  159. static void start_battery_tracking() {
  160. if (hide_battery_indicator == false) {
  161. BatteryChargeState charge = battery_state_service_peek();
  162. battery_state_handler(charge);
  163. battery_state_service_subscribe(battery_state_handler);
  164. } else {
  165. battery_state_service_unsubscribe();
  166. }
  167. layer_set_hidden(bitmap_layer_get_layer(s_battery_icon_layer), hide_battery_indicator);
  168. layer_set_hidden(s_battery_indicator_layer, hide_battery_indicator);
  169. }
  170. static void inbox_received_handler(DictionaryIterator *iter, void *context) {
  171. // Handling seconds_hand setting.
  172. Tuple *seconds_hand_t = dict_find(iter, KEY_SECONDS_HAND);
  173. if (seconds_hand_t && seconds_hand_t->value->int32 > 0) {
  174. hide_seconds_hand = false;
  175. } else {
  176. hide_seconds_hand = true;
  177. }
  178. persist_write_bool(KEY_SECONDS_HAND, hide_seconds_hand);
  179. start_time_tracking();
  180. // Handling battery_indicator setting.
  181. Tuple *battery_indicator_t = dict_find(iter, KEY_BATTERY_INDICATOR);
  182. if (battery_indicator_t && battery_indicator_t->value->int32 > 0) {
  183. hide_battery_indicator = false;
  184. } else {
  185. hide_battery_indicator = true;
  186. }
  187. persist_write_bool(KEY_BATTERY_INDICATOR, hide_battery_indicator);
  188. start_battery_tracking();
  189. // Handling date_format setting.
  190. Tuple *date_format_t = dict_find(iter, KEY_DATE_FORMAT);
  191. if (date_format_t) {
  192. strcpy(date_format, date_format_t->value->cstring);
  193. }
  194. persist_write_string(KEY_DATE_FORMAT, date_format);
  195. update_date_text_layer();
  196. }
  197. static void main_window_load(Window *window) {
  198. // Loading resources.
  199. s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BACKGROUND);
  200. s_battery_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BATTERY);
  201. s_text_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_TOPAZ_16));
  202. // Creating hand paths.
  203. s_minute_hand = gpath_create(&MINUTE_HAND_POINTS);
  204. s_hour_hand = gpath_create(&HOUR_HAND_POINTS);
  205. Layer *window_layer = window_get_root_layer(window);
  206. GRect bounds = layer_get_bounds(window_layer);
  207. // Creating background image layer.
  208. s_background_layer = bitmap_layer_create(bounds);
  209. bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
  210. bitmap_layer_set_compositing_mode(s_background_layer, GCompOpSet);
  211. layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));
  212. // Creating title text layer.
  213. s_title_layer = text_layer_create(GRect(36, 2, 46, 18));
  214. text_layer_set_background_color(s_title_layer, GColorClear);
  215. text_layer_set_text(s_title_layer, "Clock");
  216. text_layer_set_font(s_title_layer, s_text_font);
  217. layer_add_child(window_layer, text_layer_get_layer(s_title_layer));
  218. // Creating hands layer.
  219. s_hands_layer = layer_create(GRect(32, 38, 78, 78));
  220. layer_set_update_proc(s_hands_layer, s_hands_layer_update);
  221. layer_add_child(window_layer, s_hands_layer);
  222. // Creating date layer.
  223. s_date_layer = text_layer_create(GRect(17, 130, 109, 16));
  224. text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter);
  225. text_layer_set_background_color(s_date_layer, GColorClear);
  226. text_layer_set_font(s_date_layer, s_text_font);
  227. layer_add_child(window_layer, text_layer_get_layer(s_date_layer));
  228. // Creating AM/PM layer.
  229. s_ampm_layer = text_layer_create(GRect(107, 26, 16, 16));
  230. text_layer_set_text_alignment(s_ampm_layer, GTextAlignmentCenter);
  231. text_layer_set_background_color(s_ampm_layer, GColorClear);
  232. text_layer_set_text_color(s_ampm_layer, GColorWhite);
  233. text_layer_set_font(s_ampm_layer, s_text_font);
  234. layer_add_child(window_layer, text_layer_get_layer(s_ampm_layer));
  235. // Creating battery icon layer.
  236. s_battery_icon_layer = bitmap_layer_create(GRect(20, 26, 16, 7));
  237. bitmap_layer_set_bitmap(s_battery_icon_layer, s_battery_bitmap);
  238. bitmap_layer_set_compositing_mode(s_battery_icon_layer, GCompOpSet);
  239. layer_add_child(window_layer, bitmap_layer_get_layer(s_battery_icon_layer));
  240. // Creating battery indicator layer.
  241. s_battery_indicator_layer = layer_create(GRect(21, 27, 13, 5));
  242. layer_set_update_proc(s_battery_indicator_layer, s_battery_indicator_layer_update);
  243. layer_add_child(window_layer, s_battery_indicator_layer);
  244. // Read date_format setting from persistent storage.
  245. int result = persist_read_string(KEY_DATE_FORMAT, date_format, 2);
  246. if (result == E_DOES_NOT_EXIST) {
  247. strcpy(date_format, "t");
  248. }
  249. // Starting time tracking.
  250. hide_seconds_hand = persist_read_bool(KEY_SECONDS_HAND);
  251. start_time_tracking();
  252. // Starting battery tracking.
  253. hide_battery_indicator = persist_read_bool(KEY_BATTERY_INDICATOR);
  254. start_battery_tracking();
  255. }
  256. static void main_window_unload(Window *window) {
  257. battery_state_service_unsubscribe();
  258. tick_timer_service_unsubscribe();
  259. // Apparently, freeing this pointer causes the watchface to crash on
  260. // v3.3. Weird.
  261. // if (t) {
  262. // free(t);
  263. // }
  264. layer_destroy(s_battery_indicator_layer);
  265. bitmap_layer_destroy(s_battery_icon_layer);
  266. text_layer_destroy(s_ampm_layer);
  267. text_layer_destroy(s_date_layer);
  268. layer_destroy(s_hands_layer);
  269. text_layer_destroy(s_title_layer);
  270. bitmap_layer_destroy(s_background_layer);
  271. gpath_destroy(s_hour_hand);
  272. gpath_destroy(s_minute_hand);
  273. fonts_unload_custom_font(s_text_font);
  274. gbitmap_destroy(s_battery_bitmap);
  275. gbitmap_destroy(s_background_bitmap);
  276. }
  277. static void init() {
  278. s_main_window = window_create();
  279. window_set_window_handlers(s_main_window, (WindowHandlers) {
  280. .load = main_window_load,
  281. .unload = main_window_unload
  282. });
  283. window_stack_push(s_main_window, true);
  284. app_message_register_inbox_received(inbox_received_handler);
  285. app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
  286. }
  287. static void deinit() {
  288. window_destroy(s_main_window);
  289. }
  290. int main(void) {
  291. init();
  292. app_event_loop();
  293. deinit();
  294. }