239 lines
5.7 KiB
Odin
239 lines
5.7 KiB
Odin
package tween
|
|
|
|
import "base:intrinsics"
|
|
import "core:math/ease"
|
|
import "core:time"
|
|
|
|
TweenContext :: struct($T: typeid) {
|
|
tweens: [dynamic]Tween(T),
|
|
}
|
|
|
|
Tween :: struct($T: typeid) {
|
|
value: ^T,
|
|
start: T,
|
|
diff: T,
|
|
goal: T,
|
|
delay: f64, // in seconds
|
|
duration: time.Duration,
|
|
progress: f64,
|
|
rate: f64,
|
|
type: ease.Ease,
|
|
inited: bool,
|
|
id: int,
|
|
|
|
// callbacks, data can be set, will be pushed to callback
|
|
data: rawptr, // by default gets set to value input
|
|
on_start: proc(ctx: ^TweenContext(T), data: rawptr),
|
|
on_update: proc(ctx: ^TweenContext(T), data: rawptr),
|
|
on_complete: proc(ctx: ^TweenContext(T), data: rawptr),
|
|
}
|
|
|
|
@(require_results)
|
|
context_init_f :: proc(
|
|
$T: typeid,
|
|
) -> TweenContext(T) where intrinsics.type_is_float(T) {
|
|
return {tweens = make([dynamic]Tween(T))}
|
|
}
|
|
|
|
@(require_results)
|
|
context_init_v :: proc(
|
|
$T: typeid/[$N]$E,
|
|
) -> TweenContext(T) where intrinsics.type_is_float(E) {
|
|
return {tweens = make([dynamic]Tween(T))}
|
|
}
|
|
|
|
context_init :: proc {
|
|
context_init_f,
|
|
context_init_v,
|
|
}
|
|
|
|
// delete map content
|
|
context_destroy :: proc(ctx: TweenContext($T)) {
|
|
delete(ctx.tweens)
|
|
}
|
|
|
|
// clear map content, stops all animations
|
|
context_clear :: proc(ctx: ^TweenContext($T)) {
|
|
clear(&ctx.tweens)
|
|
}
|
|
|
|
// append / overwrite existing tween value to parameters
|
|
// rest is initialized in tween_init, inside update
|
|
// return value can be used to set callbacks
|
|
@(require_results)
|
|
tween_to :: proc(
|
|
ctx: ^TweenContext($T),
|
|
value: ^T,
|
|
goal: T,
|
|
type: ease.Ease = .Quadratic_Out,
|
|
duration: time.Duration = time.Second,
|
|
delay: f64 = 0,
|
|
id: int = 0,
|
|
) -> (
|
|
tween: ^Tween(T),
|
|
) {
|
|
append(&ctx.tweens, Tween(T){})
|
|
n := len(ctx.tweens) - 1
|
|
tween = &ctx.tweens[n]
|
|
|
|
tween^ = {
|
|
value = value,
|
|
goal = goal,
|
|
duration = duration,
|
|
delay = delay,
|
|
type = type,
|
|
data = value,
|
|
id = id,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// init internal properties
|
|
_tween_init :: proc(tween: ^Tween($T), duration: time.Duration) {
|
|
tween.inited = true
|
|
tween.start = tween.value^
|
|
tween.diff = tween.goal - tween.value^
|
|
s := time.duration_seconds(duration)
|
|
tween.rate = duration > 0 ? 1.0 / s : 0
|
|
tween.progress = duration > 0 ? 0 : 1
|
|
}
|
|
|
|
_tween_init_f :: proc(
|
|
tween: ^Tween($T),
|
|
duration: time.Duration,
|
|
) where intrinsics.type_is_float(T) {
|
|
_tween_init(tween, duration)
|
|
}
|
|
|
|
_tween_init_v :: proc(
|
|
tween: ^Tween($T/[$N]$E),
|
|
duration: time.Duration,
|
|
) where intrinsics.type_is_float(E) {
|
|
_tween_init(tween, duration)
|
|
}
|
|
|
|
tween_init :: proc {
|
|
_tween_init_f,
|
|
_tween_init_v,
|
|
}
|
|
|
|
_tween_interpolate :: proc(tween: ^Tween($T), dt, delay_remainder: f64) {
|
|
tween.progress += tween.rate * (dt + delay_remainder)
|
|
x := tween.progress >= 1 ? 1 : ease.ease(tween.type, tween.progress)
|
|
tween.value^ = tween.start + tween.diff * T(x)
|
|
}
|
|
|
|
tween_interpolate_f :: proc(
|
|
tween: ^Tween($T),
|
|
dt, delay_remainder: f64,
|
|
) where intrinsics.type_is_float(T) {
|
|
_tween_interpolate(tween, dt, delay_remainder)
|
|
}
|
|
|
|
tween_interpolate_v :: proc(
|
|
tween: ^Tween($T/[$N]$E),
|
|
dt, delay_remainder: f64,
|
|
) where intrinsics.type_is_float(E) {
|
|
_tween_interpolate(tween, dt, delay_remainder)
|
|
}
|
|
|
|
tween_interpolate :: proc {
|
|
tween_interpolate_f,
|
|
tween_interpolate_v,
|
|
}
|
|
|
|
// update all tweens, wait for their delay if one exists
|
|
// calls callbacks in all stages, when they're filled
|
|
// deletes tween from the map after completion
|
|
_tween_update :: proc(ctx: ^TweenContext($T), dt: f64) {
|
|
for &tween in ctx.tweens {
|
|
delay_remainder := f64(0)
|
|
|
|
// Update delay if necessary.
|
|
if tween.delay > 0 {
|
|
tween.delay -= dt
|
|
|
|
if tween.delay < 0 {
|
|
// We finished the delay, but in doing so consumed part of this frame's `dt` budget.
|
|
// Keep track of it so we can apply it to this tween without affecting others.
|
|
delay_remainder = tween.delay
|
|
// We're done with this delay.
|
|
tween.delay = 0
|
|
}
|
|
}
|
|
|
|
// We either had no delay, or the delay has been consumed.
|
|
if tween.delay <= 0 {
|
|
if !tween.inited {
|
|
tween_init(&tween, tween.duration)
|
|
|
|
if tween.on_start != nil {
|
|
tween.on_start(ctx, tween.data)
|
|
}
|
|
}
|
|
|
|
// If part of the `dt` budget was consumed this frame, then `delay_remainder` will be
|
|
// that remainder, a negative value. Adding it to `dt` applies what's left of the `dt`
|
|
// to the tween so it advances properly, instead of too much or little.
|
|
tween_interpolate(&tween, dt, delay_remainder)
|
|
// tween.progress += tween.rate * (dt + delay_remainder)
|
|
// x := tween.progress >= 1 ? 1 : ease(tween.type, tween.progress)
|
|
// tween.value^ = tween.start + tween.diff * T(x)
|
|
|
|
if tween.on_update != nil {
|
|
tween.on_update(ctx, tween.data)
|
|
}
|
|
|
|
if tween.progress >= 1 {
|
|
// append keys to array that will be deleted after the loop
|
|
|
|
if tween.on_complete != nil {
|
|
tween.on_complete(ctx, tween.data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tween_update_f :: proc(ctx: ^TweenContext($T), dt: f64) where intrinsics.type_is_float(T) {
|
|
_tween_update(ctx, dt)
|
|
}
|
|
|
|
tween_update_v :: proc(ctx: ^TweenContext($T/[$N]$E), dt: f64) where intrinsics.type_is_float(E) {
|
|
_tween_update(ctx, dt)
|
|
}
|
|
|
|
tween_update :: proc {
|
|
tween_update_v,
|
|
tween_update_f,
|
|
}
|
|
|
|
// stop a specific key inside the map
|
|
// returns true when it successfully removed the key
|
|
// @(require_results)
|
|
tween_stop :: proc(ctx: ^TweenContext($T), id: int) -> bool {
|
|
removed_tweens := false
|
|
for i := 0; i < len(ctx.tweens); {
|
|
if tween.id == id {
|
|
unordered_remove(&ctx.tweens, i)
|
|
removed_tweens = true
|
|
} else {
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Returns the first tween animation with that id
|
|
// If no tween exists with the given id, will return 0
|
|
@(require_results)
|
|
tween_time_left :: proc(flux: TweenContext($T), id: int) -> f64 {
|
|
for tween in ctx.tweens {
|
|
if tween.id == id {
|
|
return ((1 - tween.progress) * tween.rate) + tween.delay
|
|
}
|
|
}
|
|
return 0
|
|
}
|