pebble/tests/fakes/fake_animation.c
2025-01-27 11:38:16 -08:00

274 lines
8.7 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Need to use the real struct because the product code accesses the structure directly. It would
// be nice to instead create a dummy with only the fields we need, but oh well.
#include "applib/ui/animation_private.h"
#include <stdarg.h>
//! List of all animations that were created in the current test in order of creation.
ListNode *s_animations;
// Fake implementations of the real animation interface.
/////////////////////////////////////////////////////////////
Animation *animation_create(void) {
AnimationPrivate *animation = malloc(sizeof(AnimationPrivate));
*animation = (AnimationPrivate) {};
if (!s_animations) {
s_animations = (ListNode *)animation;
} else {
list_append(s_animations, (ListNode *)animation);
}
return (Animation *)animation;
}
static Animation *prv_create_from_array(Animation **animation_array, size_t array_len) {
AnimationPrivate *parent = (AnimationPrivate *)animation_create();
parent->first_child = (AnimationPrivate *)animation_array[0];
for (int i = 0; i < (int)array_len; i++) {
AnimationPrivate *child = (AnimationPrivate *)animation_array[i];
child->parent = parent;
if (i + 1 < (int)array_len) {
child->sibling = (AnimationPrivate *)animation_array[i + 1];
}
}
return (Animation *)parent;
}
static Animation *prv_create_from_vararg(Animation *animation_a, Animation *animation_b,
Animation *animation_c, va_list args) {
Animation *animation_array[ANIMATION_MAX_CREATE_VARGS];
size_t array_len = 0;
animation_array[array_len++] = animation_a;
animation_array[array_len++] = animation_b;
if (animation_c) {
animation_array[array_len++] = animation_c;
while (array_len < ANIMATION_MAX_CREATE_VARGS) {
void *arg = va_arg(args, void *);
if (arg == NULL) {
break;
}
animation_array[array_len++] = arg;
}
}
return prv_create_from_array(animation_array, array_len);
}
Animation *WEAK animation_sequence_create(Animation *animation_a, Animation *animation_b,
Animation *animation_c, ...) {
va_list args;
va_start(args, animation_c);
Animation *animation = prv_create_from_vararg(animation_a, animation_b, animation_c, args);
va_end(args);
return animation;
}
Animation *WEAK animation_sequence_create_from_array(Animation **animation_array,
uint32_t array_len) {
return prv_create_from_array(animation_array, array_len);
}
Animation *WEAK animation_spawn_create(Animation *animation_a, Animation *animation_b,
Animation *animation_c, ...) {
va_list args;
va_start(args, animation_c);
Animation *animation = prv_create_from_vararg(animation_a, animation_b, animation_c, args);
va_end(args);
return animation;
}
Animation *WEAK animation_spawn_create_from_array(Animation **animation_array,
uint32_t array_len) {
return prv_create_from_array(animation_array, array_len);
}
typedef void (*AnimationEachCallback)(AnimationPrivate *animation, uintptr_t context);
static void prv_each(AnimationPrivate *animation, AnimationEachCallback callback,
uintptr_t context) {
if (animation->first_child) {
prv_each(animation->first_child, callback, context);
}
if (animation->sibling) {
prv_each(animation->sibling, callback, context);
}
callback(animation, context);
}
static void prv_free(AnimationPrivate *animation, uintptr_t context) {
list_remove(&animation->list_node, NULL, NULL);
free(animation);
}
bool animation_destroy(Animation *animation) {
prv_each((AnimationPrivate *)animation, prv_free, (uintptr_t)NULL);
return true;
}
bool animation_set_implementation(Animation *animation_h,
const AnimationImplementation *implementation) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
animation->implementation = implementation;
return true;
}
bool animation_is_scheduled(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
return animation->scheduled;
}
uint32_t animation_get_duration(Animation *animation, bool include_delay, bool include_play_count) {
if (!animation) {
return 0;
}
return ((AnimationPrivate *)animation)->duration_ms;
}
static void prv_call_started(AnimationPrivate *animation, uintptr_t UNUSED context) {
if (animation->implementation && animation->implementation->setup) {
animation->implementation->setup((Animation *)animation);
}
if (animation->handlers.started) {
animation->handlers.started((Animation *)animation, animation->context);
}
}
static void prv_call_scheduled(AnimationPrivate *animation, uintptr_t scheduled) {
animation->scheduled = scheduled;
}
bool animation_schedule(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (!animation->scheduled) {
prv_each(animation, prv_call_scheduled, true);
// If your test is failing, build out this fake so that this is an async start
prv_each(animation, prv_call_started, (uintptr_t)NULL);
}
return true;
}
bool animation_set_elapsed(Animation *animation_h, uint32_t elapsed_ms) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (animation->duration_ms <= elapsed_ms) {
animation->is_completed = true;
animation_unschedule(animation_h);
}
return true;
}
bool animation_get_elapsed(Animation *animation_h, int32_t *elapsed_ms) {
AnimationPrivate *animation= (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (elapsed_ms) {
*elapsed_ms = animation->is_completed ? animation->duration_ms : 0;
}
return true;
}
bool animation_set_handlers(Animation *animation_h, AnimationHandlers callbacks, void *context) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
animation->handlers = callbacks;
animation->context = context;
return true;
}
void *animation_get_context(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
return animation->context;
}
static void prv_call_update(AnimationPrivate *animation, uintptr_t progress) {
if (animation->implementation && animation->implementation->update) {
animation->implementation->update((Animation *)animation, progress);
}
}
static void prv_call_stopped(AnimationPrivate *animation, uintptr_t finished) {
if (animation->handlers.stopped) {
animation->handlers.stopped((Animation *)animation, finished, animation->context);
}
if (animation->implementation && animation->implementation->teardown) {
animation->implementation->teardown((Animation *)animation);
}
}
bool animation_unschedule(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (animation->scheduled) {
prv_each(animation, prv_call_scheduled, false);
prv_each(animation, prv_call_update, ANIMATION_NORMALIZED_MAX);
prv_each(animation, prv_call_stopped, animation->is_completed);
}
return true;
}
// Interface for unit tests to query the fake animation state
/////////////////////////////////////////////////////////////
Animation *fake_animation_get_first_animation(void) {
return (Animation *)s_animations;
}
Animation *fake_animation_get_next_animation(Animation *animation) {
return (Animation *)((AnimationPrivate *)animation)->list_node.next;
}
void fake_animation_cleanup(void) {
ListNode *iter = s_animations;
while (iter) {
ListNode *current = iter;
iter = iter->next;
free(current);
}
s_animations = NULL;
}
void fake_animation_complete(Animation *animation) {
animation_schedule(animation);
const uint32_t duration =
animation_get_duration(animation, false /* delay */, true /* play_count */);
animation_set_elapsed(animation, duration);
animation_unschedule(animation);
}