2024-03-19 14:27:50 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
|
|
#include "compiler.h"
|
|
|
|
#include "transition.h"
|
|
|
|
#include "utils.h"
|
|
|
|
|
|
|
|
/// Get the current value of an `animatable`.
|
|
|
|
double animatable_get(const struct animatable *a) {
|
|
|
|
if (a->step_state) {
|
|
|
|
return a->step_state->current;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->duration) {
|
|
|
|
assert(a->progress < a->duration);
|
|
|
|
return a->interpolator(a);
|
|
|
|
}
|
|
|
|
return a->target;
|
|
|
|
}
|
|
|
|
|
|
|
|
double animatable_get_progress(const struct animatable *a) {
|
|
|
|
if (a->duration) {
|
|
|
|
return (double)a->progress / a->duration;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Advance the animation by a given number of steps.
|
|
|
|
void animatable_step(struct animatable *a, unsigned int steps) {
|
|
|
|
if (!a->duration || !steps) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(a->progress < a->duration);
|
|
|
|
if (steps > a->duration - a->progress) {
|
|
|
|
steps = a->duration - a->progress;
|
|
|
|
}
|
|
|
|
a->progress += steps;
|
|
|
|
if (a->step_state) {
|
|
|
|
a->step(a, steps);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->progress == a->duration) {
|
|
|
|
a->start = a->target;
|
|
|
|
a->duration = 0;
|
|
|
|
a->progress = 0;
|
|
|
|
if (a->step_state) {
|
|
|
|
a->step_state->current = a->target;
|
|
|
|
}
|
2024-03-21 18:00:16 +00:00
|
|
|
if (a->callback) {
|
|
|
|
a->callback(TRANSITION_COMPLETED, a->callback_data);
|
|
|
|
a->callback = NULL;
|
|
|
|
a->callback_data = NULL;
|
|
|
|
}
|
2024-03-19 14:27:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns whether an `animatable` is currently animating.
|
|
|
|
bool animatable_is_animating(const struct animatable *a) {
|
|
|
|
assert(!a->duration || a->progress < a->duration);
|
|
|
|
return a->duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cancel the current animation of an `animatable`. This stops the animation and
|
|
|
|
/// the `animatable` will retain its current value.
|
|
|
|
///
|
|
|
|
/// Returns true if the `animatable` was animated before this function is called.
|
|
|
|
bool animatable_cancel(struct animatable *a) {
|
|
|
|
if (!a->duration) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
a->start = animatable_get(a);
|
|
|
|
a->target = a->start;
|
|
|
|
a->duration = 0;
|
|
|
|
a->progress = 0;
|
|
|
|
if (a->step_state) {
|
|
|
|
a->step_state->current = a->start;
|
|
|
|
}
|
2024-03-21 18:00:16 +00:00
|
|
|
if (a->callback) {
|
|
|
|
a->callback(TRANSITION_CANCELED, a->callback_data);
|
|
|
|
a->callback = NULL;
|
|
|
|
a->callback_data = NULL;
|
|
|
|
}
|
2024-03-19 14:27:50 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cancel the current animation of an `animatable` and set its value to its target.
|
|
|
|
///
|
|
|
|
/// Returns true if the `animatable` was animated before this function is called.
|
|
|
|
bool animatable_early_stop(struct animatable *a) {
|
|
|
|
if (!a->duration) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
a->start = a->target;
|
|
|
|
a->duration = 0;
|
|
|
|
a->progress = 0;
|
|
|
|
if (a->step_state) {
|
|
|
|
a->step_state->current = a->target;
|
|
|
|
}
|
2024-03-21 18:00:16 +00:00
|
|
|
if (a->callback) {
|
|
|
|
a->callback(TRANSITION_STOPPED_EARLY, a->callback_data);
|
|
|
|
a->callback = NULL;
|
|
|
|
a->callback_data = NULL;
|
|
|
|
}
|
2024-03-19 14:27:50 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Change the target value of an `animatable`.
|
|
|
|
/// If the `animatable` is already animating, the animation will be canceled first.
|
2024-03-21 18:00:16 +00:00
|
|
|
void animatable_set_target(struct animatable *a, double target, unsigned int duration,
|
|
|
|
transition_callback_fn cb, void *data) {
|
2024-03-19 14:27:50 +00:00
|
|
|
animatable_cancel(a);
|
|
|
|
if (!duration) {
|
|
|
|
a->start = target;
|
|
|
|
a->target = target;
|
2024-03-21 18:00:16 +00:00
|
|
|
if (cb) {
|
|
|
|
cb(TRANSITION_COMPLETED, data);
|
|
|
|
}
|
2024-03-19 14:27:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
a->target = target;
|
|
|
|
a->duration = duration;
|
|
|
|
a->progress = 0;
|
|
|
|
if (a->step_state) {
|
|
|
|
a->step(a, 0);
|
|
|
|
}
|
2024-03-21 18:00:16 +00:00
|
|
|
a->callback = cb;
|
|
|
|
a->callback_data = data;
|
2024-03-19 14:27:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new animatable.
|
|
|
|
struct animatable animatable_new(double value, interpolator_fn interpolator, step_fn step) {
|
|
|
|
assert(!interpolator || !step);
|
|
|
|
struct animatable ret = {
|
|
|
|
.start = value,
|
|
|
|
.target = value,
|
|
|
|
.duration = 0,
|
|
|
|
.progress = 0,
|
|
|
|
.step_state = NULL,
|
|
|
|
};
|
|
|
|
if (interpolator) {
|
|
|
|
ret.interpolator = interpolator;
|
|
|
|
} else if (step) {
|
|
|
|
ret.step = step;
|
|
|
|
step(&ret, 0);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
double linear_interpolator(const struct animatable *a) {
|
|
|
|
double t = (double)a->progress / a->duration;
|
|
|
|
return (1 - t) * a->start + t * a->target;
|
|
|
|
}
|