animation.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package fling
   4  
   5  import (
   6  	"math"
   7  	"runtime"
   8  	"time"
   9  
  10  	"github.com/p9c/p9/pkg/gel/gio/unit"
  11  )
  12  
  13  type Animation struct {
  14  	// Current offset in pixels.
  15  	x float32
  16  	// Initial time.
  17  	t0 time.Time
  18  	// Initial velocity in pixels pr second.
  19  	v0 float32
  20  }
  21  
  22  var (
  23  	// Pixels/second.
  24  	minFlingVelocity = unit.Dp(50)
  25  	maxFlingVelocity = unit.Dp(8000)
  26  )
  27  
  28  const (
  29  	thresholdVelocity = 1
  30  )
  31  
  32  // Start a fling given a starting velocity. Returns whether a
  33  // fling was started.
  34  func (f *Animation) Start(c unit.Metric, now time.Time, velocity float32) bool {
  35  	min := float32(c.Px(minFlingVelocity))
  36  	v := velocity
  37  	if -min <= v && v <= min {
  38  		return false
  39  	}
  40  	max := float32(c.Px(maxFlingVelocity))
  41  	if v > max {
  42  		v = max
  43  	} else if v < -max {
  44  		v = -max
  45  	}
  46  	f.init(now, v)
  47  	return true
  48  }
  49  
  50  func (f *Animation) init(now time.Time, v0 float32) {
  51  	f.t0 = now
  52  	f.v0 = v0
  53  	f.x = 0
  54  }
  55  
  56  func (f *Animation) Active() bool {
  57  	return f.v0 != 0
  58  }
  59  
  60  // Tick computes and returns a fling distance since
  61  // the last time Tick was called.
  62  func (f *Animation) Tick(now time.Time) int {
  63  	if !f.Active() {
  64  		return 0
  65  	}
  66  	var k float32
  67  	if runtime.GOOS == "darwin" {
  68  		k = -2 // iOS
  69  	} else {
  70  		k = -4.2 // Android and default
  71  	}
  72  	t := now.Sub(f.t0)
  73  	// The acceleration x''(t) of a point mass with a drag
  74  	// force, f, proportional with velocity, x'(t), is
  75  	// governed by the equation
  76  	//
  77  	// x''(t) = kx'(t)
  78  	//
  79  	// Given the starting position x(0) = 0, the starting
  80  	// velocity x'(0) = v0, the position is then
  81  	// given by
  82  	//
  83  	// x(t) = v0*e^(k*t)/k - v0/k
  84  	//
  85  	ekt := float32(math.Exp(float64(k) * t.Seconds()))
  86  	x := f.v0*ekt/k - f.v0/k
  87  	dist := x - f.x
  88  	idist := int(dist)
  89  	f.x += float32(idist)
  90  	// Solving for the velocity x'(t) gives us
  91  	//
  92  	// x'(t) = v0*e^(k*t)
  93  	v := f.v0 * ekt
  94  	if -thresholdVelocity < v && v < thresholdVelocity {
  95  		f.v0 = 0
  96  	}
  97  	return idist
  98  }
  99