mirror of
https://github.com/google/pebble.git
synced 2025-11-20 14:30:55 -05:00
1933 lines
76 KiB
C
1933 lines
76 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.
|
|
*/
|
|
|
|
#include "animation_private.h"
|
|
|
|
#include "animation_timing.h"
|
|
#include "property_animation_private.h"
|
|
|
|
#include "applib/legacy2/ui/animation_legacy2.h"
|
|
#include "applib/legacy2/ui/animation_private_legacy2.h"
|
|
|
|
#include "applib/app_logging.h"
|
|
#include "applib/applib_malloc.auto.h"
|
|
|
|
#include "process_state/app_state/app_state.h"
|
|
|
|
#include "kernel/kernel_applib_state.h"
|
|
#include "kernel/memory_layout.h"
|
|
|
|
#include "services/common/animation_service.h"
|
|
|
|
#include "system/passert.h"
|
|
#include "util/math.h"
|
|
|
|
#include <string.h>
|
|
|
|
KERNEL_READONLY_DATA static bool s_paused = false;
|
|
|
|
static void prv_run(AnimationState *state, uint32_t now, AnimationPrivate *top_level_animation,
|
|
uint32_t top_level_start_time, bool do_update);
|
|
#define PebbleTask_Current PebbleTask_Unknown
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static AnimationState *prv_animation_state_get(PebbleTask task) {
|
|
if (task == PebbleTask_Current) {
|
|
task = pebble_task_get_current();
|
|
}
|
|
if (task == PebbleTask_App) {
|
|
return app_state_get_animation_state();
|
|
} else if (task == PebbleTask_KernelMain) {
|
|
return kernel_applib_get_animation_state();
|
|
} else {
|
|
WTF;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
T_STATIC AnimationPrivate *prv_animation_get_current(void) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Unknown);
|
|
return state->aux->current_animation;
|
|
}
|
|
|
|
InterpolateInt64Function animation_private_current_interpolate_override(void) {
|
|
AnimationPrivate *animation = prv_animation_get_current();
|
|
if (animation && animation->curve == AnimationCurveCustomInterpolationFunction) {
|
|
return animation->custom_interpolation_function;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
static bool prv_handle_list_filter(ListNode* node, void* data) {
|
|
AnimationPrivate* animation = (AnimationPrivate *)node;
|
|
return animation->handle == data;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// Find annotation by handle. If quiet is true, don't print out a log error message if we detect
|
|
// an invalid handle. Quiet mode is used by animation_unschedule and animation_is_scheduled.
|
|
static AnimationPrivate* prv_find_animation_by_handle(AnimationState *state, Animation *handle,
|
|
bool quiet) {
|
|
if (!handle) {
|
|
return NULL;
|
|
}
|
|
|
|
// Default to state for the current task
|
|
if (!state) {
|
|
state = prv_animation_state_get(PebbleTask_Current);
|
|
}
|
|
|
|
// Look for this animation by id. It could either be in the unscheduled or scheduled list
|
|
ListNode* node = list_find(state->unscheduled_head, prv_handle_list_filter, (void*)handle);
|
|
if (!node) {
|
|
node = list_find(state->scheduled_head, prv_handle_list_filter, (void*)handle);
|
|
}
|
|
if (!node) {
|
|
if (!quiet) {
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Animation %d does not exist", (int)handle);
|
|
}
|
|
return NULL;
|
|
}
|
|
return (AnimationPrivate *)node;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Find animation by parent and child idx
|
|
typedef struct {
|
|
AnimationPrivate *parent;
|
|
uint8_t child_idx;
|
|
} ParentChildInfo;
|
|
|
|
static bool prv_parent_list_filter(ListNode* node, void* data) {
|
|
AnimationPrivate* animation = (AnimationPrivate *)node;
|
|
ParentChildInfo *info = (ParentChildInfo *)data;
|
|
return animation->parent == info->parent && animation->child_idx == info->child_idx;
|
|
}
|
|
|
|
static AnimationPrivate* prv_find_animation_by_parent_child_idx(AnimationState *state,
|
|
AnimationPrivate *parent, int child_idx) {
|
|
if (!parent) {
|
|
return NULL;
|
|
}
|
|
|
|
// Default to state for the current task
|
|
if (!state) {
|
|
state = prv_animation_state_get(PebbleTask_Current);
|
|
}
|
|
|
|
// Look for this animation by id. It could either be in the unscheduled or scheduled list
|
|
ParentChildInfo info = (ParentChildInfo) {
|
|
.parent = parent,
|
|
.child_idx = child_idx
|
|
};
|
|
ListNode* node = list_find(state->unscheduled_head, prv_parent_list_filter, &info);
|
|
if (!node) {
|
|
node = list_find(state->scheduled_head, prv_parent_list_filter, &info);
|
|
}
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return (AnimationPrivate *)node;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Remove from being iterated after unscheduling. This must be called on any animation being
|
|
// unscheduled.
|
|
static void prv_iter_remove(AnimationState *state, AnimationPrivate *animation) {
|
|
// If this animation is the iterator's next, bump the iterator
|
|
if (state->aux->iter_next == (ListNode *)animation) {
|
|
state->aux->iter_next = list_get_next((ListNode *)animation);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Remove from our list of allocated animations and free the memory
|
|
static void prv_unlink_and_free(AnimationState *state, AnimationPrivate *animation) {
|
|
// It's an error if it's scheduled
|
|
PBL_ASSERTN(list_contains(state->unscheduled_head, &animation->list_node));
|
|
list_remove(&animation->list_node, &state->unscheduled_head /* &head */, NULL /* &tail */);
|
|
|
|
ANIMATION_LOG_DEBUG("destroying %d (%p) ", (int)animation->handle, animation);
|
|
applib_free(animation);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static int prv_scheduler_comparator(void *a, void *b) {
|
|
AnimationPrivate *animation_a = (AnimationPrivate *)a;
|
|
AnimationPrivate *animation_b = (AnimationPrivate *)b;
|
|
return serial_distance32(animation_a->abs_start_time_ms, animation_b->abs_start_time_ms);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
inline static uint32_t prv_get_ms_since_system_start(void) {
|
|
return ((sys_get_ticks() * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Get the total duration of an animation, optionally considering the delay and play count.
|
|
// This recurses into children of sequence or spawn animations
|
|
static uint32_t prv_get_total_duration(AnimationState *state, AnimationPrivate *animation,
|
|
bool include_delay, bool include_play_count) {
|
|
uint32_t child_duration;
|
|
uint32_t duration = 0;
|
|
int child_idx;
|
|
|
|
if (include_delay) {
|
|
duration += animation->delay_ms;
|
|
}
|
|
|
|
if (animation->type == AnimationTypeSequence) {
|
|
// For a sequence animation, add duration of each of the components
|
|
for (child_idx = 0; child_idx < ANIMATION_MAX_CHILDREN; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
child_duration = prv_get_total_duration(state, child, true, true);
|
|
if (child_duration == PLAY_DURATION_INFINITE) {
|
|
return PLAY_DURATION_INFINITE;
|
|
}
|
|
duration += child_duration;
|
|
}
|
|
|
|
|
|
} else if (animation->type == AnimationTypeSpawn) {
|
|
// For a spawn animation, get the max of each component
|
|
uint32_t max_child_duration = 0;
|
|
for (child_idx = 0; child_idx < ANIMATION_MAX_CHILDREN; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
child_duration = prv_get_total_duration(state, child, true, true);
|
|
if (child_duration == PLAY_DURATION_INFINITE) {
|
|
return PLAY_DURATION_INFINITE;
|
|
}
|
|
max_child_duration = MAX(max_child_duration, child_duration);
|
|
}
|
|
duration += max_child_duration;
|
|
|
|
} else {
|
|
PBL_ASSERTN(animation->type == AnimationTypePrimitive);
|
|
duration += animation->duration_ms;
|
|
}
|
|
|
|
if (include_play_count) {
|
|
// Factor in the play count of this animation now
|
|
if (animation->play_count == ANIMATION_PLAY_COUNT_INFINITE_STORED) {
|
|
duration = PLAY_DURATION_INFINITE;
|
|
} else {
|
|
duration *= animation->play_count;
|
|
}
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Return true if animation is a descendent of the given parent
|
|
static bool prv_is_descendent_of(AnimationState *state, AnimationPrivate *animation,
|
|
AnimationPrivate *parent) {
|
|
// Follow the parents up
|
|
while (animation) {
|
|
// If no parent at all, can't be
|
|
if (!animation->parent) {
|
|
return false;
|
|
}
|
|
|
|
if (animation->parent == parent) {
|
|
// Direct descendent
|
|
return true;
|
|
}
|
|
|
|
// Get parent's parent
|
|
animation = animation->parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static int32_t prv_get_elapsed(AnimationPrivate *animation, uint32_t now) {
|
|
// Compute the absolute start time of this animation, backing it up by the
|
|
// delay and any repeats we have already done
|
|
uint32_t start_ms = animation->abs_start_time_ms;
|
|
start_ms -= animation->times_played * (animation->duration_ms + animation->delay_ms);
|
|
return serial_distance32(start_ms, now);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Adjust the abs_start_time of this animation and all of its children. This is called during
|
|
// a set_elapsed operation.
|
|
static void prv_backup_start_time(AnimationState *state, AnimationPrivate *parent,
|
|
const uint32_t delta) {
|
|
AnimationPrivate *next;
|
|
if (delta == 0) {
|
|
return;
|
|
}
|
|
|
|
AnimationPrivate *animation = (AnimationPrivate *) state->scheduled_head;
|
|
while (animation) {
|
|
// Since we are reducing the start times, each of the animations we operate on will be
|
|
// moved earlier in the list. Get the next pointer now before we possibly move it.
|
|
next = (AnimationPrivate*) list_get_next(&animation->list_node);
|
|
|
|
// Note that we have to iterate through all scheduled nodes and see if each is a descendent.
|
|
// We can't follow the children of parent_h by searching using an incrementing child_idx
|
|
// because one or more of the children may have already run and destroyed themselves.
|
|
if (animation == parent || prv_is_descendent_of(state, animation, parent)) {
|
|
animation->abs_start_time_ms -= delta;
|
|
|
|
// Put back into sorted order
|
|
list_remove(&animation->list_node, &state->scheduled_head, NULL /* &tail */);
|
|
state->scheduled_head = list_sorted_add(state->scheduled_head, &animation->list_node,
|
|
prv_scheduler_comparator, true /*ascending*/);
|
|
}
|
|
animation = next;
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static void prv_reschedule_timer(AnimationState *state, uint32_t rate_control_delay_ms) {
|
|
AnimationPrivate *animation = (AnimationPrivate *)state->scheduled_head;
|
|
if (animation == NULL) {
|
|
return;
|
|
}
|
|
const uint32_t now = prv_get_ms_since_system_start();
|
|
const int32_t delta_ms = serial_distance32(now, animation->abs_start_time_ms);
|
|
const uint32_t interval_ms = MAX(delta_ms, 0) + rate_control_delay_ms;
|
|
|
|
// The animation service will call animation_private_timer_callback() when the timer fires
|
|
animation_service_timer_schedule(interval_ms);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool prv_animation_is_scheduled(AnimationState* state, AnimationPrivate *animation) {
|
|
return (animation->abs_start_time_ms != 0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Return true if animation is mutable
|
|
static bool prv_is_mutable(AnimationState *state, AnimationPrivate *animation) {
|
|
return (animation && !animation->immutable && !animation->parent &&
|
|
!prv_animation_is_scheduled(state, animation));
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Determine if any of an animation's descendents are scheduled
|
|
static bool prv_animation_children_scheduled(AnimationState *state, AnimationPrivate *animation) {
|
|
if (animation->type != AnimationTypePrimitive) {
|
|
// For a complex animation, check each component
|
|
for (int child_idx = 0; child_idx < ANIMATION_MAX_CHILDREN; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
if (child->type == AnimationTypePrimitive) {
|
|
if (prv_animation_is_scheduled(state, child)) {
|
|
return true;
|
|
}
|
|
} else if (prv_animation_is_scheduled(state, child)
|
|
|| prv_animation_children_scheduled(state, child)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Unschedule of an animation and optional destroy, recurses into children of sequence or spawn
|
|
// animations. When this method is called on children of an animation, allow_auto_destroy is
|
|
// false unless the top-level animation has already been unscheduled.
|
|
void prv_unschedule_animation(AnimationState *state, AnimationPrivate *animation,
|
|
const bool finished, bool allow_auto_destroy, bool force_destroy, bool teardown) {
|
|
if (animation->type != AnimationTypePrimitive) {
|
|
// For a complex animation, unschedule each of the components
|
|
for (int child_idx = 0; child_idx < ANIMATION_MAX_CHILDREN; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
prv_unschedule_animation(state, child, finished, allow_auto_destroy, force_destroy, teardown);
|
|
}
|
|
}
|
|
|
|
if (!prv_animation_is_scheduled(state, animation)) {
|
|
// When we unschedule a top-level animation, we call prv_unschedule_animation() on each of
|
|
// the children, which gives us a chance to destroy them. Children are not allowed to destroy
|
|
// themselves.
|
|
if (animation->calling_end_handlers) {
|
|
// We don't want to tear down an animation that is executing stopped handlers.
|
|
animation->defer_delete = true;
|
|
} else {
|
|
if (teardown && animation->did_setup) {
|
|
// When children unschedule themselves after running, their teardown isn't allowed to run
|
|
// (because the parent might repeat). So, this is a chance to finally run the child's
|
|
// teardown handler.
|
|
if (animation->implementation->teardown != NULL) {
|
|
animation->implementation->teardown(animation->handle);
|
|
}
|
|
animation->did_setup = false;
|
|
}
|
|
if (force_destroy || (allow_auto_destroy && animation->auto_destroy)) {
|
|
prv_unlink_and_free(state, animation);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Unschedule the passed in animation
|
|
ANIMATION_LOG_DEBUG("unscheduling %d (%p)", (int)animation->handle, animation);
|
|
PBL_ASSERTN(animation->implementation != NULL);
|
|
|
|
const bool was_old_head = (&animation->list_node == state->scheduled_head);
|
|
|
|
// Remove from being iterated
|
|
prv_iter_remove(state, animation);
|
|
|
|
// Move from the scheduled to the unscheduled list
|
|
PBL_ASSERTN(list_contains(state->scheduled_head, &animation->list_node));
|
|
list_remove(&animation->list_node, &state->scheduled_head, NULL);
|
|
state->unscheduled_head = list_insert_before(state->unscheduled_head, &animation->list_node);
|
|
|
|
// Reschedule the timer if we're removing the head animation:
|
|
if (was_old_head && state->scheduled_head != NULL) {
|
|
prv_reschedule_timer(state, 0);
|
|
}
|
|
|
|
// Reset these fields, before calling .stopped(), so that this animation
|
|
// instance can be rescheduled again in the .stopped() handler, if needed.
|
|
animation->abs_start_time_ms = 0;
|
|
animation->is_completed = false;
|
|
animation->times_played = 0;
|
|
bool did_start = animation->started;
|
|
animation->started = false;
|
|
if (force_destroy) {
|
|
// Setting this flag prevents the stopped handler from being able to reschedule it again
|
|
animation->being_destroyed = true;
|
|
}
|
|
|
|
// Call the stopped and teardown handlers
|
|
animation->calling_end_handlers = true;
|
|
if (animation->handlers.stopped && did_start) {
|
|
animation->handlers.stopped(animation->handle, finished, animation->context);
|
|
}
|
|
if (teardown && animation->did_setup) {
|
|
if (animation->implementation->teardown != NULL) {
|
|
animation->implementation->teardown(animation->handle);
|
|
}
|
|
animation->did_setup = false;
|
|
}
|
|
animation->calling_end_handlers = false;
|
|
|
|
#ifdef UNITTEST
|
|
// Make sure this animation didn't get deleted as a side effect of running the stopped handler
|
|
PBL_ASSERTN(list_contains(state->unscheduled_head, &animation->list_node)
|
|
|| list_contains(state->scheduled_head, &animation->list_node));
|
|
#endif
|
|
|
|
if (force_destroy || animation->defer_delete
|
|
|| ((allow_auto_destroy && animation->auto_destroy)
|
|
&& !prv_animation_is_scheduled(state, animation))) {
|
|
// It's possible the stopped handler rescheduled, so check before we destroy it
|
|
prv_unlink_and_free(state, animation);
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Low level schedule of an animation, no recursion
|
|
static void prv_schedule_low_level_animation(AnimationState* state, const uint32_t now,
|
|
AnimationPrivate *animation, int32_t add_delay_ms) {
|
|
animation->abs_start_time_ms = now + animation->delay_ms + add_delay_ms;
|
|
if (animation->abs_start_time_ms == 0) {
|
|
// 0 means not scheduled
|
|
animation->abs_start_time_ms = 1;
|
|
}
|
|
if (!animation->did_setup) {
|
|
if (animation->implementation->setup != NULL) {
|
|
animation->implementation->setup(animation->handle);
|
|
}
|
|
animation->did_setup = true;
|
|
}
|
|
|
|
const bool old_head_is_animating = state->scheduled_head
|
|
? (((AnimationPrivate *)state->scheduled_head)->abs_start_time_ms <= now)
|
|
: false;
|
|
|
|
// Move from the unscheduled to the scheduled list
|
|
PBL_ASSERTN(list_contains(state->unscheduled_head, &animation->list_node));
|
|
list_remove(&animation->list_node, &state->unscheduled_head /* &head */, NULL /* &tail */);
|
|
const bool ascending = true;
|
|
state->scheduled_head = list_sorted_add(state->scheduled_head, &animation->list_node,
|
|
prv_scheduler_comparator, ascending);
|
|
|
|
const bool has_new_head = (&animation->list_node == state->scheduled_head);
|
|
if (has_new_head) {
|
|
// Only reschedule the timer if the previous head animation wasn't running yet:
|
|
if (old_head_is_animating == false) {
|
|
prv_reschedule_timer(state, 0);
|
|
}
|
|
}
|
|
|
|
ANIMATION_LOG_DEBUG("scheduled %d (%p) to run at (%d). delay:%d, duration:%d",
|
|
(int)animation->handle, animation, (int)animation->abs_start_time_ms,
|
|
(int)(animation->delay_ms), (int)(animation->duration_ms));
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// High level schedule of an animation, recurses into children of sequence or spawn animations
|
|
static bool prv_schedule_animation(AnimationState* state, const uint32_t now,
|
|
AnimationPrivate *animation, int32_t add_delay_ms) {
|
|
bool success = true;
|
|
int child_idx;
|
|
PBL_ASSERTN(animation != NULL);
|
|
|
|
if (animation->play_count == 0) {
|
|
// Play count of 0, no need to schedule it.
|
|
return true;
|
|
}
|
|
|
|
// Don't allow an animation to be rescheduled (like from the stopped handler) if it is
|
|
// being destroyed
|
|
if (animation->being_destroyed) {
|
|
return false;
|
|
}
|
|
|
|
ANIMATION_LOG_DEBUG("scheduling %d (%p) to run in %d ms (%d)", (int)animation->handle, animation,
|
|
(int)(animation->delay_ms + add_delay_ms), (int)(now + animation->delay_ms + add_delay_ms));
|
|
|
|
uint32_t earliest_start_time = now;
|
|
|
|
if (animation->type == AnimationTypeSequence) {
|
|
// For a sequence animation, schedule each of the components with increasing delays
|
|
int32_t delay = animation->delay_ms + add_delay_ms;
|
|
|
|
// Figure out and store our total duration (used by the scheduler to tell when it's done)
|
|
animation->duration_ms = prv_get_total_duration(state, animation, false /*delay*/,
|
|
false /*play_count*/);
|
|
|
|
for (child_idx = 0; child_idx < ANIMATION_MAX_CHILDREN; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
uint32_t duration = prv_get_total_duration(state, child, true /*delay*/, true /*play_count*/);
|
|
|
|
// It is allowed that the first child may have already been scheduled and played a bit. If
|
|
// this is the case, backup the start time by reducing delay accordingly.
|
|
if (child_idx == 0 && child->abs_start_time_ms) {
|
|
// Remove the sequence's delay, we will shift all of the delay into the first child
|
|
animation->delay_ms = 0;
|
|
int32_t child_position_inc_delay = prv_get_elapsed(child, now) + child->delay_ms;
|
|
earliest_start_time = now - child_position_inc_delay;
|
|
delay = serial_distance32(now, earliest_start_time + duration);
|
|
} else {
|
|
success = prv_schedule_animation(state, now, child, delay);
|
|
if (!success) {
|
|
break;
|
|
}
|
|
delay += duration;
|
|
}
|
|
if (duration == PLAY_DURATION_INFINITE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else if (animation->type == AnimationTypeSpawn) {
|
|
// For a spawn animation, schedule each of the components in parallel
|
|
|
|
// If any of the children have already been scheduled, then we need to back up our start time
|
|
// of the spawn accordingly and adjust the delay_ms field of every child such that
|
|
// prv_get_total_duration() reflects the overall duration of the spawn correctly.
|
|
uint32_t latest_end_time = now;
|
|
for (child_idx = 0; true; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
uint32_t child_duration = prv_get_total_duration(state, child, true /*delay*/,
|
|
true /*play_count*/);
|
|
uint32_t child_end_time;
|
|
if (child->abs_start_time_ms) {
|
|
// Already scheduled
|
|
int32_t child_position_inc_delay = prv_get_elapsed(child, now) + child->delay_ms;
|
|
uint32_t child_start_time = now - child_position_inc_delay;
|
|
if (serial_distance32(child_start_time, earliest_start_time) > 0) {
|
|
// computes (earliest_start_time - child->abs_start_time_ms)
|
|
earliest_start_time = child_start_time;
|
|
}
|
|
child_end_time = child_start_time + child_duration;
|
|
} else {
|
|
child_end_time = now + animation->delay_ms + add_delay_ms + child_duration;
|
|
}
|
|
if (serial_distance32(child_end_time, latest_end_time) < 0) {
|
|
// computes (latest_end_time - child_end_time)
|
|
latest_end_time = child_end_time;
|
|
}
|
|
}
|
|
|
|
// Schedule the children that have not been scheduled yet. If any have already been scheduled,
|
|
// adjust the delays of all children to make it look the same as if the spawn had been
|
|
// scheduled in the past with no children scheduled yet.
|
|
int32_t delay = animation->delay_ms + add_delay_ms;
|
|
for (child_idx = 0; success; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, animation, child_idx);
|
|
if (!child) {
|
|
// No more children
|
|
break;
|
|
}
|
|
|
|
if (now != earliest_start_time) {
|
|
// We need to adjust the delays of each child since one or more were already scheduled
|
|
uint32_t child_start;
|
|
if (child->abs_start_time_ms) {
|
|
child_start = child->abs_start_time_ms - child->delay_ms;
|
|
} else {
|
|
success = prv_schedule_animation(state, now, child, delay);
|
|
child_start = now + delay + child->delay_ms;
|
|
}
|
|
child->delay_ms = serial_distance32(earliest_start_time, child_start);
|
|
} else {
|
|
if (!child->abs_start_time_ms) {
|
|
success = prv_schedule_animation(state, now, child, delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the duration now, after we've possibly adjusted the children delays to compensate
|
|
// for already scheduled children.
|
|
animation->duration_ms = prv_get_total_duration(state, animation, false /*delay*/,
|
|
false /*play_count*/);
|
|
|
|
} else {
|
|
PBL_ASSERTN(animation->type == AnimationTypePrimitive);
|
|
PBL_ASSERTN(animation->implementation->update != NULL);
|
|
}
|
|
|
|
if (now != earliest_start_time) {
|
|
// This is a complex animation that has a child that was already scheduled. We must pretend that
|
|
// the top-level animation started at earliest_start_time. add_delay_ms may end up being
|
|
// negative here if the child already started.
|
|
animation->delay_ms = 0;
|
|
add_delay_ms = serial_distance32(now, earliest_start_time);
|
|
}
|
|
|
|
// Schedule the parent node
|
|
prv_schedule_low_level_animation(state, now, animation, add_delay_ms);
|
|
|
|
return success;
|
|
}
|
|
|
|
static AnimationProgress prv_get_distance_normalized(const AnimationPrivate *animation,
|
|
AnimationProgress time_normalized_raw) {
|
|
AnimationProgress time_normalized;
|
|
if (animation->reverse) {
|
|
time_normalized = ANIMATION_NORMALIZED_MAX - time_normalized_raw;
|
|
} else {
|
|
time_normalized = time_normalized_raw;
|
|
}
|
|
AnimationProgress distance_normalized;
|
|
if (animation->curve >= AnimationCurveCustomFunction) {
|
|
if (animation->curve == AnimationCurveCustomFunction && animation->custom_curve_function) {
|
|
distance_normalized = animation->custom_curve_function(time_normalized);
|
|
} else {
|
|
// Just use the unchanged time if curve is AnimationCurveCustomInterpolation or there is no
|
|
// custom curve function assigned.
|
|
distance_normalized = time_normalized;
|
|
}
|
|
} else {
|
|
distance_normalized = animation_timing_curve(time_normalized, animation->curve);
|
|
}
|
|
return distance_normalized;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_private_update(AnimationState *state, AnimationPrivate *animation,
|
|
AnimationProgress progress_raw) {
|
|
PBL_ASSERTN(animation);
|
|
|
|
if (state == NULL) {
|
|
state = prv_animation_state_get(PebbleTask_Current);
|
|
}
|
|
|
|
const AnimationProgress distance_normalized = prv_get_distance_normalized(animation,
|
|
progress_raw);
|
|
|
|
state->aux->current_animation = animation;
|
|
animation->implementation->update(animation->handle, distance_normalized);
|
|
state->aux->current_animation = NULL;
|
|
}
|
|
|
|
static uint32_t prv_get_time_normalized_raw(const AnimationPrivate *animation, uint32_t now) {
|
|
const int32_t rel_ms_running = serial_distance32(animation->abs_start_time_ms, now);
|
|
|
|
// The caller should already have checked that this animation is active
|
|
PBL_ASSERTN(rel_ms_running >= 0);
|
|
|
|
uint32_t time_normalized_raw;
|
|
if (animation->duration_ms == ANIMATION_DURATION_INFINITE) {
|
|
time_normalized_raw = ANIMATION_NORMALIZED_MIN;
|
|
} else if (animation->duration_ms == 0) {
|
|
time_normalized_raw = ANIMATION_NORMALIZED_MAX;
|
|
} else {
|
|
// animation->duration_ms/2 added in for round to nearest
|
|
time_normalized_raw = (ANIMATION_NORMALIZED_MAX * rel_ms_running + animation->duration_ms/2)
|
|
/ animation->duration_ms;
|
|
time_normalized_raw = MIN(time_normalized_raw, ANIMATION_NORMALIZED_MAX);
|
|
}
|
|
return time_normalized_raw;
|
|
}
|
|
|
|
AnimationProgress animation_private_get_animation_progress(const AnimationPrivate *animation) {
|
|
// FIXME PBL-25497: Make this function less fragile
|
|
// Calling prv_get_ms_since_system_start() here means this function will have different return
|
|
// values if it's called multiple times, all of which will be different from the value actually
|
|
// passed to the animations .update() function
|
|
const uint32_t now = prv_get_ms_since_system_start();
|
|
const uint32_t time_normalized_raw = prv_get_time_normalized_raw(animation, now);
|
|
return prv_get_distance_normalized(animation, time_normalized_raw);
|
|
}
|
|
|
|
bool animation_get_progress(Animation *animation_h, AnimationProgress *progress_out) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h,
|
|
false /* quiet */);
|
|
if (!animation || !prv_animation_is_scheduled(state, animation) || !progress_out) {
|
|
return false;
|
|
}
|
|
|
|
*progress_out = animation_private_get_animation_progress(animation);
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Execute the callbacks (update (optional), started, stopped) for a given animation at the given
|
|
// timestamp. Returns true if this is a parent ready to unschedule itself but can't because
|
|
// the children have not finished yet.
|
|
static bool prv_run_animation(AnimationState *state, AnimationPrivate *animation,
|
|
const uint32_t now, bool do_update) {
|
|
bool blocked_on_children_complete = false;
|
|
|
|
// Play count of 0 should have never been scheduled.
|
|
PBL_ASSERTN(animation->play_count != 0);
|
|
|
|
// If this is the animation's first frame, call the 'started' handler:
|
|
if (animation->handlers.started && !animation->started) {
|
|
animation->handlers.started(animation->handle, animation->context);
|
|
}
|
|
animation->started = true;
|
|
|
|
uint32_t time_normalized_raw = prv_get_time_normalized_raw(animation, now);
|
|
bool completed = (time_normalized_raw == ANIMATION_NORMALIZED_MAX);
|
|
|
|
// Call the update procedure?
|
|
if (do_update || (completed && !animation->is_completed)) {
|
|
animation_private_update(state, animation, time_normalized_raw);
|
|
}
|
|
|
|
|
|
// If completed, either reschedule it now if it needs to be repeated or unschedule it (which
|
|
// results in a call to the stopped handler)
|
|
if (completed && !animation->is_completed) {
|
|
animation->is_completed = true;
|
|
animation->times_played++;
|
|
|
|
if (animation->times_played < animation->play_count) {
|
|
// We need to repeat it. The prv_unschedule_animation() method zeros out times_played, so we
|
|
// need to restore it again after scheduling.
|
|
uint16_t times_played = animation->times_played;
|
|
|
|
// Schedule this at duration past the previous start time
|
|
uint32_t new_start_time = animation->abs_start_time_ms + animation->duration_ms;
|
|
|
|
prv_unschedule_animation(state, animation, true /*finished */, false /*allow_destroy*/,
|
|
false /*force_destroy*/, false /*teardown*/);
|
|
prv_schedule_animation(state, new_start_time, animation, 0 /*add_delay*/);
|
|
animation->times_played = times_played;
|
|
}
|
|
}
|
|
|
|
if (animation->is_completed) {
|
|
// We're done with this animation, we can unschedule it if all of its children have
|
|
// also been unscheduled. If the children have not completed yet, we keep it scheduled but
|
|
// with the is_completed flag set so that we can check it again next interval.
|
|
if (!prv_animation_children_scheduled(state, animation)) {
|
|
// Once all our children have completed, we can safely unschedule ourselves
|
|
prv_unschedule_animation(state, animation, animation->is_completed,
|
|
!animation->parent /*allow_destroy*/, false /*force_destroy*/,
|
|
!animation->parent /*teardown*/);
|
|
} else {
|
|
blocked_on_children_complete = true;
|
|
}
|
|
}
|
|
return blocked_on_children_complete;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// @param state our context
|
|
// @param now the time we are running to. When called from animation_set_elapsed, this will
|
|
// be in the future, otherwise it will be the current time
|
|
// @param top_level_animation only used by animation_set_elapsed, this is the top-level parent
|
|
// that we are setting the elapsed of.
|
|
// @param top_level_start_time when the top_level animation started playing (used for debug
|
|
// logging only)
|
|
static void prv_run(AnimationState *state, uint32_t now, AnimationPrivate *top_level_animation,
|
|
uint32_t top_level_start_time, bool do_update) {
|
|
for (int i = 0; i < 2; i++) {
|
|
bool have_blocked_parents = false;
|
|
// We run through the animations up to 2 times. If during the first run we detect that some
|
|
// parents want to unschedule but couldn't because they still have children running, then we
|
|
// run again so that the parents can check again if their children finished on the first run.
|
|
AnimationPrivate *animation = (AnimationPrivate *) state->scheduled_head;
|
|
while (animation) {
|
|
#ifdef UNITTEST
|
|
// This is to ensure unit test fails in case of bad behaviour - no need to execute in
|
|
// in normal FW since this case should not exist with defer_delete check
|
|
AnimationPrivate *animation_p = animation_private_animation_find(animation->handle);
|
|
PBL_ASSERTN(animation_p != NULL);
|
|
// Make sure this is an animation in the scheduled list
|
|
PBL_ASSERTN(list_contains(state->scheduled_head, &animation->list_node));
|
|
#endif
|
|
|
|
const int32_t rel_ms_running = serial_distance32(animation->abs_start_time_ms, now);
|
|
if (rel_ms_running < 0) {
|
|
// Animations are ordered by abs_start_time_ms.
|
|
// We've reached an animation that should not start yet, so
|
|
// everything after and including this animation shouldn't run yet.
|
|
break;
|
|
}
|
|
|
|
// Get a pointer to next now, because after possible unscheduling, this animation may change
|
|
// into a node of the unscheduled list or become freed
|
|
state->aux->iter_next = list_get_next(&animation->list_node);
|
|
|
|
// If only running from a specific top-level animation, see if this animation is the target
|
|
// one or one of it's children, and if so advance it
|
|
if (!top_level_animation || animation == top_level_animation
|
|
|| prv_is_descendent_of(state, animation, top_level_animation)) {
|
|
ANIMATION_LOG_DEBUG("advancing animation %d to %"PRIu32" ms", (int)animation->handle,
|
|
now - top_level_start_time);
|
|
// Run this animation. Record if this is a parent ready to unschedule itself but
|
|
// still waiting for one of its children.
|
|
have_blocked_parents |= prv_run_animation(state, animation, now, do_update);
|
|
}
|
|
|
|
// Next one
|
|
animation = (AnimationPrivate *)state->aux->iter_next;
|
|
}
|
|
|
|
// If no blocked parents, we can exit right away
|
|
if (!have_blocked_parents) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We are done iterating
|
|
state->aux->iter_next = NULL;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
typedef Animation *(*CreateFromArrayFunc)(Animation **animation_array,
|
|
uint32_t array_len);
|
|
|
|
static Animation *prv_call_using_vargs(CreateFromArrayFunc func, Animation *animation_a,
|
|
Animation *animation_b, Animation *animation_c, va_list args) {
|
|
const int max_args = ANIMATION_MAX_CREATE_VARGS;
|
|
typedef Animation *AnimationPtr;
|
|
AnimationPtr animation_array[max_args];
|
|
int array_len = 2;
|
|
|
|
// A and B must not be NULL
|
|
if (!animation_a || !animation_b) {
|
|
return false;
|
|
}
|
|
animation_array[0] = animation_a;
|
|
animation_array[1] = animation_b;
|
|
|
|
if (animation_c) {
|
|
// If c is not NULL, we need to figure out the array length
|
|
animation_array[array_len++] = animation_c;
|
|
while (array_len < max_args) {
|
|
void *arg = va_arg(args, void *);
|
|
if (arg == NULL) {
|
|
break;
|
|
}
|
|
animation_array[array_len++] = arg;
|
|
}
|
|
}
|
|
|
|
// Create from an array
|
|
return func(animation_array, array_len);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Complex animations don't perform any logic in their update callback
|
|
static void prv_complex_animation_update(Animation * animation, uint32_t distance) {
|
|
}
|
|
static const AnimationImplementation s_complex_implementation = {
|
|
.update = (AnimationUpdateImplementation) prv_complex_animation_update,
|
|
};
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static Animation *prv_complex_init(Animation *parent_h, Animation **animation_array,
|
|
uint32_t array_len, AnimationType type) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (array_len > ANIMATION_MAX_CHILDREN) {
|
|
// Exceed max # of children allowed?
|
|
return NULL;
|
|
}
|
|
|
|
bool success = true;
|
|
|
|
AnimationPrivate *parent = animation_private_animation_find(parent_h);
|
|
parent->type = type;
|
|
|
|
// Keep track of which children we added so we can restore them in case of error
|
|
bool used_children[array_len];
|
|
memset(used_children, 0, sizeof(bool) * array_len);
|
|
|
|
// Set the parent on each of the components
|
|
uint32_t child_idx = 0;
|
|
for (uint32_t i = 0; i < array_len; i++) {
|
|
AnimationPrivate *component = prv_find_animation_by_handle(state, animation_array[i],
|
|
false /*quiet*/);
|
|
if (!component) {
|
|
// It is OK to pass in already destroyed children.
|
|
continue;
|
|
}
|
|
|
|
// The 2nd and subsequent children of a sequence must NOT be already scheduled. Also fail if
|
|
// child already has a parent
|
|
if (component->parent
|
|
|| ((type == AnimationTypeSequence) && (i > 0) && (component->abs_start_time_ms))) {
|
|
success = false;
|
|
break;
|
|
}
|
|
component->parent = parent;
|
|
component->child_idx = child_idx++;
|
|
used_children[i] = true;
|
|
}
|
|
|
|
if (!success) {
|
|
for (uint32_t i = 0; i < array_len; i++) {
|
|
if (!used_children[i]) {
|
|
continue;
|
|
}
|
|
// Undo setting of the parent and child_idx on the components we modified.
|
|
AnimationPrivate *component = prv_find_animation_by_handle(state, animation_array[i],
|
|
false /*quiet*/);
|
|
if (component) {
|
|
component->parent = NULL;
|
|
component->child_idx = 0;
|
|
}
|
|
}
|
|
prv_unlink_and_free(state, parent);
|
|
parent_h = NULL;
|
|
}
|
|
|
|
return parent_h;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static Animation *prv_complex_create(Animation **animation_array, uint32_t array_len,
|
|
AnimationType type) {
|
|
if (array_len > ANIMATION_MAX_CHILDREN) {
|
|
// Exceed max # of children allowed?
|
|
return NULL;
|
|
}
|
|
|
|
AnimationPrivate *parent = applib_type_malloc(AnimationPrivate);
|
|
if (!parent) {
|
|
return NULL;
|
|
}
|
|
Animation *parent_h = animation_private_animation_init(parent);
|
|
parent->implementation = &s_complex_implementation;
|
|
|
|
return prv_complex_init(parent_h, animation_array, array_len,
|
|
type);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static Animation *prv_animation_clone(AnimationState *state, AnimationPrivate *from) {
|
|
Animation *clone_h;
|
|
AnimationPrivate *clone = NULL;
|
|
bool success = true;
|
|
|
|
// If this is a complex animation, create the children
|
|
if (from->type != AnimationTypePrimitive) {
|
|
// Count the children
|
|
int num_children;
|
|
for (num_children = 0; num_children < ANIMATION_MAX_CHILDREN; num_children++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, from, num_children);
|
|
if (!child) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Allocate array to hold the children and allocate each of them.
|
|
Animation *children[num_children];
|
|
for (int child_idx = 0; child_idx < num_children; child_idx++) {
|
|
AnimationPrivate *child = prv_find_animation_by_parent_child_idx(state, from, child_idx);
|
|
children[child_idx] = prv_animation_clone(state, child);
|
|
|
|
// Bail if we couldn't create the child
|
|
if (children[child_idx] == NULL) {
|
|
num_children = child_idx;
|
|
success = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Allocate the complex animation parent
|
|
if (success) {
|
|
clone_h = prv_complex_create(children, num_children, from->type);
|
|
clone = prv_find_animation_by_handle(state, clone_h, false /*quiet*/);
|
|
}
|
|
if (!clone) {
|
|
for (int i = 0; i < num_children; i++) {
|
|
animation_destroy(children[i]);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
} else {
|
|
if (from->is_property_animation) {
|
|
PropertyAnimationPrivate *prop = property_animation_private_clone(
|
|
(PropertyAnimationPrivate *)from);
|
|
if (prop) {
|
|
clone = &prop->animation;
|
|
clone->is_property_animation = true;
|
|
}
|
|
} else {
|
|
clone = applib_type_malloc(AnimationPrivate);
|
|
}
|
|
if (!clone) {
|
|
return NULL;
|
|
}
|
|
clone_h = animation_private_animation_init(clone);
|
|
}
|
|
|
|
// Copy the values into the clone
|
|
clone->implementation = from->implementation;
|
|
clone->handlers = from->handlers;
|
|
clone->context = from->context;
|
|
clone->delay_ms = from->delay_ms;
|
|
clone->duration_ms = from->duration_ms;
|
|
clone->play_count = from->play_count;
|
|
clone->curve = from->curve;
|
|
clone->auto_destroy = from->auto_destroy;
|
|
clone->reverse = from->reverse;
|
|
clone->custom_curve_function = from->custom_curve_function;
|
|
|
|
return clone_h;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_private_state_init(AnimationState *state) {
|
|
#ifndef UNITTEST
|
|
_Static_assert(sizeof(AnimationState) <= sizeof(AnimationLegacy2Scheduler),
|
|
"Animation state larger than allowed for 2.0 compatibility");
|
|
#endif
|
|
|
|
// If this a legacy 2.0 application, instantiate the 2.0 legacy animation support
|
|
#ifndef RECOVERY_FW
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
animation_legacy2_private_init_scheduler((AnimationLegacy2Scheduler *)state);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Allocate the auxiliary information
|
|
AnimationAuxState *aux_state = applib_type_malloc(AnimationAuxState);
|
|
PBL_ASSERTN(aux_state);
|
|
*aux_state = (AnimationAuxState) {
|
|
// To aid for debugging, let's start each task off at a different handle offset. Eventually they
|
|
// will collide but it is not required that each task have globally unique handles
|
|
.next_handle = pebble_task_get_current() * 100000000,
|
|
.last_delay_ms = ANIMATION_TARGET_FRAME_INTERVAL_MS,
|
|
.last_frame_time_ms = prv_get_ms_since_system_start()
|
|
};
|
|
|
|
*state = (AnimationState) {
|
|
.signature = ANIMATION_STATE_3_X_SIGNATURE,
|
|
.aux = aux_state,
|
|
};
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_private_state_deinit(AnimationState *state) {
|
|
|
|
if (!process_manager_compiled_with_legacy2_sdk()) {
|
|
applib_free(state->aux);
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Return true if the animation globals were instantiated using the legacy 2.x animation
|
|
// manager
|
|
bool animation_private_using_legacy_2(AnimationState *state) {
|
|
if (state == NULL) {
|
|
state = prv_animation_state_get(PebbleTask_Current);
|
|
}
|
|
return (state->signature != ANIMATION_STATE_3_X_SIGNATURE);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Return the animation pointer for the given handle
|
|
AnimationPrivate *animation_private_animation_find(Animation *handle) {
|
|
return prv_find_animation_by_handle(NULL, handle, false /*quiet*/);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_private_animation_init(AnimationPrivate *animation) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
|
|
*animation = (AnimationPrivate) {
|
|
.handle = (Animation *)(uintptr_t)(++state->aux->next_handle),
|
|
.duration_ms = ANIMATION_DEFAULT_DURATION_MS,
|
|
.play_count = 1,
|
|
.curve = AnimationCurveDefault,
|
|
.auto_destroy = true
|
|
};
|
|
PBL_ASSERTN(animation->handle);
|
|
|
|
state->unscheduled_head = list_insert_before(state->unscheduled_head, &animation->list_node);
|
|
ANIMATION_LOG_DEBUG("creating %d (%p)", (int)animation->handle, animation);
|
|
return (Animation *)(animation->handle);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_private_timer_callback(void *context) {
|
|
AnimationState *state = (AnimationState *)context;
|
|
const uint32_t now = prv_get_ms_since_system_start();
|
|
|
|
// Tell the timer that we received the event it sent
|
|
animation_service_timer_event_received();
|
|
|
|
if(!s_paused){
|
|
// Run all animations for this time interval
|
|
prv_run(state, now, NULL /*top-level animation*/, 0/*top-level start time*/, true/*do_update*/);
|
|
}
|
|
|
|
// Frame rate control:
|
|
const int32_t frame_interval_ms = serial_distance32(state->aux->last_frame_time_ms, now);
|
|
const int32_t error_ms = frame_interval_ms - ANIMATION_TARGET_FRAME_INTERVAL_MS;
|
|
const int32_t theoretic_delay_ms = state->aux->last_delay_ms - error_ms;
|
|
const uint32_t delay_ms = CLIP(theoretic_delay_ms, (int32_t) 0,
|
|
(int32_t) ANIMATION_TARGET_FRAME_INTERVAL_MS);
|
|
|
|
prv_reschedule_timer(state, delay_ms);
|
|
state->aux->last_delay_ms = delay_ms;
|
|
state->aux->last_frame_time_ms = now;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_create(void) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return (Animation *)animation_legacy2_create();
|
|
}
|
|
AnimationPrivate *animation = applib_type_malloc(AnimationPrivate);
|
|
if (!animation) {
|
|
return NULL;
|
|
}
|
|
|
|
return animation_private_animation_init(animation);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_destroy(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_destroy((AnimationLegacy2 *)animation_h);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation || animation->parent) {
|
|
// Only top-level animations can be destroyed
|
|
return false;
|
|
}
|
|
|
|
// If we're being called from the stopped or teardown handler, set the defer_delete flag.
|
|
// This will inform us to delete the animation once we return back to the animation code
|
|
// from the handler.
|
|
if (animation->calling_end_handlers) {
|
|
animation->defer_delete = true;
|
|
return true;
|
|
}
|
|
|
|
// Set this flag so that no one can reschedule it while we're trying to destroy it
|
|
// (like it's stopped handler)
|
|
animation->being_destroyed = true;
|
|
|
|
// Unschedule and destroy it
|
|
prv_unschedule_animation(state, animation, false /*finished*/, false /*allow_auto_destroy*/,
|
|
true /*force_destroy*/, true /*teardown*/);
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_auto_destroy(Animation *animation_h, bool auto_destroy) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
PBL_ASSERTN(!auto_destroy);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
animation->auto_destroy = auto_destroy;
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_schedule(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_schedule((AnimationLegacy2 *)animation_h);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation || animation->parent) {
|
|
// Not allowed to schedule an animation that has a parent.
|
|
return false;
|
|
}
|
|
|
|
// Unschedule if it's already scheduled, or if the play_count is 0 (in which case we allow it to
|
|
// be auto-destroyed).
|
|
if (animation->abs_start_time_ms || animation->play_count == 0) {
|
|
const bool allow_auto_destroy = (animation->play_count == 0);
|
|
prv_unschedule_animation(state, animation, false /*finished=false*/, allow_auto_destroy,
|
|
false /*force_destroy*/, true /*teardown*/);
|
|
}
|
|
|
|
// Schedule it
|
|
bool success = prv_schedule_animation(state, prv_get_ms_since_system_start(), animation,
|
|
0 /*add_delay*/);
|
|
return success;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_unschedule(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_unschedule((AnimationLegacy2 *)animation_h);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, true /*quiet*/);
|
|
if (!animation || animation->parent) {
|
|
return false;
|
|
}
|
|
|
|
prv_unschedule_animation(state, animation, false /*finished=false*/, true /*allow_auto_destroy*/,
|
|
false /*force_destroy*/, true /*teardown*/);
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_unschedule_all(void) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_unschedule_all();
|
|
return;
|
|
}
|
|
|
|
while (state->scheduled_head) {
|
|
AnimationPrivate *animation = (AnimationPrivate *)state->scheduled_head;
|
|
|
|
// We can only unschedule top-level animations
|
|
while (animation) {
|
|
if (!animation->parent) {
|
|
break;
|
|
}
|
|
animation = (AnimationPrivate *) list_get_next(&animation->list_node);
|
|
}
|
|
// There had to be at least 1 top-level animation
|
|
PBL_ASSERTN(animation);
|
|
prv_unschedule_animation(state, animation, false /*finished*/, true /*allow_auto_destroy*/,
|
|
false /*force_destroy*/, true /*teardown*/);
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_is_scheduled(Animation *animation_h) {
|
|
if (!animation_h) {
|
|
return false;
|
|
}
|
|
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return animation_legacy2_is_scheduled((AnimationLegacy2 *)animation_h);
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, true /*quiet*/);
|
|
if (!animation) {
|
|
return false;
|
|
}
|
|
|
|
return prv_animation_is_scheduled(state, animation);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_handlers(Animation *animation_h, AnimationHandlers handlers,
|
|
void *context) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
AnimationLegacy2Handlers *legacy_handlers = (AnimationLegacy2Handlers *)&handlers;
|
|
animation_legacy2_set_handlers((AnimationLegacy2 *)animation_h, *legacy_handlers, context);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
animation->context = context;
|
|
animation->handlers = handlers;
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
AnimationHandlers animation_get_handlers(Animation *animation_h) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
AnimationHandlers *handlers = (AnimationHandlers *)&((AnimationLegacy2 *)animation_h)->handlers;
|
|
return *handlers;
|
|
}
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return (AnimationHandlers) {0};
|
|
}
|
|
|
|
return animation->handlers;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_implementation(Animation *animation_h,
|
|
const AnimationImplementation *implementation) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_set_implementation((AnimationLegacy2 *)animation_h,
|
|
(const AnimationLegacy2Implementation *)implementation);
|
|
return true;
|
|
}
|
|
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
animation->implementation = implementation;
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
const AnimationImplementation* animation_get_implementation(Animation *animation_h) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return (const AnimationImplementation*)((AnimationLegacy2 *)animation_h)->implementation;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return NULL;
|
|
}
|
|
return animation->implementation;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void *animation_get_context(Animation *animation_h) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return ((AnimationLegacy2 *)animation_h)->context;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return NULL;
|
|
}
|
|
return animation->context;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_delay(Animation *animation_h, uint32_t delay_ms) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_set_delay((AnimationLegacy2 *)animation_h, delay_ms);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
animation->delay_ms = delay_ms;
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
uint32_t animation_get_delay(Animation *animation_h) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return ((AnimationLegacy2 *)animation_h)->delay_ms;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return 0;
|
|
}
|
|
|
|
return animation->delay_ms;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
uint32_t animation_get_abs_start_time_ms(Animation *animation_h) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return ((AnimationLegacy2 *)animation_h)->abs_start_time_ms;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return 0;
|
|
}
|
|
|
|
return animation->abs_start_time_ms;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_duration(Animation *animation_h, uint32_t duration_ms) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_set_duration((AnimationLegacy2 *)animation_h, duration_ms);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)
|
|
|| animation->type != AnimationTypePrimitive) {
|
|
return false;
|
|
}
|
|
|
|
animation->duration_ms = duration_ms;
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
uint32_t animation_get_duration(Animation *animation_h, bool include_delay,
|
|
bool include_play_count) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return ((AnimationLegacy2 *)animation_h)->duration_ms;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return 0;
|
|
}
|
|
|
|
return prv_get_total_duration(state, animation, include_delay, include_play_count);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_curve(Animation *animation_h, AnimationCurve curve) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_set_curve((AnimationLegacy2 *)animation_h, curve);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
PBL_ASSERTN(curve < AnimationCurveCustomFunction);
|
|
animation->curve = curve;
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
AnimationCurve animation_get_curve(Animation *animation_h) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return ((AnimationLegacy2 *)animation_h)->curve;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return AnimationCurveDefault;
|
|
}
|
|
|
|
return animation->curve;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static bool prv_animation_set_custom_function(Animation *animation_h, AnimationCurve curve,
|
|
void *function) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
if (curve != AnimationCurveCustomFunction) {
|
|
// 2.x doesn't support AnimationCurveCustomInterpolationFunction
|
|
return false;
|
|
}
|
|
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
animation_legacy2_set_custom_curve((AnimationLegacy2 *)animation_h, function);
|
|
return true;
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
animation->custom_function = function;
|
|
if (function) {
|
|
animation->curve = curve;
|
|
} else {
|
|
animation->curve = AnimationCurveDefault;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_custom_curve(Animation *animation_h,
|
|
AnimationCurveFunction curve_function) {
|
|
return prv_animation_set_custom_function(animation_h,
|
|
AnimationCurveCustomFunction,
|
|
curve_function);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_custom_interpolation(Animation *animation_h,
|
|
InterpolateInt64Function interpolate_function) {
|
|
return prv_animation_set_custom_function(animation_h,
|
|
AnimationCurveCustomInterpolationFunction,
|
|
interpolate_function);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static void *prv_animation_get_custom_function(Animation *animation_h, AnimationCurve curve) {
|
|
if (animation_private_using_legacy_2(NULL)) {
|
|
if (curve != AnimationCurveCustomFunction) {
|
|
// 2.x doesn't support AnimationCurveCustomInterpolationFunction
|
|
return NULL;
|
|
}
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
return animation_legacy2_get_custom_curve((AnimationLegacy2 *)animation_h);
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(NULL, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return NULL;
|
|
}
|
|
|
|
return (animation->curve == curve) ? animation->custom_function : NULL;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
AnimationCurveFunction animation_get_custom_curve(Animation *animation_h) {
|
|
return prv_animation_get_custom_function(animation_h, AnimationCurveCustomFunction);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
InterpolateInt64Function animation_get_custom_interpolation(Animation *animation_h) {
|
|
return prv_animation_get_custom_function(animation_h, AnimationCurveCustomInterpolationFunction);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_immutable(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return false;
|
|
}
|
|
|
|
animation->immutable = true;
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_is_immutable(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return false;
|
|
}
|
|
|
|
return animation->immutable;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_reverse(Animation *animation_h, bool reverse) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
if (animation_private_using_legacy_2(state)) {
|
|
// We need to enable other applib modules like scroll_layer, menu_layer, etc. which are
|
|
// compiled to use the 3.0 animation API to work with 2.0 apps.
|
|
PBL_ASSERTN(!reverse);
|
|
}
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
// NOTE: We still need to implement reverse for sequence and spawn animations.
|
|
// tracked in this JIRA: https://pebbletechnology.atlassian.net/browse/PBL-14838
|
|
animation->reverse = reverse;
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_get_reverse(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return false;
|
|
}
|
|
|
|
return animation->reverse;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_play_count(Animation *animation_h, uint32_t play_count) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!prv_is_mutable(state, animation)) {
|
|
return false;
|
|
}
|
|
|
|
if (play_count == ANIMATION_PLAY_COUNT_INFINITE) {
|
|
animation->play_count = ANIMATION_PLAY_COUNT_INFINITE_STORED;
|
|
} else if (play_count > ANIMATION_PLAY_COUNT_INFINITE_STORED) {
|
|
// We can't support play counts greater than ANIMATION_PLAY_COUNT_INFINITE_STORED
|
|
return false;
|
|
} else {
|
|
animation->play_count = play_count;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
uint32_t animation_get_play_count(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return false;
|
|
}
|
|
|
|
if (animation->play_count == ANIMATION_PLAY_COUNT_INFINITE_STORED) {
|
|
return ANIMATION_PLAY_COUNT_INFINITE;
|
|
} else {
|
|
return animation->play_count;
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_set_elapsed(Animation *parent_h, uint32_t elapsed_ms) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
|
|
AnimationPrivate *parent = prv_find_animation_by_handle(state, parent_h, false /*quiet*/);
|
|
if (!parent || !prv_animation_is_scheduled(state, parent) || parent->parent) {
|
|
// Can only set the elapsed of a top-level animation, and then, only after it has
|
|
// been scheduled.
|
|
return false;
|
|
}
|
|
|
|
// First, we need to compute the absolute start time of this animation, backing it up by the
|
|
// delay and any repeats we have already done
|
|
uint32_t start_ms = parent->abs_start_time_ms;
|
|
start_ms -= parent->times_played * (parent->duration_ms + parent->delay_ms);
|
|
|
|
// Loop through animation and all of it's children until the "virtual now" catches up to
|
|
// the desired elapsed
|
|
const uint32_t now = prv_get_ms_since_system_start();
|
|
|
|
uint32_t virtual_now = now;
|
|
const uint32_t target_now = start_ms + elapsed_ms;
|
|
|
|
while (serial_distance32(virtual_now, target_now) >= 0) {
|
|
prv_run(state, virtual_now, parent, start_ms, (virtual_now == target_now) /*do_update*/);
|
|
|
|
// Advance virtual now
|
|
uint32_t remaining = serial_distance32(virtual_now, target_now);
|
|
if (remaining == 0) {
|
|
break;
|
|
}
|
|
virtual_now += MIN(ANIMATION_TARGET_FRAME_INTERVAL_MS, remaining);
|
|
}
|
|
|
|
// Now, go and backup the abs_start_time_ms of the animations we skipped ahead on
|
|
prv_backup_start_time(state, parent, virtual_now - now);
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
bool animation_get_elapsed(Animation *animation_h, int32_t *elapsed_ms) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation || !prv_animation_is_scheduled(state, animation) || !elapsed_ms) {
|
|
return false;
|
|
}
|
|
|
|
*elapsed_ms = prv_get_elapsed(animation, prv_get_ms_since_system_start());
|
|
return true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_sequence_create_from_array(Animation **animation_array, uint32_t array_len) {
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(NULL));
|
|
Animation *seq = prv_complex_create(animation_array, array_len, AnimationTypeSequence);
|
|
return seq;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_sequence_init_from_array(Animation *parent, Animation **animation_array,
|
|
uint32_t array_len) {
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(NULL));
|
|
return prv_complex_init(parent, animation_array, array_len, AnimationTypeSequence);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_sequence_create(Animation *animation_a, Animation *animation_b,
|
|
Animation *animation_c, ...) {
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(NULL));
|
|
va_list args;
|
|
va_start(args, animation_c);
|
|
Animation *animation = prv_call_using_vargs(animation_sequence_create_from_array, animation_a,
|
|
animation_b, animation_c, args);
|
|
va_end(args);
|
|
return animation;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_spawn_create_from_array(Animation **animation_array, uint32_t array_len) {
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(NULL));
|
|
Animation *spawn = prv_complex_create(animation_array, array_len, AnimationTypeSpawn);
|
|
return spawn;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_spawn_create(Animation *animation_a, Animation *animation_b,
|
|
Animation *animation_c, ...) {
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(NULL));
|
|
va_list args;
|
|
va_start(args, animation_c);
|
|
Animation *animation = prv_call_using_vargs(animation_spawn_create_from_array, animation_a,
|
|
animation_b, animation_c, args);
|
|
va_end(args);
|
|
return animation;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
Animation *animation_clone(Animation *animation_h) {
|
|
AnimationState *state = prv_animation_state_get(PebbleTask_Current);
|
|
PBL_ASSERTN(!animation_private_using_legacy_2(state));
|
|
|
|
AnimationPrivate *animation = prv_find_animation_by_handle(state, animation_h, false /*quiet*/);
|
|
if (!animation) {
|
|
return false;
|
|
}
|
|
|
|
return prv_animation_clone(state, animation);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
static void prv_dump_animations(ListNode *node, bool is_scheduled, char *buffer, int buffer_size) {
|
|
while (node) {
|
|
AnimationPrivate *animation = (AnimationPrivate *)node;
|
|
|
|
dbgserial_putstr_fmt(buffer, buffer_size,
|
|
"<%p> { sch: %s, handle = %p, abs_start_time_ms = %"PRIu32", delay = %"PRIu32", "
|
|
"duration = %"PRIu32", curve = %i, run = %p }",
|
|
animation, is_scheduled ? "yes" : "no", animation->handle,
|
|
(uint32_t)animation->abs_start_time_ms,
|
|
(uint32_t)animation->delay_ms, (uint32_t)animation->duration_ms,
|
|
animation->curve,
|
|
animation->implementation->update);
|
|
|
|
node = node->next;
|
|
}
|
|
}
|
|
|
|
static void prv_dump_legacy_animations(ListNode *head, char *buffer, int buffer_size) {
|
|
AnimationLegacy2 *animation = (AnimationLegacy2 *)head;
|
|
|
|
while (animation) {
|
|
dbgserial_putstr_fmt(buffer, buffer_size,
|
|
"<%p> { sch: yes, start handle = %p, stop handle = %p,"
|
|
"abs_start_time_ms = %"PRIu32", delay = %"PRIu32", "
|
|
"duration = %"PRIu32", curve = %i, run = %p }",
|
|
animation, animation->handlers.started, animation->handlers.stopped,
|
|
animation->abs_start_time_ms,
|
|
animation->delay_ms, animation->duration_ms,
|
|
animation->curve,
|
|
animation->implementation->update);
|
|
|
|
animation = (AnimationLegacy2 *)list_get_next(&animation->list_node);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
static void prv_dump_scheduler(char* buffer, int buffer_size, AnimationState* state) {
|
|
portENTER_CRITICAL();
|
|
if (animation_private_using_legacy_2(state)) {
|
|
AnimationLegacy2Scheduler *legacy_state = (AnimationLegacy2Scheduler *)state;
|
|
prv_dump_legacy_animations(legacy_state->head, buffer, buffer_size);
|
|
} else {
|
|
prv_dump_animations(state->scheduled_head, true, buffer, buffer_size);
|
|
prv_dump_animations(state->unscheduled_head, false, buffer, buffer_size);
|
|
}
|
|
portEXIT_CRITICAL();
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_private_pause(void) {
|
|
s_paused = true;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void animation_private_resume(void) {
|
|
s_paused = false;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void command_animations_info(void) {
|
|
char buffer[128];
|
|
dbgserial_putstr_fmt(buffer, sizeof(buffer), "Now: %"PRIu32, prv_get_ms_since_system_start());
|
|
|
|
dbgserial_putstr_fmt(buffer, sizeof(buffer), "Kernel Animations:");
|
|
prv_dump_scheduler(buffer, sizeof(buffer), kernel_applib_get_animation_state());
|
|
|
|
dbgserial_putstr_fmt(buffer, sizeof(buffer), "App Animations:");
|
|
prv_dump_scheduler(buffer, sizeof(buffer), app_state_get_animation_state());
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void command_pause_animations(void) {
|
|
animation_private_pause();
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
void command_resume_animations(void) {
|
|
animation_private_resume();
|
|
}
|