Vastly less error-prone animation lifecycle

This commit is contained in:
RandyTheSilly committed 2026-11-11 21:03:53 -05:00
1 parent 9a836effda
commit 4b9d90efc8
1 file changed
+66 -43
+66 -43
View File
@@ -72,7 +72,7 @@ static uint8_t s_selected_indices[3] = {9, 9, 9};
static uint8_t s_secs_since_last_input; static uint8_t s_secs_since_last_input;
static uint8_t s_shake_cooldown; static uint8_t s_shake_cooldown;
static uint8_t s_zap_cooldown; static uint8_t s_zap_cooldown;
static uint8_t s_countdown_int = 0; static uint8_t s_countdown_int;
static bool s_continuous_zap_on_first_triangle; static bool s_continuous_zap_on_first_triangle;
static char s_countdown_buf[4]; static char s_countdown_buf[4];
static char s_progress_buf[8]; static char s_progress_buf[8];
@@ -107,8 +107,11 @@ static const VibePattern s_first_triangle_vibe_pattern = {
}; };
// animation stuff // animation stuff
#define ANIMATION_DURATION 350
static GRect s_left_door_starting_grect = GRect(-(PBL_DISPLAY_WIDTH / 2), s_small_top_bar_height, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_HEIGHT - s_small_top_bar_height - s_bottom_bar_grect.size.h); static GRect s_left_door_starting_grect = GRect(-(PBL_DISPLAY_WIDTH / 2), s_small_top_bar_height, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_HEIGHT - s_small_top_bar_height - s_bottom_bar_grect.size.h);
static GRect s_right_door_starting_grect = GRect(PBL_DISPLAY_WIDTH, s_small_top_bar_height, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_HEIGHT - s_small_top_bar_height - s_bottom_bar_grect.size.h); static GRect s_right_door_starting_grect = GRect(PBL_DISPLAY_WIDTH, s_small_top_bar_height, PBL_DISPLAY_WIDTH / 2, PBL_DISPLAY_HEIGHT - s_small_top_bar_height - s_bottom_bar_grect.size.h);
static bool s_door_correct;
static Animation *s_door_anim;
// alarm drop menu options // alarm drop menu options
static const char *s_options_function[3]; static const char *s_options_function[3];
@@ -515,54 +518,69 @@ static void door_layer_green_update_proc(Layer *layer, GContext *ctx) {
graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone);
} }
static void door_closed_fail_animation_handler(Animation *animation, bool finished, void *context) { static void door_closed_animation_stopped_handler(Animation *a, bool finished, void *ctx) {
if (s_current_triangle_num > 1) { if (!finished)
return;
if (s_door_correct)
s_current_triangle_num++;
else if (s_current_triangle_num > 1)
s_current_triangle_num--; s_current_triangle_num--;
}
right_triangle_layer_regen(s_triangle_layer); right_triangle_layer_regen(s_triangle_layer);
update_for_new_triangle(); update_for_new_triangle();
} }
static void door_closed_success_animation_handler(Animation *animation, bool finished, void *context) { static void door_animation_stopped_handler(Animation *a, bool finished, void *ctx) {
s_current_triangle_num++; animation_destroy(a);
right_triangle_layer_regen(s_triangle_layer); s_door_anim = NULL;
update_for_new_triangle();
} }
static void destroy_animation_handler(Animation *animation, bool finished, void *context) {
animation_destroy(animation);
}
// TODO:
// reuse some of these animations, if possible
// add iconography to door halves
static void play_door_animation(bool correct) { static void play_door_animation(bool correct) {
PropertyAnimation *left_door_prop_anim = property_animation_create_layer_frame(s_left_door_layer, &s_left_door_starting_grect, &GRect(0, s_left_door_starting_grect.origin.y, s_left_door_starting_grect.size.w, s_left_door_starting_grect.size.h)); // prevent from playing if an instance is already running and not yet finished;
PropertyAnimation *right_door_prop_anim = property_animation_create_layer_frame(s_right_door_layer, &s_right_door_starting_grect, &GRect(PBL_DISPLAY_WIDTH / 2, s_left_door_starting_grect.origin.y, s_left_door_starting_grect.size.w, s_left_door_starting_grect.size.h)); // prevents instances where the user is punished if the timer expires AFTER the submission
Animation *left_door_close_anim = property_animation_get_animation(left_door_prop_anim); if (s_door_anim) {
Animation *right_door_close_anim = property_animation_get_animation(right_door_prop_anim); return;
animation_set_duration(left_door_close_anim, 500);
animation_set_duration(right_door_close_anim, 500);
Animation *left_door_open_anim = animation_clone(left_door_close_anim);
Animation *right_door_open_anim = animation_clone(right_door_close_anim);
animation_set_reverse(left_door_open_anim, true);
animation_set_reverse(right_door_open_anim, true);
Animation *door_close_spawn_anim = animation_spawn_create(left_door_close_anim, right_door_close_anim, NULL);
Animation *door_open_spawn_anim = animation_spawn_create(left_door_open_anim, right_door_open_anim, NULL);
animation_set_delay(door_open_spawn_anim, 100);
if (correct) {
layer_set_update_proc(s_left_door_layer, door_layer_green_update_proc);
layer_set_update_proc(s_right_door_layer, door_layer_green_update_proc);
animation_set_handlers(door_close_spawn_anim, (AnimationHandlers){.stopped = door_closed_success_animation_handler}, NULL);
} else {
layer_set_update_proc(s_left_door_layer, door_layer_red_update_proc);
layer_set_update_proc(s_right_door_layer, door_layer_red_update_proc);
animation_set_handlers(door_close_spawn_anim, (AnimationHandlers){.stopped = door_closed_fail_animation_handler}, NULL);
} }
Animation *seq = animation_sequence_create(door_close_spawn_anim, door_open_spawn_anim, NULL);
animation_set_handlers(seq, (AnimationHandlers){.stopped = destroy_animation_handler}, NULL); // reset shake cooldown here so it is guaranteed
animation_schedule(seq); // to be in effect while an animation is playing
}; s_shake_cooldown = SHAKE_COOLDOWN_SECS;
// ensure door layers are correct color
s_door_correct = correct;
layer_set_update_proc(s_left_door_layer, correct ? door_layer_green_update_proc : door_layer_red_update_proc);
layer_set_update_proc(s_right_door_layer, correct ? door_layer_green_update_proc : door_layer_red_update_proc);
// create base close animations
PropertyAnimation *l = property_animation_create_layer_frame(s_left_door_layer, &s_left_door_starting_grect, &GRect(0, s_left_door_starting_grect.origin.y, s_left_door_starting_grect.size.w, s_left_door_starting_grect.size.h));
PropertyAnimation *r = property_animation_create_layer_frame(s_right_door_layer, &s_right_door_starting_grect, &GRect(PBL_DISPLAY_WIDTH / 2, s_left_door_starting_grect.origin.y, s_left_door_starting_grect.size.w, s_left_door_starting_grect.size.h));
Animation *lc = property_animation_get_animation(l), *rc = property_animation_get_animation(r);
animation_set_duration(lc, ANIMATION_DURATION);
animation_set_duration(rc, ANIMATION_DURATION);
// duplicate and reverse for open animations
Animation *lo = animation_clone(lc), *ro = animation_clone(rc);
animation_set_reverse(lo, true);
animation_set_reverse(ro, true);
// create spawn animations
Animation *close = animation_spawn_create(lc, rc, NULL);
Animation *open = animation_spawn_create(lo, ro, NULL);
animation_set_delay(open, 100);
// set door close handler
animation_set_handlers(close, (AnimationHandlers){.stopped = door_closed_animation_stopped_handler}, NULL);
// create sequence
s_door_anim = animation_sequence_create(close, open, NULL);
// set stopped handler;
// even though the SDK auto-destroys after finished, we destroy manually
// since we already need a stopped handler to NULL it for the check in alarm_window_unload()
animation_set_handlers(s_door_anim, (AnimationHandlers){.stopped = door_animation_stopped_handler}, NULL);
// play animation sequence;
animation_schedule(s_door_anim);
}
static void increment_secs_since_last_input() { static void increment_secs_since_last_input() {
if (s_secs_since_last_input < MAX_SECS_WITHOUT_INPUT) { if (s_secs_since_last_input < MAX_SECS_WITHOUT_INPUT) {
@@ -674,7 +692,8 @@ static void accel_tap_handler(AccelAxisType axis, int32_t direction) {
if (s_countdown_int > 0) { if (s_countdown_int > 0) {
if (s_current_triangle_num == settings.TriangleCount) { if (s_current_triangle_num == settings.TriangleCount) {
tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window tick_timer_service_unsubscribe(); // prevent stimulus in confirmation window
accel_tap_service_unsubscribe(); // don't listen for shakes in confirmation window
wakeup_service_subscribe(NULL); // ignore wakeup events to avoid overwriting new alarm wakeup_service_subscribe(NULL); // ignore wakeup events to avoid overwriting new alarm
if (schedule_alarm(WAKE_ALARM)) { if (schedule_alarm(WAKE_ALARM)) {
push_status_window(STATUS_CONFIRMATION); push_status_window(STATUS_CONFIRMATION);
@@ -683,8 +702,6 @@ static void accel_tap_handler(AccelAxisType axis, int32_t direction) {
play_door_animation(true); play_door_animation(true);
} }
} }
s_shake_cooldown = SHAKE_COOLDOWN_SECS;
} }
static void alarm_click_config_provider(void *ctx) { static void alarm_click_config_provider(void *ctx) {
@@ -913,6 +930,12 @@ static void alarm_window_load() {
static void alarm_window_unload() { static void alarm_window_unload() {
tick_timer_service_unsubscribe(); tick_timer_service_unsubscribe();
accel_tap_service_unsubscribe(); accel_tap_service_unsubscribe();
if (s_door_anim) {
// unschedule and destroy if unload occurs while an animation is running;
// e.g. if the alarm reset button is used from the phone-side
animation_destroy(s_door_anim);
s_door_anim = NULL;
}
layer_destroy(s_right_door_layer); layer_destroy(s_right_door_layer);
layer_destroy(s_left_door_layer); layer_destroy(s_left_door_layer);
text_layer_destroy(s_progress_txt_layer); text_layer_destroy(s_progress_txt_layer);