1184 lines
45 KiB
C
1184 lines
45 KiB
C
#include "scheduler.h"
|
|
#include "settings.h"
|
|
#include "triangles.h"
|
|
#include <pebble.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
// settings
|
|
static AppSettings settings;
|
|
|
|
// layer-age
|
|
static Window *s_window;
|
|
static Window *s_status_window;
|
|
static Window *s_drop_window;
|
|
static Layer *s_top_bar_layer;
|
|
static Layer *s_bottom_bar_layer;
|
|
//// status
|
|
static ScrollLayer *s_status_scroll_layer;
|
|
static TextLayer *s_status_txt_layer;
|
|
//// setter
|
|
static BitmapLayer *s_clock_bmp_layer;
|
|
static BitmapLayer *s_check_bmp_layer;
|
|
static TextLayer *s_setter_txt_layer;
|
|
static Layer *s_setter_highlight_layer;
|
|
//// alarm
|
|
static BitmapLayer *s_x_bmp_layer;
|
|
static Animation *s_x_anim_master;
|
|
static Layer *s_graph_layer;
|
|
static TextLayer *s_theta_txt_layer;
|
|
static TextLayer *s_parenthesis_txt_layer;
|
|
static Layer *s_drop_bg_function_layer;
|
|
static Layer *s_drop_bg_over_layer;
|
|
static Layer *s_drop_bg_under_layer;
|
|
static TextLayer *s_drop_txt_function_layer;
|
|
static TextLayer *s_drop_txt_over_layer;
|
|
static TextLayer *s_drop_txt_under_layer;
|
|
static Layer *s_division_layer;
|
|
static Layer *s_alarm_highlight_layer;
|
|
static MenuLayer *s_drop_menu_layer;
|
|
static RightTriangleLayer *s_triangle_layer;
|
|
static TextLayer *s_clock_txt_layer;
|
|
static TextLayer *s_countdown_txt_layer;
|
|
static TextLayer *s_progress_txt_layer;
|
|
static TextLayer *s_side_a_txt_layer;
|
|
static TextLayer *s_side_b_txt_layer;
|
|
static TextLayer *s_side_c_txt_layer;
|
|
|
|
// fonts
|
|
GFont s_font_lecoish_small;
|
|
//// setter
|
|
GFont s_font_lecoish_big;
|
|
//// alarm
|
|
GFont s_font_mansalva_big;
|
|
GFont s_font_mansalva_small;
|
|
|
|
// bitmaps
|
|
//// setter
|
|
static GBitmap *s_clock_bmp;
|
|
static GBitmap *s_check_bmp;
|
|
//// alarm
|
|
static GBitmap *s_x_bmp;
|
|
|
|
// global vars
|
|
static WakeupReason s_alarm_reason;
|
|
static GColor s_setter_highlight_color;
|
|
static uint8_t s_highlight_pos = 0;
|
|
static bool s_skip_current_day = false;
|
|
//// alarm
|
|
static uint8_t s_current_triangle_num;
|
|
static uint8_t s_solution_indices[3];
|
|
static uint8_t s_selected_indices[3] = {9, 9, 9};
|
|
static uint8_t s_secs_since_last_input;
|
|
static uint8_t s_shake_cooldown;
|
|
static uint8_t s_zap_cooldown;
|
|
static uint8_t s_countdown_int = 0;
|
|
static bool s_continuous_zap_on_first_triangle;
|
|
static char s_countdown_buf[4];
|
|
static char s_progress_buf[8];
|
|
|
|
// constants
|
|
//// shared (setter+alarm)
|
|
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;
|
|
//// status
|
|
#define STATUS_TEXT_INSET 4
|
|
//// alarm
|
|
#define MAX_SECS_WITHOUT_INPUT 10
|
|
#define SHAKE_COOLDOWN_SECS 2
|
|
#define ZAP_COOLDOWN_SECS 4
|
|
static const uint8_t s_drop_height = 22;
|
|
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 const char *s_sin_arr[2] = {"sin¯¹", "csc¯¹"};
|
|
static const char *s_cos_arr[2] = {"cos¯¹", "sec¯¹"};
|
|
static const char *s_tan_arr[2] = {"tan¯¹", "cot¯¹"};
|
|
static const char *s_sin;
|
|
static const char *s_cos;
|
|
static const char *s_tan;
|
|
static const uint32_t s_first_triangle_vibe_segments[] = {200, 150, 200, 150, 200};
|
|
static const VibePattern s_first_triangle_vibe_pattern = {
|
|
.durations = s_first_triangle_vibe_segments,
|
|
.num_segments = ARRAY_LENGTH(s_first_triangle_vibe_segments),
|
|
};
|
|
|
|
// alarm drop menu options
|
|
static const char *s_options_function[3];
|
|
static char *s_options_over[2];
|
|
static char *s_options_under[2];
|
|
|
|
////////////////////// STATUS //////////////////////
|
|
|
|
typedef enum {
|
|
STATUS_TUTORIAL = 1,
|
|
STATUS_CONFIRMATION = 2,
|
|
STATUS_FAILURE = 3
|
|
} StatusWindowPurpose;
|
|
|
|
static void push_setter_window();
|
|
|
|
static void click_handler_push_setter(void *recognizer, void *ctx) {
|
|
s_highlight_pos = 0;
|
|
window_stack_pop_all(true);
|
|
push_setter_window();
|
|
}
|
|
|
|
static void click_handler_disable_tutorial_and_push_setter(void *recognizer, void *ctx) {
|
|
settings.TutorialEnabled = false;
|
|
persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings));
|
|
click_handler_push_setter(recognizer, ctx);
|
|
}
|
|
|
|
static void click_handler_push_setter_skip_current_day(void *recognizer, void *ctx) {
|
|
s_skip_current_day = true; // setting the next alarm should assume the user meant tomorrow
|
|
click_handler_push_setter(recognizer, ctx);
|
|
}
|
|
|
|
static void click_handler_exit(void *recognizer, void *ctx) {
|
|
window_stack_pop_all(true);
|
|
}
|
|
|
|
static void status_tutorial_click_config_provider(void *ctx) {
|
|
window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_push_setter);
|
|
window_single_click_subscribe(BUTTON_ID_BACK, click_handler_disable_tutorial_and_push_setter);
|
|
}
|
|
|
|
static void status_confirmation_click_config_provider(void *ctx) {
|
|
window_single_click_subscribe(BUTTON_ID_BACK, click_handler_push_setter_skip_current_day);
|
|
window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_exit);
|
|
}
|
|
|
|
static void status_set_failure_click_config_provider(void *ctx) {
|
|
window_single_click_subscribe(BUTTON_ID_BACK, click_handler_push_setter);
|
|
window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_push_setter);
|
|
window_single_click_subscribe(BUTTON_ID_UP, click_handler_push_setter);
|
|
window_single_click_subscribe(BUTTON_ID_DOWN, click_handler_push_setter);
|
|
}
|
|
|
|
static void status_window_load(StatusWindowPurpose purpose) {
|
|
Layer *window_layer = window_get_root_layer(s_status_window);
|
|
GRect bounds = layer_get_bounds(window_layer);
|
|
s_status_txt_layer = text_layer_create(GRect(0, 0, bounds.size.w, 750));
|
|
text_layer_set_font(s_status_txt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24));
|
|
text_layer_set_background_color(s_status_txt_layer, GColorClear);
|
|
text_layer_set_text_alignment(s_status_txt_layer, GTextAlignmentCenter);
|
|
|
|
switch (purpose) {
|
|
case STATUS_TUTORIAL:
|
|
text_layer_set_text(s_status_txt_layer, "\nWelcome to Trigalarm!\n\nIn Trigalarm, you set one alarm that reoccurs every day and cannot be disabled. You can, however, change the time whenever you please.\n\nWhen your alarm goes off, you are presented with a (configurable) set of triangles. Each triangle has its own time limit it must be solved within in order to progress. Shake the watch to submit each answer.\n\nIf a triangle's time limit expires, your progress will be set back by one. Solve every triangle to disarm the alarm!\n\nSELECT: \"Okay!\"\nBACK: \"Shut up!\"\n\n\n");
|
|
s_status_scroll_layer = scroll_layer_create(layer_get_frame(window_layer));
|
|
#ifdef PBL_ROUND
|
|
scroll_layer_set_paging(s_status_scroll_layer, true);
|
|
#endif
|
|
scroll_layer_set_callbacks(s_status_scroll_layer, (ScrollLayerCallbacks){.click_config_provider = status_tutorial_click_config_provider});
|
|
scroll_layer_set_click_config_onto_window(s_status_scroll_layer, s_status_window);
|
|
scroll_layer_add_child(s_status_scroll_layer, text_layer_get_layer(s_status_txt_layer));
|
|
layer_add_child(window_layer, scroll_layer_get_layer(s_status_scroll_layer));
|
|
text_layer_enable_screen_text_flow_and_paging(s_status_txt_layer, STATUS_TEXT_INSET);
|
|
GSize max_size = text_layer_get_content_size(s_status_txt_layer);
|
|
text_layer_set_size(s_status_txt_layer, max_size);
|
|
scroll_layer_set_content_size(s_status_scroll_layer, GSize(bounds.size.w, max_size.h));
|
|
return;
|
|
case STATUS_CONFIRMATION: {
|
|
static char s_status_confirmed_buffer[88];
|
|
snprintf(s_status_confirmed_buffer, sizeof(s_status_confirmed_buffer), "Alarm disarmed and re-set (%02u:%02u) successfully.\n\nSELECT: Exit\nBACK: Change alarm time", the_time.HourSet, the_time.MinuteSet);
|
|
text_layer_set_text(s_status_txt_layer, s_status_confirmed_buffer);
|
|
window_set_click_config_provider(s_status_window, status_confirmation_click_config_provider);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_status_txt_layer));
|
|
break;
|
|
}
|
|
case STATUS_FAILURE:
|
|
text_layer_set_text(s_status_txt_layer, "Failed to set an alarm.\nPress any button to return to alarm setter.");
|
|
window_set_click_config_provider(s_status_window, status_set_failure_click_config_provider);
|
|
}
|
|
layer_add_child(window_layer, text_layer_get_layer(s_status_txt_layer));
|
|
layer_set_frame(text_layer_get_layer(s_status_txt_layer), GRect(0, (PBL_DISPLAY_HEIGHT / 2) - (text_layer_get_content_size(s_status_txt_layer).h / 2), PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT));
|
|
text_layer_enable_screen_text_flow_and_paging(s_status_txt_layer, STATUS_TEXT_INSET);
|
|
}
|
|
|
|
static void status_window_unload() {
|
|
text_layer_destroy(s_status_txt_layer);
|
|
if (s_status_scroll_layer != NULL) {
|
|
scroll_layer_destroy(s_status_scroll_layer);
|
|
s_status_scroll_layer = NULL;
|
|
}
|
|
window_destroy(s_status_window);
|
|
}
|
|
|
|
static void push_status_window(StatusWindowPurpose purpose) {
|
|
s_status_window = window_create();
|
|
window_set_window_handlers(
|
|
s_status_window,
|
|
(WindowHandlers){.unload = status_window_unload});
|
|
status_window_load(purpose);
|
|
window_stack_push(s_status_window, true);
|
|
}
|
|
|
|
////////////////////// MISC //////////////////////
|
|
|
|
static time_t compute_alarm_time(uint8_t hour, uint8_t minute) {
|
|
time_t now = time(NULL);
|
|
struct tm target_tm = *localtime(&now);
|
|
|
|
bool already_passed_today =
|
|
(hour < target_tm.tm_hour) ||
|
|
(hour == target_tm.tm_hour && minute <= target_tm.tm_min);
|
|
|
|
target_tm.tm_hour = hour;
|
|
target_tm.tm_min = minute;
|
|
target_tm.tm_sec = 0;
|
|
|
|
if (s_skip_current_day || already_passed_today) {
|
|
target_tm.tm_mday += 1;
|
|
}
|
|
|
|
return mktime(&target_tm);
|
|
}
|
|
|
|
static bool schedule_alarm(WakeupReason schedule_reason) {
|
|
time_t target;
|
|
WakeupId status;
|
|
|
|
switch (schedule_reason) {
|
|
case WAKE_ALARM: {
|
|
target = compute_alarm_time(the_time.HourWIP, the_time.MinuteWIP);
|
|
wakeup_cancel_all();
|
|
status = wakeup_schedule_simple_robust(target, schedule_reason);
|
|
break;
|
|
}
|
|
case WAKE_RECOVERY_ALARM:
|
|
target = time(NULL) + 15;
|
|
// no need to cancel alarms, as recovery alarms are short-lived and are expired by the time another is being scheduled
|
|
status = wakeup_schedule_simple_robust(target, schedule_reason);
|
|
}
|
|
if (status < 0) {
|
|
push_status_window(STATUS_FAILURE);
|
|
persist_write_int(STORAGE_KEY_ALARM_REASON, WAKE_ALARM);
|
|
s_alarm_reason = WAKE_ALARM;
|
|
return false;
|
|
}
|
|
if (persist_read_int(STORAGE_KEY_ALARM_REASON) != schedule_reason) {
|
|
persist_write_int(STORAGE_KEY_ALARM_REASON, schedule_reason);
|
|
s_alarm_reason = schedule_reason;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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, bool small_top) {
|
|
if (small_top) {
|
|
s_top_bar_layer = layer_create(GRect(s_top_bar_grect.origin.x, s_top_bar_grect.origin.y, s_top_bar_grect.size.w, s_top_bar_grect.size.h / 2));
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
////////////////////// PKJS //////////////////////
|
|
|
|
static void apply_uint8_from_tuple(DictionaryIterator *iter, uint32_t key, void *dest) {
|
|
Tuple *t = dict_find(iter, key);
|
|
if (!t)
|
|
return;
|
|
*(uint8_t *)dest = t->value->uint8;
|
|
}
|
|
|
|
static void inbox_received_handler(DictionaryIterator *iter, void *context) {
|
|
Tuple *ready_tuple = dict_find(iter, MESSAGE_KEY_PKJS_READY);
|
|
if (ready_tuple) {
|
|
return;
|
|
}
|
|
|
|
// handle alarm reset requests
|
|
if (dict_find(iter, MESSAGE_KEY_PKJS_ALARM_RESET)) {
|
|
wakeup_service_subscribe(NULL);
|
|
the_time.HourWIP = 0;
|
|
the_time.MinuteWIP = 0;
|
|
s_alarm_reason = WAKE_ALARM;
|
|
if (!schedule_alarm(WAKE_ALARM)) {
|
|
return; // leave the failure status window visible
|
|
}
|
|
persist_write_int(STORAGE_KEY_ALARM_HOUR, the_time.HourSet);
|
|
persist_write_int(STORAGE_KEY_ALARM_MINUTE, the_time.MinuteSet);
|
|
window_stack_pop_all(true);
|
|
return;
|
|
}
|
|
|
|
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE, &settings.PavlokEnabled);
|
|
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_INTENSITY, &settings.PavlokIntensity);
|
|
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_COUNT, &settings.TriangleCount);
|
|
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_SECS_MIN, &settings.TriangleSecsMin);
|
|
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_SECS_STEP, &settings.TriangleSecsStep);
|
|
persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings));
|
|
}
|
|
|
|
static void send_zap() {
|
|
if (!settings.PavlokEnabled || s_zap_cooldown != 0) {
|
|
return;
|
|
}
|
|
|
|
DictionaryIterator *out;
|
|
AppMessageResult result = app_message_outbox_begin(&out);
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
dict_write_uint8(out, MESSAGE_KEY_PKJS_PAVLOK_ZAP, settings.PavlokIntensity);
|
|
result = app_message_outbox_send();
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
|
|
s_zap_cooldown = ZAP_COOLDOWN_SECS;
|
|
}
|
|
|
|
////////////////////// ALARM //////////////////////
|
|
|
|
static void set_alarm_highlighter_frame() {
|
|
switch (s_highlight_pos) {
|
|
case 0:
|
|
layer_set_frame(s_alarm_highlight_layer, s_drop_function_grect);
|
|
break;
|
|
case 1:
|
|
layer_set_frame(s_alarm_highlight_layer, s_drop_over_grect);
|
|
break;
|
|
case 2:
|
|
layer_set_frame(s_alarm_highlight_layer, s_drop_under_grect);
|
|
}
|
|
}
|
|
|
|
static void clear_selections() {
|
|
for (int i = 0; i < 3; ++i) {
|
|
s_selected_indices[i] = 9;
|
|
}
|
|
s_highlight_pos = 0;
|
|
set_alarm_highlighter_frame();
|
|
text_layer_set_text(s_drop_txt_function_layer, "");
|
|
text_layer_set_text(s_drop_txt_over_layer, "");
|
|
text_layer_set_text(s_drop_txt_under_layer, "");
|
|
}
|
|
|
|
static void update_for_new_triangle() {
|
|
clear_selections();
|
|
|
|
// pick & display 2 side lengths to the user
|
|
static char side_a_buffer[8], side_b_buffer[8], side_c_buffer[8];
|
|
uint8_t ignored_side = rand() % 3;
|
|
uint8_t sin_index = rand() % 2;
|
|
uint8_t cos_index = rand() % 2;
|
|
uint8_t tan_index = rand() % 2;
|
|
switch (ignored_side) {
|
|
case 0:
|
|
side_a_buffer[0] = '\0';
|
|
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
|
|
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
|
|
s_options_over[0] = side_b_buffer;
|
|
s_options_over[1] = side_c_buffer;
|
|
s_options_under[0] = side_b_buffer;
|
|
s_options_under[1] = side_c_buffer;
|
|
|
|
// store solution
|
|
s_solution_indices[0] = 2; // tan/cot
|
|
if (s_triangle_layer->theta_angle_is_left) {
|
|
if (tan_index == 0) { // tan
|
|
s_solution_indices[1] = 0; // b (opposite)
|
|
s_solution_indices[2] = 1; // c (adjacent)
|
|
} else { // cot
|
|
s_solution_indices[1] = 1; // c (adjacent)
|
|
s_solution_indices[2] = 0; // b (opposite)
|
|
}
|
|
} else {
|
|
if (tan_index == 0) { // tan
|
|
s_solution_indices[1] = 1; // c (opposite)
|
|
s_solution_indices[2] = 0; // b (adjacent)
|
|
} else { // cot
|
|
s_solution_indices[1] = 0; // b (adjacent)
|
|
s_solution_indices[2] = 1; // c (opposite)
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 1:
|
|
side_b_buffer[0] = '\0';
|
|
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
|
|
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
|
|
s_options_over[0] = side_a_buffer;
|
|
s_options_over[1] = side_c_buffer;
|
|
s_options_under[0] = side_a_buffer;
|
|
s_options_under[1] = side_c_buffer;
|
|
|
|
// store solution
|
|
if (s_triangle_layer->theta_angle_is_left) {
|
|
s_solution_indices[0] = 1; // cos/sec
|
|
if (cos_index == 0) { // cos
|
|
s_solution_indices[1] = 1; // c (adjacent)
|
|
s_solution_indices[2] = 0; // a (hypotenuse)
|
|
} else { // sec
|
|
s_solution_indices[1] = 0; // a (hypotenuse)
|
|
s_solution_indices[2] = 1; // c (adjacent)
|
|
}
|
|
} else {
|
|
s_solution_indices[0] = 0; // sin/csc
|
|
if (sin_index == 0) { // sin
|
|
s_solution_indices[1] = 1; // c (opposite)
|
|
s_solution_indices[2] = 0; // a (hypotenuse)
|
|
} else { // csc
|
|
s_solution_indices[1] = 0; // a (hypotenuse)
|
|
s_solution_indices[2] = 1; // c (opposite)
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 2:
|
|
side_c_buffer[0] = '\0';
|
|
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
|
|
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
|
|
s_options_over[0] = side_a_buffer;
|
|
s_options_over[1] = side_b_buffer;
|
|
s_options_under[0] = side_a_buffer;
|
|
s_options_under[1] = side_b_buffer;
|
|
|
|
// store solution
|
|
if (s_triangle_layer->theta_angle_is_left) {
|
|
s_solution_indices[0] = 0; // sin/csc
|
|
if (sin_index == 0) { // sin
|
|
s_solution_indices[1] = 1; // b (opposite)
|
|
s_solution_indices[2] = 0; // a (hypotenuse)
|
|
} else { // csc
|
|
s_solution_indices[1] = 0; // a (hypotenuse)
|
|
s_solution_indices[2] = 1; // b (opposite)
|
|
}
|
|
} else {
|
|
s_solution_indices[0] = 1;
|
|
if (cos_index == 0) { // cos
|
|
s_solution_indices[1] = 1; // b (adjacent)
|
|
s_solution_indices[2] = 0; // a (hypotenuse)
|
|
} else { // sec
|
|
s_solution_indices[1] = 0; // a (hypotenuse)
|
|
s_solution_indices[2] = 1; // b (adjacent)
|
|
}
|
|
}
|
|
}
|
|
text_layer_set_text(s_side_a_txt_layer, side_a_buffer);
|
|
text_layer_set_text(s_side_b_txt_layer, side_b_buffer);
|
|
text_layer_set_text(s_side_c_txt_layer, side_c_buffer);
|
|
|
|
// set trigonometric function options
|
|
s_sin = s_sin_arr[sin_index];
|
|
s_cos = s_cos_arr[cos_index];
|
|
s_tan = s_tan_arr[tan_index];
|
|
s_options_function[0] = s_sin;
|
|
s_options_function[1] = s_cos;
|
|
s_options_function[2] = s_tan;
|
|
|
|
// setup countdown
|
|
s_countdown_int = settings.TriangleSecsMin + ((settings.TriangleCount - s_current_triangle_num) * settings.TriangleSecsStep);
|
|
snprintf(s_countdown_buf, sizeof(s_countdown_buf), "%03u", s_countdown_int);
|
|
text_layer_set_text(s_countdown_txt_layer, s_countdown_buf);
|
|
|
|
// set progress counter
|
|
snprintf(s_progress_buf, sizeof(s_progress_buf), "%d/%d", s_current_triangle_num, settings.TriangleCount);
|
|
text_layer_set_text(s_progress_txt_layer, s_progress_buf);
|
|
}
|
|
|
|
static void increment_secs_since_last_input() {
|
|
if (s_secs_since_last_input < MAX_SECS_WITHOUT_INPUT) {
|
|
s_secs_since_last_input++;
|
|
}
|
|
}
|
|
|
|
static void decrement_cooldown(uint8_t *cooldown) {
|
|
if (*cooldown != 0) {
|
|
(*cooldown)--;
|
|
}
|
|
}
|
|
|
|
static void alarm_tick_handler(struct tm *tick_time, TimeUnits units_changed) {
|
|
// seconds
|
|
s_countdown_int--;
|
|
decrement_cooldown(&s_shake_cooldown);
|
|
if (s_current_triangle_num == 1) {
|
|
decrement_cooldown(&s_zap_cooldown);
|
|
increment_secs_since_last_input();
|
|
if (s_secs_since_last_input >= MAX_SECS_WITHOUT_INPUT) { // pause stimulus during user input; this fixes shake detection and saves battery
|
|
vibes_enqueue_custom_pattern(s_first_triangle_vibe_pattern);
|
|
if (s_continuous_zap_on_first_triangle) {
|
|
send_zap();
|
|
}
|
|
}
|
|
}
|
|
snprintf(s_countdown_buf, sizeof(s_countdown_buf), "%03u", s_countdown_int);
|
|
text_layer_set_text(s_countdown_txt_layer, s_countdown_buf);
|
|
if (s_countdown_int == 0) {
|
|
s_continuous_zap_on_first_triangle = true; // begin zapping once the first countdown expires
|
|
send_zap();
|
|
if (s_current_triangle_num > 1) {
|
|
s_current_triangle_num--;
|
|
}
|
|
s_secs_since_last_input = 10;
|
|
right_triangle_layer_regen(s_triangle_layer);
|
|
update_for_new_triangle();
|
|
}
|
|
|
|
// minutes
|
|
if (units_changed == 0 || (units_changed & MINUTE_UNIT)) {
|
|
static char s_clock_buffer[8];
|
|
strftime(s_clock_buffer, sizeof(s_clock_buffer), clock_is_24h_style() ? "%H:%M" : "%I:%M", tick_time);
|
|
text_layer_set_text(s_clock_txt_layer, s_clock_buffer);
|
|
}
|
|
}
|
|
|
|
static void graph_layer_update_proc(Layer *layer, GContext *ctx) {
|
|
GRect bounds = layer_get_bounds(layer);
|
|
static const uint8_t s_pixel_gap = PBL_DISPLAY_WIDTH / 20;
|
|
graphics_context_set_stroke_color(ctx, GColorElectricBlue);
|
|
for (int i = s_pixel_gap; i < PBL_DISPLAY_WIDTH; i += s_pixel_gap) {
|
|
graphics_draw_line(ctx, GPoint(0, i), GPoint(bounds.size.w, i)); // horizontal line
|
|
graphics_draw_line(ctx, GPoint(i, 0), GPoint(i, bounds.size.h)); // vertical line
|
|
}
|
|
}
|
|
|
|
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_up_click_handler(void *recognizer, void *ctx) {
|
|
s_secs_since_last_input = 0;
|
|
if (s_highlight_pos == 0) {
|
|
s_highlight_pos = 2;
|
|
} else {
|
|
s_highlight_pos--;
|
|
}
|
|
set_alarm_highlighter_frame();
|
|
}
|
|
|
|
static void alarm_down_click_handler(void *recognizer, void *ctx) {
|
|
s_secs_since_last_input = 0;
|
|
if (s_highlight_pos == 2) {
|
|
s_highlight_pos = 0;
|
|
} else {
|
|
s_highlight_pos++;
|
|
}
|
|
set_alarm_highlighter_frame();
|
|
}
|
|
|
|
static void alarm_select_click_handler(void *recognizer, void *ctx) {
|
|
s_secs_since_last_input = 0;
|
|
window_stack_push(s_drop_window, true);
|
|
}
|
|
|
|
static void accel_tap_handler(AccelAxisType axis, int32_t direction) {
|
|
if (s_shake_cooldown != 0) {
|
|
return;
|
|
}
|
|
|
|
// check solution
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (s_selected_indices[i] != s_solution_indices[i]) {
|
|
animation_schedule(animation_clone(s_x_anim_master));
|
|
clear_selections();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (s_countdown_int > 0) {
|
|
if (s_current_triangle_num == settings.TriangleCount) {
|
|
tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window
|
|
wakeup_service_subscribe(NULL); // ignore wakeup events to avoid overwriting new alarm
|
|
if (schedule_alarm(WAKE_ALARM)) {
|
|
push_status_window(STATUS_CONFIRMATION);
|
|
}
|
|
} else {
|
|
s_current_triangle_num++;
|
|
right_triangle_layer_regen(s_triangle_layer);
|
|
update_for_new_triangle();
|
|
}
|
|
}
|
|
|
|
s_shake_cooldown = SHAKE_COOLDOWN_SECS;
|
|
}
|
|
|
|
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_single_click_subscribe(BUTTON_ID_SELECT, alarm_select_click_handler);
|
|
}
|
|
|
|
static uint16_t menu_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) {
|
|
if (s_highlight_pos == 0) {
|
|
return 3;
|
|
} else {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
static void menu_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) {
|
|
switch (s_highlight_pos) {
|
|
case 0:
|
|
menu_cell_basic_draw(ctx, cell_layer, s_options_function[cell_index->row], NULL, NULL);
|
|
break;
|
|
case 1:
|
|
menu_cell_basic_draw(ctx, cell_layer, s_options_over[cell_index->row], NULL, NULL);
|
|
break;
|
|
case 2:
|
|
menu_cell_basic_draw(ctx, cell_layer, s_options_under[cell_index->row], NULL, NULL);
|
|
}
|
|
}
|
|
|
|
static void menu_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) {
|
|
switch (s_highlight_pos) {
|
|
case 0:
|
|
text_layer_set_text(s_drop_txt_function_layer, s_options_function[cell_index->row]);
|
|
break;
|
|
case 1:
|
|
text_layer_set_text(s_drop_txt_over_layer, s_options_over[cell_index->row]);
|
|
break;
|
|
case 2:
|
|
text_layer_set_text(s_drop_txt_under_layer, s_options_under[cell_index->row]);
|
|
}
|
|
s_selected_indices[s_highlight_pos] = cell_index->row;
|
|
window_stack_remove(s_drop_window, true);
|
|
}
|
|
|
|
// when the user presses select, call this to load a menu;
|
|
// this function will determine the type of menu to load by checking the highlight position;
|
|
// once the user presses select in this menu, the result is written to a text layer and the menu is exited
|
|
static void drop_menu_window_load(Window *window) {
|
|
Layer *window_layer = window_get_root_layer(window);
|
|
s_drop_menu_layer = menu_layer_create(layer_get_bounds(window_layer));
|
|
menu_layer_set_callbacks(s_drop_menu_layer, NULL, (MenuLayerCallbacks){
|
|
.get_num_rows = menu_get_num_rows,
|
|
.draw_row = menu_draw_row,
|
|
.select_click = menu_select,
|
|
});
|
|
menu_layer_set_click_config_onto_window(s_drop_menu_layer, window);
|
|
layer_add_child(window_layer, menu_layer_get_layer(s_drop_menu_layer));
|
|
}
|
|
|
|
static void drop_menu_window_unload(Window *window) {
|
|
menu_layer_destroy(s_drop_menu_layer);
|
|
s_drop_menu_layer = NULL;
|
|
}
|
|
|
|
static void live_wakeup_handler_for_alarm(WakeupId id, int32_t wakeup_reason) {
|
|
if (wakeup_reason == WAKE_RECOVERY_ALARM) {
|
|
// if recovery alarm, re-schedule it
|
|
schedule_alarm(wakeup_reason);
|
|
}
|
|
}
|
|
|
|
static void anim_handler_flash_x_start(Animation *animation, void *context) {
|
|
layer_set_hidden(bitmap_layer_get_layer(s_x_bmp_layer), false);
|
|
}
|
|
static void anim_handler_flash_x_end(Animation *animation, bool finished, void *context) {
|
|
layer_set_hidden(bitmap_layer_get_layer(s_x_bmp_layer), true);
|
|
animation_destroy(animation);
|
|
}
|
|
static void anim_update_flash_x(Animation *animation, const AnimationProgress progress) {} // dummy
|
|
static const AnimationImplementation s_x_anim_impl = {.update = anim_update_flash_x};
|
|
|
|
static void alarm_window_load() {
|
|
wakeup_service_subscribe(live_wakeup_handler_for_alarm);
|
|
|
|
Layer *window_layer = window_get_root_layer(s_window);
|
|
|
|
// change highlight color to avoid changing color of bottom bar
|
|
s_setter_highlight_color = s_primary_color;
|
|
|
|
// graph paper bg
|
|
s_graph_layer = layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT));
|
|
layer_set_update_proc(s_graph_layer, graph_layer_update_proc);
|
|
layer_add_child(window_layer, s_graph_layer);
|
|
|
|
// top&bottom bars
|
|
add_bar_layers_to_window(window_layer, true);
|
|
|
|
// theta text
|
|
s_font_mansalva_small = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_MANSALVA_REGULAR_22));
|
|
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
|
|
//// function
|
|
s_drop_bg_function_layer = layer_create(s_drop_function_grect);
|
|
layer_set_update_proc(s_drop_bg_function_layer, drop_layer_update_proc);
|
|
layer_add_child(window_layer, s_drop_bg_function_layer);
|
|
//// over
|
|
s_drop_bg_over_layer = layer_create(s_drop_over_grect);
|
|
layer_set_update_proc(s_drop_bg_over_layer, drop_layer_update_proc);
|
|
layer_add_child(window_layer, s_drop_bg_over_layer);
|
|
//// under
|
|
s_drop_bg_under_layer = layer_create(s_drop_under_grect);
|
|
layer_set_update_proc(s_drop_bg_under_layer, drop_layer_update_proc);
|
|
layer_add_child(window_layer, s_drop_bg_under_layer);
|
|
|
|
// drop text (blank by default)
|
|
//// function
|
|
s_drop_txt_function_layer = text_layer_create(GRect(s_drop_function_grect.origin.x, s_drop_function_grect.origin.y - 3, s_drop_function_grect.size.w, s_drop_function_grect.size.h));
|
|
text_layer_set_background_color(s_drop_txt_function_layer, GColorClear);
|
|
text_layer_set_font(s_drop_txt_function_layer, s_font_mansalva_small);
|
|
text_layer_set_text_alignment(s_drop_txt_function_layer, GTextAlignmentCenter);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_drop_txt_function_layer));
|
|
//// over
|
|
s_drop_txt_over_layer = text_layer_create(GRect(s_drop_over_grect.origin.x, s_drop_over_grect.origin.y - 3, s_drop_over_grect.size.w, s_drop_over_grect.size.h));
|
|
text_layer_set_background_color(s_drop_txt_over_layer, GColorClear);
|
|
text_layer_set_font(s_drop_txt_over_layer, s_font_mansalva_small);
|
|
text_layer_set_text_alignment(s_drop_txt_over_layer, GTextAlignmentCenter);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_drop_txt_over_layer));
|
|
//// under
|
|
s_drop_txt_under_layer = text_layer_create(GRect(s_drop_under_grect.origin.x, s_drop_under_grect.origin.y - 3, s_drop_under_grect.size.w, s_drop_under_grect.size.h));
|
|
text_layer_set_background_color(s_drop_txt_under_layer, GColorClear);
|
|
text_layer_set_font(s_drop_txt_under_layer, s_font_mansalva_small);
|
|
text_layer_set_text_alignment(s_drop_txt_under_layer, GTextAlignmentCenter);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_drop_txt_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_function_grect);
|
|
layer_set_update_proc(s_alarm_highlight_layer, alarm_highlight_layer_update_proc);
|
|
layer_add_child(window_layer, s_alarm_highlight_layer);
|
|
|
|
// triangle
|
|
static const uint8_t s_small_top_bar_height = s_top_bar_grect.size.h / 2;
|
|
s_triangle_layer = right_triangle_layer_create(GRect(0, s_small_top_bar_height, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - s_small_top_bar_height - s_bottom_bar_grect.size.h), GColorBlack);
|
|
layer_add_child(window_layer, s_triangle_layer->layer);
|
|
|
|
// triangle measurements text layers
|
|
uint16_t bc_y = (PBL_DISPLAY_HEIGHT / 2) - s_small_top_bar_height;
|
|
//// side_a
|
|
s_side_a_txt_layer = text_layer_create(GRect(0, s_bottom_bar_grect.origin.y - 26, PBL_DISPLAY_WIDTH, 26));
|
|
text_layer_set_background_color(s_side_a_txt_layer, GColorClear);
|
|
text_layer_set_font(s_side_a_txt_layer, s_font_mansalva_small);
|
|
text_layer_set_text_alignment(s_side_a_txt_layer, GTextAlignmentCenter);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_side_a_txt_layer));
|
|
//// side_b
|
|
s_side_b_txt_layer = text_layer_create(GRect(-4, bc_y, PBL_DISPLAY_WIDTH, 26));
|
|
text_layer_set_background_color(s_side_b_txt_layer, GColorClear);
|
|
text_layer_set_font(s_side_b_txt_layer, s_font_mansalva_small);
|
|
text_layer_set_text_alignment(s_side_b_txt_layer, GTextAlignmentRight);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_side_b_txt_layer));
|
|
//// side_c
|
|
s_side_c_txt_layer = text_layer_create(GRect(4, bc_y, PBL_DISPLAY_WIDTH, 26));
|
|
text_layer_set_background_color(s_side_c_txt_layer, GColorClear);
|
|
text_layer_set_font(s_side_c_txt_layer, s_font_mansalva_small);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_side_c_txt_layer));
|
|
|
|
// drop window
|
|
s_drop_window = window_create();
|
|
window_set_window_handlers(
|
|
s_drop_window,
|
|
(WindowHandlers){.load = drop_menu_window_load, .unload = drop_menu_window_unload});
|
|
|
|
// clock
|
|
static const uint16_t s_top_bar_edge_margin = (PBL_DISPLAY_WIDTH - 44) / 2; // 44 is the width of the countdown ((3*16)-4)
|
|
s_clock_txt_layer = text_layer_create(GRect(0, 4, s_top_bar_edge_margin, s_small_top_bar_height));
|
|
text_layer_set_background_color(s_clock_txt_layer, GColorClear);
|
|
text_layer_set_text_color(s_clock_txt_layer, GColorWhite);
|
|
text_layer_set_text_alignment(s_clock_txt_layer, GTextAlignmentCenter);
|
|
text_layer_set_font(s_clock_txt_layer, fonts_get_system_font(FONT_KEY_LECO_20_BOLD_NUMBERS));
|
|
layer_add_child(window_layer, text_layer_get_layer(s_clock_txt_layer));
|
|
|
|
// countdown
|
|
s_countdown_txt_layer = text_layer_create(GRect(0, (s_small_top_bar_height / 2) - PBL_IF_ROUND_ELSE(6, 10), PBL_DISPLAY_WIDTH, s_small_top_bar_height));
|
|
text_layer_set_background_color(s_countdown_txt_layer, GColorClear);
|
|
text_layer_set_text_color(s_countdown_txt_layer, GColorWhite);
|
|
text_layer_set_text_alignment(s_countdown_txt_layer, GTextAlignmentCenter);
|
|
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));
|
|
|
|
// progress
|
|
s_progress_txt_layer = text_layer_create(GRect(PBL_DISPLAY_WIDTH - s_top_bar_edge_margin, 4, s_top_bar_edge_margin, s_small_top_bar_height));
|
|
text_layer_set_background_color(s_progress_txt_layer, GColorClear);
|
|
text_layer_set_text_color(s_progress_txt_layer, GColorWhite);
|
|
text_layer_set_text_alignment(s_progress_txt_layer, GTextAlignmentCenter);
|
|
text_layer_set_font(s_progress_txt_layer, fonts_get_system_font(FONT_KEY_LECO_20_BOLD_NUMBERS));
|
|
layer_add_child(window_layer, text_layer_get_layer(s_progress_txt_layer));
|
|
|
|
// x (incorrect equation icon)
|
|
s_x_bmp = gbitmap_create_with_resource(RESOURCE_ID_X);
|
|
s_x_bmp_layer = bitmap_layer_create(GRect(20, 24, 160, 179));
|
|
bitmap_layer_set_bitmap(s_x_bmp_layer, s_x_bmp);
|
|
bitmap_layer_set_compositing_mode(s_x_bmp_layer, GCompOpSet);
|
|
layer_set_hidden(bitmap_layer_get_layer(s_x_bmp_layer), true); // hide until needed
|
|
layer_add_child(window_layer, bitmap_layer_get_layer(s_x_bmp_layer));
|
|
|
|
// x flash animation init
|
|
s_x_anim_master = animation_create();
|
|
animation_set_implementation(s_x_anim_master, &s_x_anim_impl);
|
|
animation_set_duration(s_x_anim_master, 1500);
|
|
animation_set_handlers(s_x_anim_master, (AnimationHandlers){.started = anim_handler_flash_x_start, .stopped = anim_handler_flash_x_end}, NULL);
|
|
|
|
// configure UI around current triangle in s_triangle_layer
|
|
s_current_triangle_num = 1;
|
|
s_secs_since_last_input = MAX_SECS_WITHOUT_INPUT;
|
|
s_continuous_zap_on_first_triangle = false;
|
|
s_zap_cooldown = 0;
|
|
update_for_new_triangle();
|
|
|
|
// schedule recovery alarm and begin countdown :)
|
|
schedule_alarm(WAKE_RECOVERY_ALARM);
|
|
accel_tap_service_subscribe(accel_tap_handler);
|
|
tick_timer_service_subscribe(SECOND_UNIT | MINUTE_UNIT, alarm_tick_handler);
|
|
}
|
|
|
|
static void alarm_window_unload() {
|
|
tick_timer_service_unsubscribe();
|
|
accel_tap_service_unsubscribe();
|
|
animation_destroy(s_x_anim_master);
|
|
gbitmap_destroy(s_x_bmp);
|
|
bitmap_layer_destroy(s_x_bmp_layer);
|
|
text_layer_destroy(s_progress_txt_layer);
|
|
text_layer_destroy(s_countdown_txt_layer);
|
|
window_destroy(s_drop_window);
|
|
text_layer_destroy(s_side_a_txt_layer);
|
|
text_layer_destroy(s_side_b_txt_layer);
|
|
text_layer_destroy(s_side_c_txt_layer);
|
|
right_triangle_layer_destroy(s_triangle_layer);
|
|
layer_destroy(s_alarm_highlight_layer);
|
|
layer_destroy(s_division_layer);
|
|
text_layer_destroy(s_parenthesis_txt_layer);
|
|
text_layer_destroy(s_drop_txt_under_layer);
|
|
text_layer_destroy(s_drop_txt_over_layer);
|
|
text_layer_destroy(s_drop_txt_function_layer);
|
|
layer_destroy(s_drop_bg_under_layer);
|
|
layer_destroy(s_drop_bg_over_layer);
|
|
layer_destroy(s_drop_bg_function_layer);
|
|
text_layer_destroy(s_theta_txt_layer);
|
|
layer_destroy(s_bottom_bar_layer);
|
|
layer_destroy(s_top_bar_layer);
|
|
layer_destroy(s_graph_layer);
|
|
fonts_unload_custom_font(s_font_mansalva_big);
|
|
fonts_unload_custom_font(s_font_mansalva_small);
|
|
}
|
|
|
|
static void push_alarm_window() {
|
|
if (s_alarm_reason == WAKE_RECOVERY_ALARM) {
|
|
send_zap();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
////////////////////// SETTER //////////////////////
|
|
|
|
static void setter_seconds_handler(struct tm *tick_time, TimeUnits units_changed) {
|
|
struct tm now_tm = *tick_time;
|
|
|
|
time_t now = mktime(&now_tm);
|
|
time_t target = compute_alarm_time(the_time.HourSet, the_time.MinuteSet);
|
|
|
|
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[12];
|
|
snprintf(s_countdown_buffer, sizeof(s_countdown_buffer), "%02u:%02u:%02u", 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(10, 14)];
|
|
snprintf(s_setter_buffer, sizeof(s_setter_buffer), PBL_IF_ROUND_ELSE("%02u : %02u", "%02u : %02u"), the_time.HourWIP, the_time.MinuteWIP);
|
|
text_layer_set_text(s_setter_txt_layer, s_setter_buffer);
|
|
}
|
|
|
|
static void setter_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_up_click_handler(void *recognizer, void *ctx) {
|
|
switch (s_highlight_pos) {
|
|
case 0:
|
|
if (the_time.HourWIP == 23) {
|
|
the_time.HourWIP = 0;
|
|
} else {
|
|
the_time.HourWIP++;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (the_time.MinuteWIP == 59) {
|
|
the_time.MinuteWIP = 0;
|
|
} else {
|
|
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 (the_time.HourWIP == 0) {
|
|
the_time.HourWIP = 23;
|
|
} else {
|
|
the_time.HourWIP--;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (the_time.MinuteWIP == 0) {
|
|
the_time.MinuteWIP = 59;
|
|
} else {
|
|
the_time.MinuteWIP--;
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
update_setter_display();
|
|
}
|
|
|
|
static void setter_select_click_handler(void *recognizer, void *ctx) {
|
|
if (s_highlight_pos == 2) {
|
|
bool success = schedule_alarm(WAKE_ALARM);
|
|
if (!success) {
|
|
return; // early return to avoid indicating alarm was set when it was not
|
|
}
|
|
|
|
// persist
|
|
persist_write_int(STORAGE_KEY_ALARM_HOUR, the_time.HourSet);
|
|
persist_write_int(STORAGE_KEY_ALARM_MINUTE, 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 live_wakeup_handler_for_setter(WakeupId id, int32_t reason) {
|
|
// reset values set in setter
|
|
s_highlight_pos = 0;
|
|
|
|
// reset values potentially set by prior alarm
|
|
s_skip_current_day = false;
|
|
s_selected_indices[0] = 9;
|
|
s_selected_indices[1] = 9;
|
|
s_selected_indices[2] = 9;
|
|
|
|
window_stack_pop_all(false);
|
|
push_alarm_window();
|
|
}
|
|
|
|
static void setter_window_load() {
|
|
wakeup_service_subscribe(live_wakeup_handler_for_setter);
|
|
|
|
s_setter_highlight_color = GColorMediumAquamarine;
|
|
Layer *window_layer = window_get_root_layer(s_window);
|
|
|
|
// top&bottom bars
|
|
add_bar_layers_to_window(window_layer, false);
|
|
|
|
// 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, 15), 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_countdown_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 43), (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();
|
|
|
|
// progress
|
|
|
|
// 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, setter_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() {
|
|
tick_timer_service_unsubscribe();
|
|
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);
|
|
}
|
|
|
|
static void push_setter_window() {
|
|
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 init() {
|
|
s_window = window_create();
|
|
s_font_lecoish_small = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_LECOISH_MONO_BOLD_20));
|
|
the_time.HourWIP = persist_read_int(STORAGE_KEY_ALARM_HOUR);
|
|
the_time.MinuteWIP = persist_read_int(STORAGE_KEY_ALARM_MINUTE);
|
|
the_time.HourSet = the_time.HourWIP;
|
|
the_time.MinuteSet = the_time.MinuteWIP;
|
|
|
|
// initialize pkjs
|
|
app_message_register_inbox_received(inbox_received_handler);
|
|
app_message_open(128, 64);
|
|
|
|
// load settings from persist (or set defaults if not present)
|
|
if (persist_exists(STORAGE_KEY_SETTINGS)) {
|
|
persist_read_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings));
|
|
} else {
|
|
settings.TutorialEnabled = true;
|
|
settings.PavlokEnabled = false;
|
|
settings.PavlokIntensity = 50;
|
|
settings.TriangleCount = 6;
|
|
settings.TriangleSecsMin = 18;
|
|
settings.TriangleSecsStep = 2;
|
|
}
|
|
|
|
// read even when opening setter to ensure a recovery alarm is not able to be overwritten
|
|
s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON);
|
|
|
|
switch (launch_reason()) {
|
|
case APP_LAUNCH_WAKEUP:
|
|
push_alarm_window();
|
|
break;
|
|
default:
|
|
if (s_alarm_reason == WAKE_RECOVERY_ALARM) {
|
|
push_alarm_window();
|
|
} else {
|
|
if (settings.TutorialEnabled) {
|
|
push_status_window(STATUS_TUTORIAL);
|
|
} else {
|
|
push_setter_window();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
static void deinit() {
|
|
fonts_unload_custom_font(s_font_lecoish_small);
|
|
window_destroy(s_window);
|
|
}
|
|
|
|
int main(void) {
|
|
init();
|
|
app_event_loop();
|
|
deinit();
|
|
}
|