summaryrefslogtreecommitdiff
path: root/Animator.hs
blob: fb0e3f67b8ff39cf9746eb273561ee57f41dea11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
module Animator where

import Control.Monad
import Data.Int
import qualified Data.IntMap as IntMap
         ;import Data.IntMap (IntMap)
import Data.IORef
import Data.Word
import GI.Gtk as Gtk
import GI.Gdk.Objects
import GI.GLib.Constants (pattern SOURCE_CONTINUE,pattern SOURCE_REMOVE)


-- | Type alias to mark a value returned by 'widgetAddTickCallback'.
type TickCallbackHandle = Word32

data Animator = Animator
    { tmSeconds          :: IORef Double
    , tmFirstFrame       :: IORef Int64
    , tmCallback         :: IORef (Maybe TickCallbackHandle)
    , tmAnimations       :: IORef (IntMap Animation)
    , tmAnimationCounter :: IORef Int
    , tmWidget           :: Widget
    }

-- | An animation is an action that updates some state based on elapsing time.
-- The first argument is the elapsed seconds since the prior call (or zero the
-- first time) and the second argument is the current seconds elapsed since the
-- first tick.
--
-- When the animation is done, Nothing is returned.  Otherwise itself or a
-- replacement action is returned to be invoked at the next frame.
newtype Animation = Animation (Double -> Double -> IO (Maybe Animation))

newAnimator :: Widget -> IO Animator
newAnimator w = do
    s <- newIORef 0.0
    ff <- newIORef 0
    cb <- newIORef Nothing
    as <- newIORef IntMap.empty
    cnt <- newIORef 0
    return $ Animator s ff cb as cnt w

getSeconds :: Animator -> IO Double
getSeconds tk = readIORef (tmSeconds tk)

addAnimation :: Animator -> Animation -> IO Int
addAnimation tm action = do
    k <- atomicModifyIORef' (tmAnimationCounter tm) $ \x -> (succ x, x)
    m <- atomicModifyIORef' (tmAnimations tm) $ \m -> (IntMap.insert k action m,m)
    when (IntMap.null m) $ do
        cb <- widgetAddTickCallback (tmWidget tm) (tick tm)
        writeIORef (tmCallback tm) (Just cb)
    return k


removeAnimation :: Animator -> Int -> IO ()
removeAnimation tm k = do
    m <- atomicModifyIORef' (tmAnimations tm) $ \m -> (IntMap.delete k m, m)
    when (IntMap.null $ IntMap.delete k m) $ do
        mcb <- readIORef (tmCallback tm)
        mapM_ (widgetRemoveTickCallback $ tmWidget tm) mcb
        writeIORef (tmCallback tm) Nothing

tick :: Animator -> Widget -> FrameClock -> IO Bool
tick tm widget clock = widgetGetWindow widget >>= \case
    Nothing  -> do writeIORef (tmCallback tm) Nothing
                   return SOURCE_REMOVE
    Just win -> do
        windowInvalidateRect win Nothing False
        micros <- frameClockGetFrameTime clock
        ff <- atomicModifyIORef' (tmFirstFrame tm) $ \prev ->
            if prev == 0 then (micros, micros)
                         else (prev, prev)
        (prior,secs) <- atomicModifyIORef' (tmSeconds tm) $ \prior -> do
            let secs = fromIntegral (micros - ff) / 1000000.0
             in (secs,(prior,secs))
        let delta | prior==0  = 0
                  | otherwise = secs - prior

        as <- readIORef (tmAnimations tm)
        as' <- mapM (\(Animation f) -> f delta secs) as
        let as'' = IntMap.mapMaybe id as'
        writeIORef (tmAnimations tm) as''
        if IntMap.null as''
            then do writeIORef (tmCallback tm) Nothing
                    return SOURCE_REMOVE
            else return SOURCE_CONTINUE