{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiWayIf, FlexibleContexts #-} module React.Anim where import Control.Applicative import Data.IORef import Data.Monoid import Haste import Lens.Family2 import React.Imports import React.Types -- TODO support delays -- TODO look at velocity -- TODO also `Floating (Scalar v)` ? -- Double ~ Scalar v? -- TODO common pattern: -- from .+^ (?? *^ (to .-. from)) {- easingFunc :: (AffineSpace p, v ~ Diff p, VectorSpace v) => Easing -> a -> a -> Double -> a easingFunc Linear from to t = from .+^ (t *^ (to .-. from)) -- easingFunc Linear from to t = alerp from to t easingFunc EaseInQuad from to t = from .+^ ((t*t) *^ (to .-. from)) easingFunc _ _ _ _ = error "that easing function has not been defined yet" -} instance Animatable Double where interpolate ease from to t = if | t <= 0 -> from | t >= 1 -> to | otherwise -> from + easeDouble ease t * (to - from) animAdd = (+) animSub = (-) animZero = 0 -- I think this could become Functor if we limit `to` to `animZero` -- instance (Applicative f, Animatable a) => Animatable (f a) where -- interpolate ease from to t = interpolate ease <$> from <*> to <*> pure t -- animAdd = liftA2 animAdd -- animZero = pure animZero -- TODO use generics for all tuple instances instance Animatable () where interpolate _ _ _ _ = () animAdd _ _ = () animSub _ _ = () animZero = () instance (Animatable a, Animatable b) => Animatable (a, b) where interpolate ease (x0, y0) (x1, y1) t = (interpolate ease x0 x1 t, interpolate ease y0 y1 t) animAdd (x0, y0) (x1, y1) = (x0 `animAdd` x1, y0 `animAdd` y1) animSub (x0, y0) (x1, y1) = (x0 `animSub` x1, y0 `animSub` y1) animZero = (animZero, animZero) instance (Animatable a, Animatable b, Animatable c) => Animatable (a, b, c) where interpolate ease (x0, y0, z0) (x1, y1, z1) t = (interpolate ease x0 x1 t, interpolate ease y0 y1 t, interpolate ease z0 z1 t) animAdd (x0, y0, z0) (x1, y1, z1) = (x0 `animAdd` x1, y0 `animAdd` y1, z0 `animAdd` z1) animSub (x0, y0, z0) (x1, y1, z1) = (x0 `animSub` x1, y0 `animSub` y1, z0 `animSub` z1) animZero = (animZero, animZero, animZero) -- TODO use color package -- | 24-bit colors which can be interpolated. data Color = Color Int Int Int instance Animatable Color where interpolate ease c1@(Color r0 g0 b0) c2@(Color r1 g1 b1) t = let t' = interpolate ease 0 1 t in Color (intLerp r0 r1 t') (intLerp g0 g1 t') (intLerp b0 b1 t') animAdd (Color r0 g0 b0) (Color r1 g1 b1) = Color (r0 + r1) (g0 + g1) (b0 + b1) animSub (Color r0 g0 b0) (Color r1 g1 b1) = Color (r0 - r1) (g0 - g1) (b0 - b1) animZero = (Color 0 0 0) instance Show Color where show (Color r g b) = "rgb" ++ show (r, g, b) easeInPow :: Int -> Double -> Double easeInPow pow t = t ^^ pow easeOutPow :: Int -> Double -> Double easeOutPow pow t = 1 - easeInPow pow (1 - t) easeInOutPow :: Int -> Double -> Double easeInOutPow pow t = if t < 0.5 then easeInPow pow (t * 2) / 2 else 1 - easeInPow pow ((1 - t) * 2) / 2 elastic :: Double -> Double elastic t = let p = 0.3 powFactor = 2 ** (-10 * t) sinFactor = sin $ (t - p / 4) * (2 * pi / p) in powFactor * sinFactor + 1 easeDouble :: Easing -> Double -> Double easeDouble Linear t = t easeDouble EaseInQuad t = easeInPow 2 t easeDouble EaseOutQuad t = easeOutPow 2 t easeDouble EaseInOutQuad t = easeInOutPow 2 t easeDouble EaseInCubic t = easeInPow 3 t easeDouble EaseOutCubic t = easeOutPow 3 t easeDouble EaseInOutCubic t = easeInOutPow 3 t easeDouble EaseInQuart t = easeInPow 4 t easeDouble EaseOutQuart t = easeOutPow 4 t easeDouble EaseInOutQuart t = easeInOutPow 4 t easeDouble EaseInQuint t = easeInPow 5 t easeDouble EaseOutQuint t = easeOutPow 5 t easeDouble EaseInOutQuint t = easeInOutPow 5 t easeDouble EaseInBounce t = easeDouble EaseOutBounce (1 - t) easeDouble EaseOutBounce t = let c = 7.5625 in if | t < (1 / 2.75) -> c * t * t | t < (2 / 2.75) -> let t' = t - (1.5 / 2.75) in c * t' * t' + 0.75 | t < (2.5 / 2.75) -> let t' = t - (2.25 / 2.75) in c * t' * t' + 0.9375 | otherwise -> let t' = t - (2.625 / 2.75) in c * t' * t' + 0.984375 -- TODO fix easeDouble EaseInOutBounce t = if t < 0.5 then easeDouble EaseInBounce (t * 2) / 2 else 1 - easeDouble EaseOutBounce ((1 - t) * 2) / 2 easeDouble EaseInElastic t = 1 - elastic (1 - t) easeDouble EaseOutElastic t = elastic t -- TODO fix easeDouble EaseInOutElastic t = if t < 0.5 then elastic (t * 2) / 2 else 1 - elastic ((1 - t) * 2) / 2 easeDouble (EaseBezier x0 y0 x1 y1) t = js_bezier x0 y0 x1 y1 t -- some magic numbers i found on the internet easeDouble EaseInSine t = js_bezier 0.47 0 0.745 0.715 t easeDouble EaseOutSine t = js_bezier 0.39 0.575 0.565 1 t getAnimationState :: Monad m => ReactT ty m (AnimationState ty) getAnimationState = ReactT $ \anim -> return ([], anim) stepRunningAnims :: AnimationState ty -> [(RunningAnim ty, Double)] -> AnimationState ty stepRunningAnims anim running = let start = foldr ( \(RunningAnim AnimConfig{lens=lens} _, _) anim' -> anim' & lens .~ animZero ) anim running in foldr ( \(RunningAnim (AnimConfig _ (from, to) lens easing _) _, progress) anim' -> anim' & lens %~ (`animAdd` interpolate easing from to progress) ) start running lerp :: Double -> RunningAnim ty -> Double lerp time (RunningAnim (AnimConfig duration _ _ _ _) begin) = (time - begin) / duration intLerp :: Int -> Int -> Double -> Int intLerp a b t = floor $ (fromIntegral a) + (fromIntegral $ b - a) * t