gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/internal/fling/animation.go (about)

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