/** * Copyright (c) 2015 Tomek Wójcik * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include static Window *s_main_window; static GBitmap *s_background_bitmap; static GBitmap *s_battery_bitmap; static GFont s_text_font; static GPath *s_minute_hand; static GPath *s_hour_hand; static BitmapLayer *s_background_layer; static TextLayer *s_title_layer; static Layer *s_hands_layer; static TextLayer *s_date_layer; static TextLayer *s_ampm_layer; static BitmapLayer *s_battery_icon_layer; static Layer *s_battery_indicator_layer; static struct tm *t = NULL; static uint8_t battery_charge = 0; static bool hide_seconds_hand = false; static bool hide_battery_indicator = false; static char date_format[2]; static bool use_wide_layout = false; static const GPathInfo MINUTE_HAND_POINTS = { 4, (GPoint []) { { 0, 0 }, { -4.5, -26 }, { 0, -38 }, { 4.5, -26 } } }; static const GPathInfo HOUR_HAND_POINTS = { 4, (GPoint []) { { 0, 0 }, { -4.5, -22 }, { 0, -29 }, { 4.5, -22 } } }; #define AMPM_TEXT_SIZE 3 #define DUMMY_AMPM_TEXT "" #define DATE_TEXT_SIZE 12 #define DUMMY_DATE_TEXT "23 Jul 1985" #define KEY_SECONDS_HAND 0 #define KEY_BATTERY_INDICATOR 1 #define KEY_DATE_FORMAT 2 #define KEY_WIDE_LAYOUT 3 static void update_date_text_layer() { static char date_text[DATE_TEXT_SIZE]; size_t result = 0; if (strcmp(date_format, "d") == 0) { result = strftime(date_text, DATE_TEXT_SIZE, "%d/%m/%y", t); } else if (strcmp(date_format, "m") == 0) { result = strftime(date_text, DATE_TEXT_SIZE, "%m/%d/%y", t); } else { result = strftime(date_text, DATE_TEXT_SIZE, "%e %b %Y", t); } if (result != 0) { text_layer_set_text(s_date_layer, date_text); } else { text_layer_set_text(s_date_layer, DUMMY_DATE_TEXT); } } static void update_ampm_text_layer() { static char ampm_text[AMPM_TEXT_SIZE]; if (clock_is_24h_style()) { text_layer_set_text(s_ampm_layer, DUMMY_AMPM_TEXT); } else { size_t result = strftime(ampm_text, AMPM_TEXT_SIZE, "%p", t); if (result != 0) { text_layer_set_text(s_ampm_layer, ampm_text); } else { text_layer_set_text(s_ampm_layer, DUMMY_AMPM_TEXT); } } text_layer_set_text(s_ampm_layer, ampm_text); } static void tick_handler(struct tm *tick_time, TimeUnits changed_units) { // Getting the current time. time_t now = time(NULL); t = localtime(&now); // Updating views. layer_mark_dirty(s_hands_layer); update_date_text_layer(); update_ampm_text_layer(); } static void s_hands_layer_update(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); GPoint center = grect_center_point(&bounds); // Drawing the minute hand. graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_stroke_color(ctx, GColorBlack); gpath_move_to(s_minute_hand, (GPoint){39.5, 39}); gpath_rotate_to(s_minute_hand, TRIG_MAX_ANGLE * t->tm_min / 60); gpath_draw_outline(ctx, s_minute_hand); gpath_draw_filled(ctx, s_minute_hand); // Drawing the hour hand. graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_stroke_color(ctx, GColorBlack); gpath_move_to(s_hour_hand, (GPoint){39.5, 39}); gpath_rotate_to(s_hour_hand, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) + (t->tm_min / 10))) / (12 * 6)); gpath_draw_outline(ctx, s_hour_hand); gpath_draw_filled(ctx, s_hour_hand); // Drawing the second hand. if (hide_seconds_hand == false) { graphics_context_set_antialiased(ctx, false); int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60; int16_t second_hand_length = (bounds.size.w / 2) - 1; 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, }; graphics_context_set_stroke_color(ctx, GColorPictonBlue); graphics_draw_line(ctx, second_hand, center); graphics_context_set_antialiased(ctx, true); } } static void s_battery_indicator_layer_update(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); graphics_context_set_fill_color(ctx, GColorGreen); if (battery_charge <= 10) { graphics_context_set_fill_color(ctx, GColorRed); } else if (battery_charge <= 60) { graphics_context_set_fill_color(ctx, GColorYellow); } graphics_fill_rect(ctx, bounds, 0, GCornerNone); } static void battery_state_handler(BatteryChargeState charge) { if (charge.charge_percent != battery_charge) { battery_charge = charge.charge_percent; layer_mark_dirty(s_battery_indicator_layer); } } static void start_time_tracking() { TimeUnits unit = MINUTE_UNIT; if (hide_seconds_hand == false) { unit = SECOND_UNIT; } tick_handler(NULL, unit); tick_timer_service_subscribe(unit, tick_handler); } static void start_battery_tracking() { if (hide_battery_indicator == false) { BatteryChargeState charge = battery_state_service_peek(); battery_state_handler(charge); battery_state_service_subscribe(battery_state_handler); } else { battery_state_service_unsubscribe(); } layer_set_hidden(bitmap_layer_get_layer(s_battery_icon_layer), hide_battery_indicator); layer_set_hidden(s_battery_indicator_layer, hide_battery_indicator); } static void update_geometry() { if (s_background_bitmap) { gbitmap_destroy(s_background_bitmap); } if (use_wide_layout == false) { s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BACKGROUND); layer_set_frame(text_layer_get_layer(s_title_layer), GRect(36, 2, 46, 18)); layer_set_frame(text_layer_get_layer(s_date_layer), GRect(17, 130, 109, 16)); layer_set_frame(text_layer_get_layer(s_ampm_layer), GRect(107, 26, 16, 16)); layer_set_frame(bitmap_layer_get_layer(s_battery_icon_layer), GRect(20, 26, 16, 7)); layer_set_frame(s_battery_indicator_layer, GRect(21, 27, 13, 5)); } else { s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BACKGROUND_WIDE); layer_set_frame(text_layer_get_layer(s_title_layer), GRect(24, 2, 71, 18)); layer_set_frame(text_layer_get_layer(s_date_layer), GRect(4, 130, 136, 16)); layer_set_frame(text_layer_get_layer(s_ampm_layer), GRect(122, 25, 16, 16)); layer_set_frame(bitmap_layer_get_layer(s_battery_icon_layer), GRect(7, 26, 16, 7)); layer_set_frame(s_battery_indicator_layer, GRect(8, 27, 13, 5)); } bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap); } static void inbox_received_handler(DictionaryIterator *iter, void *context) { // Handling seconds_hand setting. Tuple *seconds_hand_t = dict_find(iter, KEY_SECONDS_HAND); if (seconds_hand_t && seconds_hand_t->value->int32 > 0) { hide_seconds_hand = false; } else { hide_seconds_hand = true; } persist_write_bool(KEY_SECONDS_HAND, hide_seconds_hand); start_time_tracking(); // Handling battery_indicator setting. Tuple *battery_indicator_t = dict_find(iter, KEY_BATTERY_INDICATOR); if (battery_indicator_t && battery_indicator_t->value->int32 > 0) { hide_battery_indicator = false; } else { hide_battery_indicator = true; } persist_write_bool(KEY_BATTERY_INDICATOR, hide_battery_indicator); start_battery_tracking(); // Handling date_format setting. Tuple *date_format_t = dict_find(iter, KEY_DATE_FORMAT); if (date_format_t) { strcpy(date_format, date_format_t->value->cstring); } persist_write_string(KEY_DATE_FORMAT, date_format); update_date_text_layer(); // Handling wide_layout setting. Tuple *wide_layout_t = dict_find(iter, KEY_WIDE_LAYOUT); if (wide_layout_t && wide_layout_t->value->int32 > 0) { use_wide_layout = true; } else { use_wide_layout = false; } persist_write_bool(KEY_WIDE_LAYOUT, use_wide_layout); update_geometry(); } static void main_window_load(Window *window) { // Loading resources. s_battery_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BATTERY); s_text_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_TOPAZ_16)); // Creating hand paths. s_minute_hand = gpath_create(&MINUTE_HAND_POINTS); s_hour_hand = gpath_create(&HOUR_HAND_POINTS); Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); // Creating background image layer. s_background_layer = bitmap_layer_create(bounds); bitmap_layer_set_compositing_mode(s_background_layer, GCompOpSet); layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer)); // Creating title text layer. s_title_layer = text_layer_create(GRect(0, 0, 71, 18)); text_layer_set_background_color(s_title_layer, GColorClear); text_layer_set_text(s_title_layer, "Clock"); text_layer_set_font(s_title_layer, s_text_font); layer_add_child(window_layer, text_layer_get_layer(s_title_layer)); // Creating hands layer. s_hands_layer = layer_create(GRect(32, 38, 78, 78)); layer_set_update_proc(s_hands_layer, s_hands_layer_update); layer_add_child(window_layer, s_hands_layer); // Creating date layer. s_date_layer = text_layer_create(GRect(0, 0, 136, 16)); text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter); text_layer_set_background_color(s_date_layer, GColorClear); text_layer_set_font(s_date_layer, s_text_font); layer_add_child(window_layer, text_layer_get_layer(s_date_layer)); // Creating AM/PM layer. s_ampm_layer = text_layer_create(GRect(0, 0, 16, 16)); text_layer_set_text_alignment(s_ampm_layer, GTextAlignmentCenter); text_layer_set_background_color(s_ampm_layer, GColorClear); text_layer_set_text_color(s_ampm_layer, GColorWhite); text_layer_set_font(s_ampm_layer, s_text_font); layer_add_child(window_layer, text_layer_get_layer(s_ampm_layer)); // Creating battery icon layer. s_battery_icon_layer = bitmap_layer_create(GRect(0, 0, 16, 7)); bitmap_layer_set_bitmap(s_battery_icon_layer, s_battery_bitmap); bitmap_layer_set_compositing_mode(s_battery_icon_layer, GCompOpSet); layer_add_child(window_layer, bitmap_layer_get_layer(s_battery_icon_layer)); // Creating battery indicator layer. s_battery_indicator_layer = layer_create(GRect(0, 0, 13, 5)); layer_set_update_proc(s_battery_indicator_layer, s_battery_indicator_layer_update); layer_add_child(window_layer, s_battery_indicator_layer); // Update geometry according to wide_layout setting. use_wide_layout = persist_read_bool(KEY_WIDE_LAYOUT); update_geometry(); // Read date_format setting from persistent storage. int result = persist_read_string(KEY_DATE_FORMAT, date_format, 2); if (result == E_DOES_NOT_EXIST) { strcpy(date_format, "t"); } // Starting time tracking. hide_seconds_hand = persist_read_bool(KEY_SECONDS_HAND); start_time_tracking(); // Starting battery tracking. hide_battery_indicator = persist_read_bool(KEY_BATTERY_INDICATOR); start_battery_tracking(); } static void main_window_unload(Window *window) { battery_state_service_unsubscribe(); tick_timer_service_unsubscribe(); // Apparently, freeing this pointer causes the watchface to crash on // v3.3. Weird. // if (t) { // free(t); // } layer_destroy(s_battery_indicator_layer); bitmap_layer_destroy(s_battery_icon_layer); text_layer_destroy(s_ampm_layer); text_layer_destroy(s_date_layer); layer_destroy(s_hands_layer); text_layer_destroy(s_title_layer); bitmap_layer_destroy(s_background_layer); gpath_destroy(s_hour_hand); gpath_destroy(s_minute_hand); fonts_unload_custom_font(s_text_font); gbitmap_destroy(s_battery_bitmap); gbitmap_destroy(s_background_bitmap); } static void init() { s_main_window = window_create(); window_set_window_handlers(s_main_window, (WindowHandlers) { .load = main_window_load, .unload = main_window_unload }); window_stack_push(s_main_window, true); app_message_register_inbox_received(inbox_received_handler); app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); } static void deinit() { window_destroy(s_main_window); } int main(void) { init(); app_event_loop(); deinit(); }