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 }