Files
pebble/src/fw/applib/ui/animation.c
2025-01-27 11:38:16 -08:00

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();
}