{-# LANGUAGE DefaultSignatures, TypeOperators, FlexibleContexts, FlexibleInstances #-} {-| Contains all data structures for describing transitions, composing and animating them. -} module FRP.Helm.Transition ( -- * Types Transition, TransitionStatus(..), Interpolate(..), -- * Creating waypoint, startWith, fromList, -- * Transitions transition, length ) where import Control.Applicative import FRP.Elerea.Simple import FRP.Helm.Color (Color) import FRP.Helm.Time (Time, inSeconds) import Data.List (find) import Prelude hiding (length) import Data.Maybe (fromJust) import GHC.Float import Data.Word import Data.Int import GHC.Generics import Control.Monad.Writer.Lazy import Control.Monad.State.Lazy {-| A type describing a combosable transition. The writer keeps record of all the frames in the transition. The state holds the current value of the transition. This allows you to easily compose transitions using do notation. -} type Transition a = StateT a (Writer [(a, Time)]) {-| This is used only for easier search of frames when transitioning is in progress. -} data InternalFrame a = InternalFrame { -- | The initial value in the transition. s :: a, -- | The final value in the transition. e :: a, -- | The time that the transition will take. t :: Double, -- | The transition-relative time of the beginning of this frame. tend :: Double, -- | The transition-relative time of the end of this frame. tstart :: Double } deriving Show type InternalTransition a = [InternalFrame a] {-| A variety of statuses that can be used to control a transition. -} data TransitionStatus -- | The transition will repeat forever. = Cycle -- | The transition will be paused and won't changed until resumed. | Pause -- | The transition is cycled once and then stops. | Once -- | The transition will reset to a certain point in time. | Set Time {-| Adds a value to the transition monad that will be the next point in the transition. -} waypoint :: Interpolate a => a -> Time -> Transition a a waypoint a t = do tell [(a, t)] put a return a {-| Interpolates between the beginning and the end of the given frame. -} transFrame :: Interpolate a => InternalFrame a -> Time -> a transFrame InternalFrame{..} time = interpolate progress s e where progress = time / (tend - tstart) {-| Searches the frame active at the given time and gives back the value of the frame at that time. -} transitionAt :: Interpolate a => InternalTransition a -> Time -> a transitionAt pks timeUnsafe = transFrame currentTransition currentTime where currentTime = time - tstart currentTransition currentTransition = fromJust $ find (\InternalFrame { .. } -> tend >= time) pks time = cycleTime pks timeUnsafe {-| Turns the internal representation of a transition into a signal. The provided time signal acts as the inner clock of the transition. The status signal can be used to control the transition, deciding whether the transition should cycle, go to a specific time, pause, stop or run once. -} transition :: Interpolate a => SignalGen (Signal Time) -> SignalGen (Signal TransitionStatus) -> InternalTransition a -> SignalGen (Signal a) transition _ _ [] = error "empty transitions don't have any default value" transition dtGen statusGen trans = do dt <- dtGen status <- statusGen time <- transfer2 0 step' status $ inSeconds <$> dt return $ transitionAt trans <$> time where step' Cycle dt t = cycleTime trans (dt + t) step' Pause _ t = t step' Once dt t = if newT < length trans then newT else length trans where newT = dt + t step' (Set t) _ _ = inSeconds t {-| Converts a list of tuples describing a waypoint value and time into a transition. The first element in the list is the starting value and time of the transition. > color = transition (constant $ Time.fps 60) (constant Cycle) $ fromList [(white, 0), (green, 2 * second), (red, 5 * second), (black, 1 * second), (yellow, 2 * second)] -} fromList :: Interpolate a => [(a,Time)] -> InternalTransition a fromList [] = error "empty transitions don't have any default value" fromList ((v1, d1) : xs) = scanl (\InternalFrame { .. } (v, d) -> InternalFrame e v d (tend + d) tend) first xs where first = InternalFrame v1 v1 d1 d1 0 {-| Starts a transition with an initial value. > color = transition (constant $ Time.fps 60) (constant Cycle) $ startWith white $ do > waypoint green (2 * second) > waypoint red (5 * second) > waypoint black (1 * second) > waypoint yellow (2 * second) -} startWith :: Interpolate a => a -> Transition a b -> InternalTransition a startWith beginning transitionMonad = fromList $ snd $ runWriter $ evalStateT (tell [(beginning, 0)] >> transitionMonad) beginning {-| Given an animation, a function is created which loops the time of the animation to always be in the animations length boundary. -} cycleTime :: InternalTransition a -> Time -> Time cycleTime [] = const 0 cycleTime anim = cycleTime' (length anim) {-| Helper function which makes a timer loop through an time interval. -} cycleTime' :: Time -> Time -> Time cycleTime' l t | t > l = cycleTime' l (t-l) | t < 0 = cycleTime' l (l+t) | otherwise = t {-| How long it takes for the provided transition to end. -} length :: InternalTransition a -> Double length = tend . last {-| Defines a value that can be interpolated. An example instance of this class follows: > data YourDataType = YourDataConstructor SomeInterpolableType SomeOtherInterpolableType deriving Generic > > instance Interpolate YourDataType > interpolate 0.5 (YourDataConstructor 3 5) (YourDataConstructor 5 7) == YourDataConstructor 4 6 -} class Interpolate a where interpolate :: Double -> a -> a -> a default interpolate :: (Generic a, GInterpolate (Rep a)) => Double -> a -> a -> a interpolate d a b = to $ ginterpolate d (from a) (from b) class GInterpolate a where ginterpolate :: Double -> a b -> a b -> a b instance GInterpolate V1 where ginterpolate _ _ b = b instance GInterpolate U1 where ginterpolate _ _ b = b instance (GInterpolate a, GInterpolate b) => GInterpolate (a :*: b) where ginterpolate d (a1 :*: b1) (a2 :*: b2) = ginterpolate d a1 a2 :*: ginterpolate d b1 b2 instance (GInterpolate a, GInterpolate b) => GInterpolate (a :+: b) where ginterpolate d (L1 a) (L1 b) = L1 $ ginterpolate d a b ginterpolate d (R1 a) (R1 b) = R1 $ ginterpolate d a b ginterpolate _ (L1 _) (R1 b) = R1 b ginterpolate _ (R1 _) (L1 b) = L1 b instance (GInterpolate a) => GInterpolate (M1 i c a) where ginterpolate d (M1 a) (M1 b) = M1 $ ginterpolate d a b instance (Interpolate a) => GInterpolate (K1 i a) where ginterpolate d (K1 a) (K1 b) = K1 $ interpolate d a b instance Interpolate Double where interpolate p a b = b * p + a * (1-p) instance Interpolate Float where interpolate p a b = b * double2Float p + a * double2Float (1 - p) instance Interpolate Char where interpolate _ _ b = b integralInterpolate :: Integral a => Double -> a -> a -> a integralInterpolate d a b = ceiling $ interpolate d (fromIntegral a :: Double) (fromIntegral b :: Double) instance Interpolate Word where interpolate = integralInterpolate instance Interpolate Word8 where interpolate = integralInterpolate instance Interpolate Word16 where interpolate = integralInterpolate instance Interpolate Word32 where interpolate = integralInterpolate instance Interpolate Word64 where interpolate = integralInterpolate instance Interpolate Int where interpolate = integralInterpolate instance Interpolate Int8 where interpolate = integralInterpolate instance Interpolate Int16 where interpolate = integralInterpolate instance Interpolate Int32 where interpolate = integralInterpolate instance Interpolate Int64 where interpolate = integralInterpolate instance Interpolate Integer where interpolate = integralInterpolate instance Interpolate Bool instance Interpolate (Double, Double) instance Interpolate Color