2019-05-05 23:32:30 +00:00
|
|
|
package uix
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
2022-01-17 04:09:27 +00:00
|
|
|
"github.com/dop251/goja"
|
2019-05-05 23:32:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Animation holds a named animation for a doodad script.
|
|
|
|
type Animation struct {
|
|
|
|
Name string
|
|
|
|
Interval time.Duration
|
|
|
|
Layers []int
|
|
|
|
|
|
|
|
// runtime state variables
|
|
|
|
activeLayer int
|
|
|
|
nextFrameAt time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
TickAnimation advances an animation forward.
|
|
|
|
|
|
|
|
This method is called by canvas.Loop() only when the actor is currently
|
|
|
|
`animating` and their current animation's nextFrameAt has been reached by the
|
|
|
|
current time.Now().
|
|
|
|
|
|
|
|
Returns true when the animation has finished and false if there is still more
|
|
|
|
frames left to animate.
|
|
|
|
*/
|
|
|
|
func (a *Actor) TickAnimation(an *Animation) bool {
|
|
|
|
an.activeLayer++
|
|
|
|
if an.activeLayer < len(an.Layers) {
|
|
|
|
a.ShowLayer(an.Layers[an.activeLayer])
|
|
|
|
} else if an.activeLayer >= len(an.Layers) {
|
|
|
|
// final layer has been shown for 2 ticks, return that the animation has
|
|
|
|
// been concluded.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Schedule the next frame of animation.
|
|
|
|
an.nextFrameAt = time.Now().Add(an.Interval)
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddAnimation installs a new animation into the scripting engine for this actor.
|
|
|
|
//
|
|
|
|
// The layers can be an array of string names or integer indexes.
|
|
|
|
func (a *Actor) AddAnimation(name string, interval int64, layers []interface{}) error {
|
|
|
|
if len(layers) == 0 {
|
|
|
|
return errors.New("no named layers given to AddAnimation()")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find all the layers by name.
|
|
|
|
var indexes []int
|
|
|
|
for _, name := range layers {
|
|
|
|
switch v := name.(type) {
|
|
|
|
case string:
|
|
|
|
var found bool
|
2020-04-05 04:00:32 +00:00
|
|
|
for i, layer := range a.Doodad().Layers {
|
2019-05-05 23:32:30 +00:00
|
|
|
if layer.Name == v {
|
|
|
|
indexes = append(indexes, i)
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
return fmt.Errorf("layer named '%s' not found in doodad", v)
|
|
|
|
}
|
|
|
|
case int64:
|
|
|
|
// TODO: I want to find out if this is ever not an int64 coming from
|
|
|
|
// JavaScript.
|
|
|
|
if reflect.TypeOf(v).String() != "int64" {
|
|
|
|
log.Error("AddAnimation: expected an int64 from JavaScript but got a %s", reflect.TypeOf(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
iv := int(v)
|
2020-04-05 04:00:32 +00:00
|
|
|
if iv < len(a.Doodad().Layers) {
|
2019-05-05 23:32:30 +00:00
|
|
|
indexes = append(indexes, iv)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("layer numbered '%d' is out of bounds", iv)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf(
|
|
|
|
"invalid type for layer '%+v': should be a string (named layer) "+
|
|
|
|
"or int (indexed layer) but was a %s", v, reflect.TypeOf(name))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
a.animations[name] = &Animation{
|
|
|
|
Name: name,
|
|
|
|
Interval: time.Duration(interval) * time.Millisecond,
|
|
|
|
Layers: indexes,
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PlayAnimation starts an animation and then calls a JavaScript function when
|
|
|
|
// the last frame has played out. Set a null function to ignore the callback.
|
2022-01-17 04:09:27 +00:00
|
|
|
func (a *Actor) PlayAnimation(name string, callback goja.Value) error {
|
2019-05-05 23:32:30 +00:00
|
|
|
anim, ok := a.animations[name]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("animation named '%s' not found", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
a.activeAnimation = anim
|
|
|
|
a.animationCallback = callback
|
|
|
|
|
|
|
|
// Show the first layer.
|
|
|
|
anim.activeLayer = 0
|
|
|
|
anim.nextFrameAt = time.Now().Add(anim.Interval)
|
|
|
|
a.ShowLayer(anim.Layers[0])
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsAnimating returns if the current actor is playing an animation.
|
|
|
|
func (a *Actor) IsAnimating() bool {
|
|
|
|
return a.activeAnimation != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StopAnimation stops any current animations.
|
|
|
|
func (a *Actor) StopAnimation() {
|
|
|
|
if a.activeAnimation == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
a.activeAnimation = nil
|
2022-01-17 04:09:27 +00:00
|
|
|
a.animationCallback = goja.Null()
|
2019-05-05 23:32:30 +00:00
|
|
|
}
|