diff --git a/src/meson.build b/src/meson.build index bf023886..31ec0b49 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,7 +10,7 @@ base_deps = [ srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c', - 'vblank.c') ] + 'vblank.c', 'transition.c') ] picom_inc = include_directories('.') cflags = [] diff --git a/src/transition.c b/src/transition.c new file mode 100644 index 00000000..89f4930c --- /dev/null +++ b/src/transition.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include +#include +#include + +#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; + } + } +} + +/// 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; + } + 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; + } + return true; +} + +/// Change the target value of an `animatable`. +/// If the `animatable` is already animating, the animation will be canceled first. +void animatable_set_target(struct animatable *a, double target, unsigned int duration) { + animatable_cancel(a); + if (!duration) { + a->start = target; + a->target = target; + return; + } + + a->target = target; + a->duration = duration; + a->progress = 0; + if (a->step_state) { + a->step(a, 0); + } +} + +/// 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; +} diff --git a/src/transition.h b/src/transition.h new file mode 100644 index 00000000..c57cf4dc --- /dev/null +++ b/src/transition.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once +#include + +struct animatable; +/// The interpolator function for an animatable. This function should calculate the +/// current value of the `animatable` based on its `start`, `target`, `duration` and +/// `progress`. +typedef double (*interpolator_fn)(const struct animatable *); +/// The step function for an animatable. This function should advance the animation state +/// by one step. This function is called _after_ `progress` is incremented. If `progress` +/// is 0 when the function is called, it means an animation has just started, and this +/// function should initialize the state. If `step_state` is NULL when this function is +/// called, this function should allocate and initialize `step_state`. +/// `steps` is the number of steps to advance. This is always 1 or more, unless `progress` +/// is 0 or `step_state` is NULL, in which case `steps` is 0. +typedef void (*step_fn)(struct animatable *, unsigned int steps); + +/// The base type for step_state. +struct step_state_base { + /// The current value of the `animatable`. + /// If the `animatable` is not animated, this equals to `animatable->target`. + double current; +}; + +/// An animatable value +struct animatable { + /// The starting value. + /// When this `animatable` is not animated, this is the current value. + double start; + /// The target value. + /// If the `animatable` is not animated, this equals to `start`. + double target; + /// The animation duration in number of steps. + /// If the `animatable` is not animated, this is 0. + unsigned int duration; + /// The current progress of the animation. From 0 to `duration - 1`. + /// If the `animatable` is not animated, this is 0. + unsigned int progress; + + /// Step function state. + struct step_state_base *step_state; + /// The function for calculating the current value. If + /// `step_state` is not NULL, the `step` function is used; + /// otherwise, the `interpolator` function is used. + union { + /// The interpolator function. + interpolator_fn interpolator; + /// The step function. + step_fn step; + }; +}; + +// =============================== API =============================== + +/// Get the current value of an `animatable`. +double animatable_get(const struct animatable *a); +/// Get the animation progress as a percentage of the total duration. +double animatable_get_progress(const struct animatable *a); +/// Advance the animation by a given number of steps. +void animatable_step(struct animatable *a, unsigned int steps); +/// Returns whether an `animatable` is currently animating. +bool animatable_is_animating(const struct animatable *a); +/// 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); +/// 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); +/// Change the target value of an `animatable`. +/// If the `animatable` is already animating, the animation will be canceled first. +void animatable_set_target(struct animatable *a, double target, unsigned int duration); +/// Create a new animatable. +struct animatable animatable_new(double value, interpolator_fn interpolator, step_fn step); + +// ========================== Interpolators ========================== + +double linear_interpolator(const struct animatable *a);