#include "src/resource_ids.auto.h" #include // types typedef struct TheTime { uint8_t HourWIP; uint8_t MinuteWIP; uint8_t HourSet; uint8_t MinuteSet; } __attribute__((__packed__)) TheTime; // layer-age static Window *s_window; static Layer *s_top_bar_layer; static Layer *s_bottom_bar_layer; //// setter static BitmapLayer *s_clock_bmp_layer; static BitmapLayer *s_check_bmp_layer; static TextLayer *s_countdown_txt_layer; static TextLayer *s_setter_txt_layer; static Layer *s_setter_highlight_layer; //// alarm static TextLayer *s_type_txt_layer; static TextLayer *s_theta_txt_layer; static TextLayer *s_parenthesis_txt_layer; static Layer *s_drop_type_layer; static Layer *s_drop_function_layer; static Layer *s_drop_over_layer; static Layer *s_drop_under_layer; static Layer *s_division_layer; static Layer *s_alarm_highlight_layer; // fonts //// setter GFont s_font_lecoish_big; GFont s_font_lecoish_small; //// alarm GFont s_font_mansalva_big; GFont s_font_mansalva_small; // bitmaps //// setter static GBitmap *s_clock_bmp; static GBitmap *s_check_bmp; // global vars static GColor s_setter_highlight_color; static uint8_t s_highlight_pos = 0; static struct TheTime s_the_time; // constants static const uint8_t s_edge_bar_height = ((PBL_DISPLAY_HEIGHT / 7) * 2); static const GRect s_top_bar_grect = GRect(0, 0, PBL_DISPLAY_WIDTH, s_edge_bar_height); static const GRect s_bottom_bar_grect = GRect(0, PBL_DISPLAY_HEIGHT - s_edge_bar_height, PBL_DISPLAY_WIDTH, s_edge_bar_height); static const GColor s_primary_color = GColorJaegerGreen; #define STORAGE_KEY_ALARM_HOUR 0 #define STORAGE_KEY_ALARM_MINUTE 1 //// alarm static const uint8_t s_drop_height = 22; static const GRect s_drop_type_grect = GRect(PBL_IF_ROUND_ELSE(48, 62), (s_top_bar_grect.size.h / 2) - PBL_IF_ROUND_ELSE(-4, s_drop_height / 2), PBL_DISPLAY_WIDTH - PBL_IF_ROUND_ELSE(96, 70), s_drop_height); static const GRect s_drop_function_grect = GRect(PBL_IF_ROUND_ELSE(55, 45), s_bottom_bar_grect.origin.y + ((s_bottom_bar_grect.size.h / 2) - PBL_IF_ROUND_ELSE(s_drop_height - 1, s_drop_height / 2)), 50, s_drop_height); static const GRect s_drop_over_grect = GRect(s_drop_function_grect.origin.x + 78, s_drop_function_grect.origin.y - 13, s_drop_function_grect.size.w + 1, s_drop_function_grect.size.h); static const GRect s_drop_under_grect = GRect(s_drop_over_grect.origin.x, s_drop_over_grect.origin.y + s_drop_over_grect.size.h + 5, s_drop_function_grect.size.w, s_drop_over_grect.size.h); ///////////////////////////////// static void glance_reload_callback(AppGlanceReloadSession *session, size_t limit, void *target) { static char s_glance_buffer[11]; snprintf(s_glance_buffer, sizeof(s_glance_buffer), "Up @ %02d:%02d", s_the_time.HourSet, s_the_time.MinuteSet); AppGlanceSlice slice = { .expiration_time = (time_t)(uintptr_t)target, .layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON, .layout.subtitle_template_string = s_glance_buffer, }; app_glance_add_slice(session, slice); } // returns (passes through) the negative value from wakeup_schedule // when an error is encountered static int8_t schedule_main_alarm() { time_t now = time(NULL); struct tm now_tm = *localtime(&now); struct tm target_tm = now_tm; target_tm.tm_hour = s_the_time.HourWIP; target_tm.tm_min = s_the_time.MinuteWIP; target_tm.tm_sec = 0; time_t target = mktime(&target_tm); if (target <= now) { target += SECONDS_PER_DAY; } wakeup_cancel_all(); WakeupId status = wakeup_schedule(target, 0, true); if (status < 0) { APP_LOG(APP_LOG_LEVEL_DEBUG, "SCHEDULING ERROR: %d", status); return status; } s_the_time.HourSet = s_the_time.HourWIP; s_the_time.MinuteSet = s_the_time.MinuteWIP; app_glance_reload(glance_reload_callback, (void *)(uintptr_t)target); return 0; } static void setter_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { struct tm now_tm = *tick_time; // build the target time today struct tm target_tm = now_tm; target_tm.tm_hour = s_the_time.HourSet; target_tm.tm_min = s_the_time.MinuteSet; target_tm.tm_sec = 0; time_t now = mktime(&now_tm); time_t target = mktime(&target_tm); if (target <= now) { target += SECONDS_PER_DAY; } int32_t diff = (int32_t)(target - now); uint8_t hours = diff / 3600; uint8_t minutes = (diff % 3600) / 60; uint8_t seconds = diff % 60; static char s_countdown_buffer[9]; snprintf(s_countdown_buffer, sizeof(s_countdown_buffer), "%02d:%02d:%02d", hours, minutes, seconds); text_layer_set_text(s_countdown_txt_layer, s_countdown_buffer); } static void update_countdown_display() { time_t now = time(NULL); setter_seconds_handler(localtime(&now), SECOND_UNIT); } static void update_setter_display() { static char s_setter_buffer[PBL_IF_ROUND_ELSE(8, 12)]; snprintf(s_setter_buffer, sizeof(s_setter_buffer), PBL_IF_ROUND_ELSE("%02d : %02d", "%02d : %02d"), s_the_time.HourWIP, s_the_time.MinuteWIP); text_layer_set_text(s_setter_txt_layer, s_setter_buffer); } static void setter_up_click_handler(void *recognizer, void *ctx) { switch (s_highlight_pos) { case 0: if (s_the_time.HourWIP == 23) { s_the_time.HourWIP = 0; } else { s_the_time.HourWIP++; } break; case 1: if (s_the_time.MinuteWIP == 59) { s_the_time.MinuteWIP = 0; } else { s_the_time.MinuteWIP++; } break; default: return; } update_setter_display(); } static void setter_down_click_handler(void *recognizer, void *ctx) { switch (s_highlight_pos) { case 0: if (s_the_time.HourWIP == 0) { s_the_time.HourWIP = 23; } else { s_the_time.HourWIP--; } break; case 1: if (s_the_time.MinuteWIP == 0) { s_the_time.MinuteWIP = 59; } else { s_the_time.MinuteWIP--; } break; default: return; } update_setter_display(); } static void setter_select_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 2) { int8_t status = schedule_main_alarm(); if (status < 0) { return; // early return to avoid indicating alarm was set when it was not } // persist persist_write_int(STORAGE_KEY_ALARM_HOUR, s_the_time.HourSet); persist_write_int(STORAGE_KEY_ALARM_MINUTE, s_the_time.MinuteSet); // update countdown immediately update_countdown_display(); // move selection back to start s_highlight_pos = 0; } else { s_highlight_pos++; } layer_mark_dirty(s_setter_highlight_layer); layer_mark_dirty(s_bottom_bar_layer); } static void setter_back_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 0) { window_stack_pop_all(true); } else { s_highlight_pos--; } layer_mark_dirty(s_setter_highlight_layer); layer_mark_dirty(s_bottom_bar_layer); } static void setter_click_config_provider(void *ctx) { window_single_repeating_click_subscribe(BUTTON_ID_UP, 60, setter_up_click_handler); window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 60, setter_down_click_handler); window_single_click_subscribe(BUTTON_ID_SELECT, setter_select_click_handler); window_single_click_subscribe(BUTTON_ID_BACK, setter_back_click_handler); } static void top_bar_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, s_primary_color); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); } static void bottom_bar_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, (s_highlight_pos == 2 ? s_setter_highlight_color : s_primary_color)); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); } static void add_bar_layers_to_window(Layer *window_layer) { s_top_bar_layer = layer_create(s_top_bar_grect); layer_set_update_proc(s_top_bar_layer, top_bar_layer_update_proc); layer_add_child(window_layer, s_top_bar_layer); s_bottom_bar_layer = layer_create(s_bottom_bar_grect); layer_set_update_proc(s_bottom_bar_layer, bottom_bar_layer_update_proc); layer_add_child(window_layer, s_bottom_bar_layer); } static void highlight_layer_update_proc(Layer *layer, GContext *ctx) { GRect fill_area = layer_get_bounds(layer); fill_area.size.w = PBL_DISPLAY_WIDTH / 2; switch (s_highlight_pos) { case 2: return; // do not draw highlight case 1: // draw highlight on minutes site fill_area.origin.x = fill_area.size.w; break; } graphics_context_set_fill_color(ctx, s_setter_highlight_color); graphics_fill_rect(ctx, fill_area, 0, GCornerNone); } static void setter_window_load() { s_setter_highlight_color = GColorMediumAquamarine; Layer *window_layer = window_get_root_layer(s_window); // top&bottom bars add_bar_layers_to_window(window_layer); // clock icon s_clock_bmp = gbitmap_create_with_resource(RESOURCE_ID_CLOCK35); s_clock_bmp_layer = bitmap_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 8), PBL_IF_ROUND_ELSE(6, 0), s_top_bar_grect.size.w, s_top_bar_grect.size.h)); bitmap_layer_set_bitmap(s_clock_bmp_layer, s_clock_bmp); bitmap_layer_set_alignment(s_clock_bmp_layer, PBL_IF_ROUND_ELSE(GAlignTop, GAlignLeft)); bitmap_layer_set_compositing_mode(s_clock_bmp_layer, GCompOpSet); layer_add_child(window_layer, bitmap_layer_get_layer(s_clock_bmp_layer)); // check icon s_check_bmp = gbitmap_create_with_resource(RESOURCE_ID_CHECK); s_check_bmp_layer = bitmap_layer_create(s_bottom_bar_grect); bitmap_layer_set_bitmap(s_check_bmp_layer, s_check_bmp); bitmap_layer_set_compositing_mode(s_check_bmp_layer, GCompOpSet); layer_add_child(window_layer, bitmap_layer_get_layer(s_check_bmp_layer)); // countdown s_font_lecoish_small = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_LECOISH_MONO_BOLD_20)); s_countdown_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 39), (s_top_bar_grect.size.h / 2) + PBL_IF_ROUND_ELSE(10, -10), PBL_IF_ROUND_ELSE(s_top_bar_grect.size.w, s_top_bar_grect.size.w - 39), 32)); text_layer_set_text_alignment(s_countdown_txt_layer, GTextAlignmentCenter); text_layer_set_text_color(s_countdown_txt_layer, GColorWhite); text_layer_set_background_color(s_countdown_txt_layer, GColorClear); text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small); layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); update_countdown_display(); // highlighter s_setter_highlight_layer = layer_create(GRect(0, s_top_bar_grect.size.h, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - (2 * s_top_bar_grect.size.h))); layer_set_update_proc(s_setter_highlight_layer, highlight_layer_update_proc); layer_add_child(window_layer, s_setter_highlight_layer); // setter s_font_lecoish_big = fonts_load_custom_font(resource_get_handle(PBL_IF_ROUND_ELSE(RESOURCE_ID_FONT_LECOISH_MONO_BOLD_60, RESOURCE_ID_FONT_LECOISH_MONO_BOLD_40))); s_setter_txt_layer = text_layer_create(GRect(0, (PBL_DISPLAY_HEIGHT / 2) - PBL_IF_ROUND_ELSE(30, 20), PBL_DISPLAY_WIDTH, PBL_IF_ROUND_ELSE(60, 40))); text_layer_set_text_alignment(s_setter_txt_layer, GTextAlignmentCenter); text_layer_set_background_color(s_setter_txt_layer, GColorClear); text_layer_set_font(s_setter_txt_layer, s_font_lecoish_big); layer_add_child(window_layer, text_layer_get_layer(s_setter_txt_layer)); update_setter_display(); tick_timer_service_subscribe(SECOND_UNIT, setter_seconds_handler); } static void setter_window_unload() { text_layer_destroy(s_setter_txt_layer); layer_destroy(s_setter_highlight_layer); text_layer_destroy(s_countdown_txt_layer); bitmap_layer_destroy(s_check_bmp_layer); bitmap_layer_destroy(s_clock_bmp_layer); gbitmap_destroy(s_check_bmp); gbitmap_destroy(s_clock_bmp); layer_destroy(s_bottom_bar_layer); layer_destroy(s_top_bar_layer); fonts_unload_custom_font(s_font_lecoish_big); fonts_unload_custom_font(s_font_lecoish_small); } static void schedule_fallback_alarm() { wakeup_cancel_all(); time_t target_time = time(NULL); WakeupId status; while (true) { target_time += 15; status = wakeup_schedule(target_time, 0, true); if (status >= 0) { break; } } } static void alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { if (tick_time->tm_sec % 15 == 0) { schedule_fallback_alarm(); } vibes_short_pulse(); } static void drop_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, layer_get_bounds(layer), 5, GCornersAll); } static void division_layer_update_proc(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); graphics_context_set_stroke_color(ctx, GColorWhite); graphics_draw_line(ctx, GPoint(bounds.origin.x, bounds.origin.y), GPoint(bounds.origin.x + bounds.size.w, bounds.origin.y)); } static void alarm_highlight_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_stroke_color(ctx, GColorRed); graphics_context_set_stroke_width(ctx, 3); graphics_draw_rect(ctx, layer_get_bounds(layer)); } static void alarm_window_load() { s_setter_highlight_color = s_primary_color; Layer *window_layer = window_get_root_layer(s_window); // top&bottom bars add_bar_layers_to_window(window_layer); // type text s_font_mansalva_small = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_MANSALVA_REGULAR_22)); s_type_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 7), PBL_IF_ROUND_ELSE(4, 16), PBL_DISPLAY_WIDTH, 26)); text_layer_set_text_alignment(s_type_txt_layer, PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft)); text_layer_set_background_color(s_type_txt_layer, GColorClear); text_layer_set_text_color(s_type_txt_layer, GColorWhite); text_layer_set_font(s_type_txt_layer, s_font_mansalva_small); text_layer_set_text(s_type_txt_layer, "type:"); layer_add_child(window_layer, text_layer_get_layer(s_type_txt_layer)); // theta text s_theta_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 8), s_bottom_bar_grect.origin.y + PBL_IF_ROUND_ELSE(50, 16), PBL_DISPLAY_WIDTH, 26)); text_layer_set_text_alignment(s_theta_txt_layer, PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft)); text_layer_set_background_color(s_theta_txt_layer, GColorClear); text_layer_set_text_color(s_theta_txt_layer, GColorWhite); text_layer_set_font(s_theta_txt_layer, s_font_mansalva_small); text_layer_set_text(s_theta_txt_layer, PBL_IF_ROUND_ELSE("= θ", "θ =")); layer_add_child(window_layer, text_layer_get_layer(s_theta_txt_layer)); // drop backgrounds //// type s_drop_type_layer = layer_create(s_drop_type_grect); layer_set_update_proc(s_drop_type_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_type_layer); //// function s_drop_function_layer = layer_create(s_drop_function_grect); layer_set_update_proc(s_drop_function_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_function_layer); //// over s_drop_over_layer = layer_create(s_drop_over_grect); layer_set_update_proc(s_drop_over_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_over_layer); //// under s_drop_under_layer = layer_create(s_drop_under_grect); layer_set_update_proc(s_drop_under_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_under_layer); // parenthesis text s_font_mansalva_big = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_MANSALVA_REGULAR_50)); s_parenthesis_txt_layer = text_layer_create(GRect(s_drop_function_grect.origin.x + s_drop_function_grect.size.w + 2, s_bottom_bar_grect.origin.y - PBL_IF_ROUND_ELSE(12, 5), 200, 60)); text_layer_set_background_color(s_parenthesis_txt_layer, GColorClear); text_layer_set_text_color(s_parenthesis_txt_layer, GColorWhite); text_layer_set_font(s_parenthesis_txt_layer, s_font_mansalva_big); text_layer_set_text(s_parenthesis_txt_layer, "( )"); layer_add_child(window_layer, text_layer_get_layer(s_parenthesis_txt_layer)); // division layer s_division_layer = layer_create(GRect(s_drop_function_grect.origin.x + s_drop_function_grect.size.w + 25, s_drop_function_grect.origin.y + s_drop_function_grect.size.h / 2, 57, 1)); layer_set_update_proc(s_division_layer, division_layer_update_proc); layer_add_child(window_layer, s_division_layer); // highlighter s_alarm_highlight_layer = layer_create(s_drop_type_grect); layer_set_update_proc(s_alarm_highlight_layer, alarm_highlight_layer_update_proc); layer_add_child(window_layer, s_alarm_highlight_layer); schedule_fallback_alarm(); tick_timer_service_subscribe(SECOND_UNIT, alarm_seconds_handler); } static void alarm_window_unload() { layer_destroy(s_alarm_highlight_layer); layer_destroy(s_division_layer); text_layer_destroy(s_parenthesis_txt_layer); layer_destroy(s_drop_under_layer); layer_destroy(s_drop_over_layer); layer_destroy(s_drop_function_layer); layer_destroy(s_drop_type_layer); text_layer_destroy(s_theta_txt_layer); text_layer_destroy(s_type_txt_layer); layer_destroy(s_bottom_bar_layer); layer_destroy(s_top_bar_layer); fonts_unload_custom_font(s_font_mansalva_big); fonts_unload_custom_font(s_font_mansalva_small); } static void set_alarm_highlighter_frame() { switch (s_highlight_pos) { case 0: layer_set_frame(s_alarm_highlight_layer, s_drop_type_grect); break; case 1: layer_set_frame(s_alarm_highlight_layer, s_drop_function_grect); break; case 2: layer_set_frame(s_alarm_highlight_layer, s_drop_over_grect); break; case 3: layer_set_frame(s_alarm_highlight_layer, s_drop_under_grect); } } static void alarm_up_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 0) { s_highlight_pos = 3; } else { s_highlight_pos--; } set_alarm_highlighter_frame(); } static void alarm_down_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 3) { s_highlight_pos = 0; } else { s_highlight_pos++; } set_alarm_highlighter_frame(); } // DEBUG: logic will eventually be mapped to when triangle is solved static void alarm_select_click_handler(void *recognizer, void *ctx) { wakeup_cancel_all(); int8_t status = schedule_main_alarm(); while (status < 0) { s_the_time.MinuteWIP++; if (s_the_time.MinuteWIP >= 60) { s_the_time.MinuteWIP = 0; s_the_time.HourWIP++; if (s_the_time.HourWIP >= 24) { s_the_time.HourWIP = 0; } } status = schedule_main_alarm(); } window_stack_pop_all(true); } static void alarm_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_BACK, NULL); window_single_click_subscribe(BUTTON_ID_UP, alarm_up_click_handler); window_single_click_subscribe(BUTTON_ID_DOWN, alarm_down_click_handler); window_long_click_subscribe(BUTTON_ID_SELECT, 5000, alarm_select_click_handler, NULL); } static void init() { s_window = window_create(); s_the_time.HourWIP = persist_read_int(STORAGE_KEY_ALARM_HOUR); s_the_time.MinuteWIP = persist_read_int(STORAGE_KEY_ALARM_MINUTE); s_the_time.HourSet = s_the_time.HourWIP; s_the_time.MinuteSet = s_the_time.MinuteWIP; switch (launch_reason()) { case APP_LAUNCH_PHONE: // DEBUG, REMOVE PRIOR TO RELEASE! case APP_LAUNCH_WAKEUP: window_set_window_handlers( s_window, (WindowHandlers){.load = alarm_window_load, .unload = alarm_window_unload}); window_set_click_config_provider(s_window, alarm_click_config_provider); window_stack_push(s_window, false); break; default: window_set_window_handlers( s_window, (WindowHandlers){.load = setter_window_load, .unload = setter_window_unload}); window_set_click_config_provider(s_window, setter_click_config_provider); window_stack_push(s_window, false); }; } static void deinit() { window_destroy(s_window); } int main(void) { init(); app_event_loop(); deinit(); }