#include "palette_manip.h" #include // DEBUG memory usage layer static TextLayer *s_memory_layer; // declare general statics static Window *s_main_window; static GFont s_custom_font; static Layer *s_time_bar_layer; static TextLayer *s_time_layer; static TextLayer *s_date_layer; static BitmapLayer *s_bt_layer; static BitmapLayer *s_guy_head_layer; static BitmapLayer *s_guy_butt_layer; // declare silly guy parts static GColor8 s_random_color_current; static GColor8 s_random_color_next; static GBitmap *s_head; static GBitmap *s_butt; static GBitmap *s_bt0_icon; static GBitmap *s_bt1_icon; // declare positions for animation stages static GRect s_guy_head_grect_on_screen_raised; static GRect s_guy_head_grect_off_screen_raised; static GRect s_guy_head_grect_on_screen_resting; static GRect s_guy_butt_grect_on_screen_raised; static GRect s_guy_butt_grect_off_screen_raised; static GRect s_guy_butt_grect_on_screen_resting; #if PBL_DISPLAY_WIDTH <= 180 // 8x+20(4-x)+20, // where x=one_count, // 8=width of "1", // first 20=width of other, // second 20=combined width of spaces+colon static const uint8_t s_max_time_width = 100; #else // 10x+25(4-x)+21, // where x=one_count, // 10=width of "1", // 25=width of other, // 21=combined width of spaces+colon static const uint8_t s_max_time_width = 121; #endif static const uint8_t s_date_pos_x = 0; static const uint8_t s_date_height = 14; static const uint8_t s_bt_height = 24; static uint8_t s_date_pos_y; static uint8_t s_date_bt_width; static GRect s_date_grect_a; static uint8_t s_bt_pos_x; static uint8_t s_bt_pos_y; static GRect s_bt_grect_a; static GRect s_date_grect_b; static GRect s_bt_grect_b; // declare animation templates static uint8_t s_animation_duration = 255; static Animation *s_head_raise_template; static Animation *s_head_out_template; static Animation *s_butt_raise_template; static Animation *s_butt_out_template; // declare lookup tables #if PBL_DISPLAY_WIDTH <= 180 static const uint8_t s_ones_offset[] = {0, 3, 6, 9, 12}; #else static const uint8_t s_ones_offset[] = {0, 4, 7, 11, 15}; #endif static const uint8_t s_head_count = 25; static const uint32_t s_random_heads[] = { RESOURCE_ID_H001, RESOURCE_ID_H002, RESOURCE_ID_H003, RESOURCE_ID_H004, RESOURCE_ID_H005, RESOURCE_ID_H006, RESOURCE_ID_H007, RESOURCE_ID_H008, RESOURCE_ID_H009, RESOURCE_ID_H010, RESOURCE_ID_H011, RESOURCE_ID_H012, RESOURCE_ID_H013, RESOURCE_ID_H014, RESOURCE_ID_H015, RESOURCE_ID_H016, RESOURCE_ID_H017, RESOURCE_ID_H018, RESOURCE_ID_H019, RESOURCE_ID_H020, RESOURCE_ID_H021, RESOURCE_ID_H022, RESOURCE_ID_H023, RESOURCE_ID_H024, RESOURCE_ID_H025}; static const uint8_t s_butt_count = 26; static const uint32_t s_random_butts[] = { RESOURCE_ID_B001, RESOURCE_ID_B002, RESOURCE_ID_B003, RESOURCE_ID_B004, RESOURCE_ID_B005, RESOURCE_ID_B006, RESOURCE_ID_B007, RESOURCE_ID_B008, RESOURCE_ID_B009, RESOURCE_ID_B010, RESOURCE_ID_B011, RESOURCE_ID_B012, RESOURCE_ID_B013, RESOURCE_ID_B014, RESOURCE_ID_B015, RESOURCE_ID_B016, RESOURCE_ID_B017, RESOURCE_ID_B018, RESOURCE_ID_B019, RESOURCE_ID_B020, RESOURCE_ID_B021, RESOURCE_ID_B022, RESOURCE_ID_B023, RESOURCE_ID_B024, RESOURCE_ID_B025, RESOURCE_ID_XB001}; static const uint8_t s_color_count = 20; static const GColor8 s_dark_bg_colors[] = { // Red GColorFolly, GColorRed, // Orange GColorOrange, GColorRajah, // Yellow GColorIcterine, GColorPastelYellow, GColorYellow, // Green GColorInchworm, GColorJaegerGreen, GColorKellyGreen, GColorMintGreen, // Blue GColorElectricBlue, GColorTiffanyBlue, GColorVividCerulean, // Purple GColorLavenderIndigo, GColorPurpureus, // Pink GColorBrilliantRose, GColorMelon, GColorShockingPink, // Brown GColorRoseVale}; static void destroy_animation_handler(Animation *animation, bool finished, void *context) { animation_destroy(animation); } static bool first_minute_update = true; static void update_minute_1() { // get a tm structure time_t temp = time(NULL); struct tm *tick_time = localtime(&temp); // write the current hours and minutes into a buffer & format static char s_time_buffer[8]; strftime(s_time_buffer, sizeof(s_time_buffer), clock_is_24h_style() ? "%H:%M" : "%I:%M", tick_time); // write the current date into a buffer & format static char s_date_buffer[16]; #if PBL_DISPLAY_WIDTH <= 144 strftime(s_date_buffer, sizeof(s_date_buffer), "%m .%d", tick_time); #else strftime(s_date_buffer, sizeof(s_date_buffer), "%m.%d", tick_time); #endif // update layers text_layer_set_text(s_time_layer, s_time_buffer); text_layer_set_text(s_date_layer, s_date_buffer); // create date/bt centering animations //// count "1"s in s_time_buffer static uint8_t ones; uint8_t i, new_ones; for (i = 0, new_ones = 0; s_time_buffer[i]; i++) { new_ones += (s_time_buffer[i] == '1'); } if (new_ones != ones) { // only animate if necessary ones = new_ones; //// calculate new positions s_date_grect_b = GRect(s_ones_offset[ones], s_date_pos_y, s_date_bt_width, s_date_height); s_bt_grect_b = GRect(s_bt_pos_x - s_ones_offset[ones], s_bt_pos_y, s_date_bt_width, s_bt_height); if (!first_minute_update) { //// configure animations ////// date PropertyAnimation *date_center_prop = property_animation_create_layer_frame(text_layer_get_layer(s_date_layer), &s_date_grect_a, &s_date_grect_b); Animation *date_center_anim = property_animation_get_animation(date_center_prop); animation_set_curve(date_center_anim, AnimationCurveEaseOut); animation_set_duration(date_center_anim, s_animation_duration); ////// bt PropertyAnimation *s_bt_center_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_bt_layer), &s_bt_grect_a, &s_bt_grect_b); Animation *bt_center_anim = property_animation_get_animation(s_bt_center_prop); animation_set_curve(bt_center_anim, AnimationCurveEaseOut); animation_set_duration(bt_center_anim, s_animation_duration); ////// create spawn animation (animate centering of both elements at the same time) Animation *centering_spawn_anim = animation_spawn_create(date_center_anim, bt_center_anim, NULL); ////// set the handler to destroy the spawn animation (and its children) when finished animation_set_handlers(centering_spawn_anim, (AnimationHandlers){.stopped = destroy_animation_handler}, NULL); ////// run centering spawn animation animation_schedule(centering_spawn_anim); } ////// update current date/bt positions s_date_grect_a = s_date_grect_b; s_bt_grect_a = s_bt_grect_b; } first_minute_update = false; // DEBUG memory usage layer static char s_memory_buffer[32]; snprintf(s_memory_buffer, sizeof(s_memory_buffer), "%d/%d", (int)heap_bytes_used(), (int)heap_bytes_free() + (int)heap_bytes_used()); text_layer_set_text(s_memory_layer, s_memory_buffer); } static void schedule_guy_animation(Animation *raise_tmpl, Animation *out_tmpl, AnimationStoppedHandler out_handler) { Animation *raise = animation_clone(raise_tmpl); Animation *out = animation_clone(out_tmpl); animation_set_handlers(out, (AnimationHandlers){.stopped = out_handler}, NULL); Animation *in = animation_clone(out_tmpl); animation_set_reverse(in, true); Animation *rest = animation_clone(raise_tmpl); animation_set_reverse(rest, true); Animation *seq = animation_sequence_create(raise, out, in, rest, NULL); // set the handler to destroy the sequence (and its children) when finished animation_set_handlers(seq, (AnimationHandlers){.stopped = destroy_animation_handler}, NULL); // run it animation_schedule(seq); } static void update_minute_30_out_handler(Animation *animation, bool finished, void *context) { // destroy and reassign gbitmap_destroy(s_butt); s_butt = gbitmap_create_with_resource(s_random_butts[rand() % s_butt_count]); // swap colors replace_gbitmap_color(GColorGreen, s_random_color_next, s_butt, NULL); replace_gbitmap_color(s_random_color_current, s_random_color_next, s_head, NULL); s_random_color_current = s_random_color_next; s_random_color_next = s_dark_bg_colors[rand() % s_color_count]; // force update bitmap layers bitmap_layer_set_bitmap(s_guy_butt_layer, s_butt); bitmap_layer_set_bitmap(s_guy_head_layer, s_head); } static void update_minute_30() { update_minute_1(); schedule_guy_animation(s_butt_raise_template, s_butt_out_template, update_minute_30_out_handler); } static void update_minute_60_out_handler(Animation *animation, bool finished, void *context) { // destroy and reassign gbitmap_destroy(s_head); s_head = gbitmap_create_with_resource(s_random_heads[rand() % s_head_count]); // swap colors replace_gbitmap_color(GColorGreen, s_random_color_next, s_head, NULL); replace_gbitmap_color(s_random_color_current, s_random_color_next, s_butt, NULL); s_random_color_current = s_random_color_next; s_random_color_next = s_dark_bg_colors[rand() % s_color_count]; // force update bitmap layers bitmap_layer_set_bitmap(s_guy_head_layer, s_head); bitmap_layer_set_bitmap(s_guy_butt_layer, s_butt); } static void update_minute_60() { update_minute_1(); schedule_guy_animation(s_head_raise_template, s_head_out_template, update_minute_60_out_handler); } static void minute_handler(struct tm *tick_time, TimeUnits units_changed) { if (tick_time->tm_min % 60 == 0) { update_minute_60(); } else if (tick_time->tm_min % 30 == 0) { update_minute_30(); } else { update_minute_1(); } } static void time_bar_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); } // define contents of the Window upon load static void main_window_load(Window *window) { // dynamically calculate coordinates for silly guy animations int8_t part_x_on_screen = (260 - PBL_DISPLAY_WIDTH) / -2; int8_t head_y_resting = (260 - PBL_DISPLAY_HEIGHT) / -2; int8_t head_y_rasied = head_y_resting - 15; s_guy_head_grect_on_screen_raised = GRect(part_x_on_screen, head_y_rasied, 260, 115); s_guy_head_grect_off_screen_raised = GRect(-PBL_DISPLAY_WIDTH + part_x_on_screen, head_y_rasied, 260, 115); s_guy_head_grect_on_screen_resting = GRect(part_x_on_screen, head_y_resting, 260, 115); s_guy_butt_grect_on_screen_raised = GRect(part_x_on_screen, head_y_resting + 180, 260, 115); s_guy_butt_grect_off_screen_raised = GRect(PBL_DISPLAY_WIDTH + part_x_on_screen, head_y_resting + 160, 260, 115); s_guy_butt_grect_on_screen_resting = GRect(part_x_on_screen, head_y_resting + 145, 260, 115); // format silly guy layers s_guy_head_layer = bitmap_layer_create(s_guy_head_grect_on_screen_resting); s_guy_butt_layer = bitmap_layer_create(s_guy_butt_grect_on_screen_resting); bitmap_layer_set_compositing_mode(s_guy_head_layer, GCompOpSet); bitmap_layer_set_alignment(s_guy_head_layer, GAlignLeft); bitmap_layer_set_bitmap(s_guy_head_layer, s_head); bitmap_layer_set_compositing_mode(s_guy_butt_layer, GCompOpSet); bitmap_layer_set_alignment(s_guy_butt_layer, GAlignTopLeft); bitmap_layer_set_bitmap(s_guy_butt_layer, s_butt); // create time bar layer s_time_bar_layer = layer_create(GRect(0, PBL_DISPLAY_HEIGHT / 2 - 15, PBL_DISPLAY_WIDTH, 30)); layer_set_update_proc(s_time_bar_layer, time_bar_update_proc); // add layers as children to window Layer *window_layer = window_get_root_layer(window); layer_add_child(window_layer, bitmap_layer_get_layer(s_guy_head_layer)); layer_add_child(window_layer, bitmap_layer_get_layer(s_guy_butt_layer)); layer_add_child(window_layer, s_time_bar_layer); layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); layer_add_child(window_layer, text_layer_get_layer(s_date_layer)); layer_add_child(window_layer, bitmap_layer_get_layer(s_bt_layer)); // DEBUG memory usage layer layer_add_child(window_layer, text_layer_get_layer(s_memory_layer)); // create animation templates // butt Raise PropertyAnimation *butt_raise_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_butt_layer), &s_guy_butt_grect_on_screen_resting, &s_guy_butt_grect_on_screen_raised); s_butt_raise_template = property_animation_get_animation(butt_raise_prop); animation_set_curve(s_butt_raise_template, AnimationCurveEaseOut); animation_set_duration(s_butt_raise_template, s_animation_duration); // butt Out PropertyAnimation *butt_out_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_butt_layer), &s_guy_butt_grect_on_screen_raised, &s_guy_butt_grect_off_screen_raised); s_butt_out_template = property_animation_get_animation(butt_out_prop); animation_set_curve(s_butt_out_template, AnimationCurveEaseOut); animation_set_duration(s_butt_out_template, s_animation_duration); // head Raise PropertyAnimation *head_raise_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_head_layer), &s_guy_head_grect_on_screen_resting, &s_guy_head_grect_on_screen_raised); s_head_raise_template = property_animation_get_animation(head_raise_prop); animation_set_curve(s_head_raise_template, AnimationCurveEaseOut); animation_set_duration(s_head_raise_template, s_animation_duration); // head Out PropertyAnimation *head_out_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_head_layer), &s_guy_head_grect_on_screen_raised, &s_guy_head_grect_off_screen_raised); s_head_out_template = property_animation_get_animation(head_out_prop); animation_set_curve(s_head_out_template, AnimationCurveEaseOut); animation_set_duration(s_head_out_template, s_animation_duration); } // free memory on Window close static void main_window_unload(Window *window) { // destroy layers bitmap_layer_destroy(s_guy_head_layer); bitmap_layer_destroy(s_guy_butt_layer); layer_destroy(s_time_bar_layer); // Destroy animation templates animation_destroy(s_butt_raise_template); animation_destroy(s_butt_out_template); animation_destroy(s_head_raise_template); animation_destroy(s_head_out_template); } static void bluetooth_callback(bool connected) { if (connected) { bitmap_layer_set_bitmap(s_bt_layer, s_bt1_icon); } else { bitmap_layer_set_bitmap(s_bt_layer, s_bt0_icon); } } // DEBUG buttons static void up_single_click_handler(ClickRecognizerRef recognizer, void *context) { Window *window = (Window *)context; update_minute_60(); } // DEBUG buttons static void down_single_click_handler(ClickRecognizerRef recognizer, void *context) { Window *window = (Window *)context; update_minute_30(); } // DEBUG buttons static void click_config_provider(Window *window) { window_single_click_subscribe(BUTTON_ID_UP, up_single_click_handler); window_single_click_subscribe(BUTTON_ID_DOWN, down_single_click_handler); } // set up the app on launch (don't put app logic in here); 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}); // set app background color window_set_background_color(s_main_window, GColorBlack); // load initial batch of silly guy parts into memory s_head = gbitmap_create_with_resource(s_random_heads[rand() % s_head_count]); s_butt = gbitmap_create_with_resource(s_random_butts[rand() % s_butt_count]); // pick starting colors s_random_color_current = s_dark_bg_colors[rand() % s_color_count]; replace_gbitmap_color(GColorGreen, s_random_color_current, s_head, NULL); replace_gbitmap_color(GColorGreen, s_random_color_current, s_butt, NULL); // don't overwrite current color, as it will need to be replaced s_random_color_next = s_dark_bg_colors[rand() % s_color_count]; // load custom font #if PBL_DISPLAY_WIDTH <= 180 s_custom_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_RETRO_COMPUTER_28)); #else s_custom_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_RETRO_COMPUTER_34)); #endif // load bluetooth indicators s_bt0_icon = gbitmap_create_with_resource(RESOURCE_ID_BT0); s_bt1_icon = gbitmap_create_with_resource(RESOURCE_ID_BT1); // dynamically calculate coordinates for date/bt indicators #if PBL_DISPLAY_WIDTH <= 144 s_date_pos_y = PBL_DISPLAY_HEIGHT / 2 - 6; #else s_date_pos_y = PBL_DISPLAY_HEIGHT / 2 - 9; #endif s_date_bt_width = (PBL_DISPLAY_WIDTH - s_max_time_width) / 2; s_date_grect_a = GRect(s_date_pos_x, s_date_pos_y, s_date_bt_width, s_date_height); #if PBL_DISPLAY_WIDTH <= 180 s_bt_pos_x = s_date_bt_width + s_max_time_width; #else s_bt_pos_x = s_date_bt_width + s_max_time_width + 1; #endif s_bt_pos_y = (PBL_DISPLAY_HEIGHT / 2) - 12; s_bt_grect_a = GRect(s_bt_pos_x, s_bt_pos_y, s_date_bt_width, s_bt_height); // pre-create layers used in update_minute_1 //// time #if PBL_DISPLAY_WIDTH <= 180 s_time_layer = text_layer_create(GRect(0, (PBL_DISPLAY_HEIGHT / 2) - 18, PBL_DISPLAY_WIDTH, 28)); #else s_time_layer = text_layer_create(GRect(0, (PBL_DISPLAY_HEIGHT / 2) - 21, PBL_DISPLAY_WIDTH, 34)); #endif text_layer_set_background_color(s_time_layer, GColorClear); text_layer_set_font(s_time_layer, s_custom_font); text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); //// date s_date_layer = text_layer_create(s_date_grect_a); text_layer_set_background_color(s_date_layer, GColorClear); #if PBL_DISPLAY_WIDTH <= 144 text_layer_set_font(s_date_layer, fonts_get_system_font(FONT_KEY_GOTHIC_09)); #else text_layer_set_font(s_date_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD)); #endif text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter); //// bt s_bt_layer = bitmap_layer_create(s_bt_grect_a); bitmap_layer_set_compositing_mode(s_bt_layer, GCompOpSet); bitmap_layer_set_alignment(s_bt_layer, GAlignCenter); //// DEBUG memory usage layer s_memory_layer = text_layer_create(GRect(0, PBL_DISPLAY_HEIGHT - 24, PBL_DISPLAY_WIDTH, 14)); text_layer_set_background_color(s_memory_layer, GColorClear); text_layer_set_font(s_memory_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14)); text_layer_set_text_color(s_memory_layer, GColorWhite); PBL_IF_ROUND_ELSE(text_layer_set_text_alignment(s_memory_layer, GTextAlignmentCenter), text_layer_set_text_alignment(s_memory_layer, GTextAlignmentLeft)); // make sure the time is displayed from the start // and that the date/bt indicators are centered update_minute_1(); //// register with ConnectionService (bluetooth status) connection_service_subscribe((ConnectionHandlers){.pebble_app_connection_handler = bluetooth_callback}); //// force a check to display correct bt indicator to start bluetooth_callback(connection_service_peek_pebble_app_connection()); //// center data/bt indicators layer_set_frame(text_layer_get_layer(s_date_layer), s_date_grect_a); layer_set_frame(bitmap_layer_get_layer(s_bt_layer), s_bt_grect_a); // show the Window on the watch, with animated=true window_stack_push(s_main_window, true); // register with TickTimerService tick_timer_service_subscribe(MINUTE_UNIT, minute_handler); // DEBUG buttons window_set_click_config_provider(s_main_window, (ClickConfigProvider)click_config_provider); } // free memory on app exit static void deinit() { // unsubscribe tick_timer_service_unsubscribe(); // destroy layers text_layer_destroy(s_time_layer); text_layer_destroy(s_date_layer); bitmap_layer_destroy(s_bt_layer); // DEBUG memory usage layer text_layer_destroy(s_memory_layer); // unload font fonts_unload_custom_font(s_custom_font); // destroy gbitmaps gbitmap_destroy(s_head); gbitmap_destroy(s_butt); gbitmap_destroy(s_bt0_icon); gbitmap_destroy(s_bt1_icon); // destroy window window_destroy(s_main_window); } int main(void) { init(); app_event_loop(); deinit(); }