175 lines
4.3 KiB
Go
175 lines
4.3 KiB
Go
package animation
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
)
|
|
|
|
// Runner is the main driver for animations package
|
|
type Runner struct {
|
|
// animationMutex synchronizes access to `animations` and `pendingAnimations`
|
|
// between the runner goroutine and calls to Start and Stop
|
|
animationMutex sync.RWMutex
|
|
|
|
// animations is the list of animations that are being ticked in the current frame
|
|
animations []*anim
|
|
|
|
// pendingAnimations is animations that have been started but not yet picked up
|
|
// by the runner goroutine to be ticked each frame
|
|
pendingAnimations []*anim
|
|
|
|
// nextFrameAnimations is the list of animations that will be ticked in the next frame.
|
|
// It is accessed only by the runner goroutine and accumulates the continuing animations
|
|
// during a tick that are not completed, plus the pendingAnimations picked up at the end of the frame.
|
|
// At the end of a full frame of animations, the nextFrameAnimations slice is swapped with
|
|
// the current `animations` slice which is then cleared out, while holding the mutex.
|
|
nextFrameAnimations []*anim
|
|
|
|
runnerStarted bool
|
|
}
|
|
|
|
// Start will register the passed application and initiate its ticking.
|
|
func (r *Runner) Start(a *fyne.Animation) {
|
|
r.animationMutex.Lock()
|
|
defer r.animationMutex.Unlock()
|
|
|
|
if !r.runnerStarted {
|
|
r.runnerStarted = true
|
|
if r.animations == nil {
|
|
// initialize with excess capacity to avoid re-allocations
|
|
// on subsequent Starts
|
|
r.animations = make([]*anim, 0, 16)
|
|
}
|
|
r.animations = append(r.animations, newAnim(a))
|
|
} else {
|
|
if r.pendingAnimations == nil {
|
|
// initialize with excess capacity to avoid re-allocations
|
|
// on subsequent Starts
|
|
r.pendingAnimations = make([]*anim, 0, 16)
|
|
}
|
|
r.pendingAnimations = append(r.pendingAnimations, newAnim(a))
|
|
}
|
|
}
|
|
|
|
// Stop causes an animation to stop ticking (if it was still running) and removes it from the runner.
|
|
func (r *Runner) Stop(a *fyne.Animation) {
|
|
r.animationMutex.Lock()
|
|
defer r.animationMutex.Unlock()
|
|
|
|
newList := make([]*anim, 0, len(r.animations))
|
|
stopped := false
|
|
for _, item := range r.animations {
|
|
if item.a != a {
|
|
newList = append(newList, item)
|
|
} else {
|
|
item.setStopped()
|
|
stopped = true
|
|
}
|
|
}
|
|
r.animations = newList
|
|
if stopped {
|
|
return
|
|
}
|
|
|
|
newList = make([]*anim, 0, len(r.pendingAnimations))
|
|
for _, item := range r.pendingAnimations {
|
|
if item.a != a {
|
|
newList = append(newList, item)
|
|
} else {
|
|
item.setStopped()
|
|
}
|
|
}
|
|
r.pendingAnimations = newList
|
|
}
|
|
|
|
// TickAnimations progresses all running animations by one tick.
|
|
// This will be called from the driver to update objects immediately before next paint.
|
|
func (r *Runner) TickAnimations() {
|
|
if !r.runnerStarted {
|
|
return
|
|
}
|
|
|
|
done := r.runOneFrame()
|
|
|
|
if done {
|
|
r.animationMutex.Lock()
|
|
r.runnerStarted = false
|
|
r.animationMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
func (r *Runner) runOneFrame() (done bool) {
|
|
r.animationMutex.Lock()
|
|
oldList := r.animations
|
|
r.animationMutex.Unlock()
|
|
for _, a := range oldList {
|
|
if !a.isStopped() && r.tickAnimation(a) {
|
|
r.nextFrameAnimations = append(r.nextFrameAnimations, a)
|
|
}
|
|
}
|
|
|
|
r.animationMutex.Lock()
|
|
// nil out old r.animations for re-use as next r.nextFrameAnimations
|
|
tmp := r.animations
|
|
for i := range tmp {
|
|
tmp[i] = nil
|
|
}
|
|
r.animations = append(r.nextFrameAnimations, r.pendingAnimations...)
|
|
r.nextFrameAnimations = tmp[:0]
|
|
// nil out r.pendingAnimations
|
|
for i := range r.pendingAnimations {
|
|
r.pendingAnimations[i] = nil
|
|
}
|
|
r.pendingAnimations = r.pendingAnimations[:0]
|
|
done = len(r.animations) == 0
|
|
r.animationMutex.Unlock()
|
|
return done
|
|
}
|
|
|
|
// tickAnimation will process a frame of animation and return true if this should continue animating
|
|
func (r *Runner) tickAnimation(a *anim) bool {
|
|
if time.Now().After(a.end) {
|
|
if a.reverse {
|
|
a.a.Tick(0.0)
|
|
if a.repeatsLeft == 0 {
|
|
return false
|
|
}
|
|
a.reverse = false
|
|
} else {
|
|
a.a.Tick(1.0)
|
|
if a.a.AutoReverse {
|
|
a.reverse = true
|
|
}
|
|
}
|
|
if !a.reverse {
|
|
if a.repeatsLeft == 0 {
|
|
return false
|
|
}
|
|
if a.repeatsLeft > 0 {
|
|
a.repeatsLeft--
|
|
}
|
|
}
|
|
|
|
a.start = time.Now()
|
|
a.end = a.start.Add(a.a.Duration)
|
|
return true
|
|
}
|
|
|
|
delta := time.Since(a.start).Milliseconds()
|
|
|
|
val := float32(delta) / float32(a.total)
|
|
curve := a.a.Curve
|
|
if curve == nil {
|
|
curve = fyne.AnimationEaseInOut
|
|
}
|
|
if a.reverse {
|
|
a.a.Tick(curve(1 - val))
|
|
} else {
|
|
a.a.Tick(curve(val))
|
|
}
|
|
|
|
return true
|
|
}
|