summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-04-29 20:01:45 -0400
committerJoe Crayne <joe@jerkface.net>2019-04-29 20:01:45 -0400
commitccb333bf33588dc578380b60c40b8bc963b84f42 (patch)
tree30b035eac56520747e85f325555d081c30addcb6
parentd04c6fb9334493b9afa23bc7df2cddbae7fd4903 (diff)
Animator utility.
-rw-r--r--Animator.hs82
1 files changed, 82 insertions, 0 deletions
diff --git a/Animator.hs b/Animator.hs
new file mode 100644
index 0000000..e51fc50
--- /dev/null
+++ b/Animator.hs
@@ -0,0 +1,82 @@
1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE PatternSynonyms #-}
3module Animator where
4
5import Control.Monad
6import Data.Int
7import qualified Data.IntMap as IntMap
8 ;import Data.IntMap (IntMap)
9import Data.IORef
10import Data.Word
11import GI.Gtk as Gtk
12import GI.Gdk.Objects
13import GI.GLib.Constants (pattern SOURCE_CONTINUE,pattern SOURCE_REMOVE)
14
15
16-- | Type alias to mark a value returned by 'widgetAddTickCallback'.
17type TickCallbackHandle = Word32
18
19data Animator = Animator
20 { tmSeconds :: IORef Double
21 , tmFirstFrame :: IORef Int64
22 , tmCallback :: IORef (Maybe TickCallbackHandle)
23 , tmAnimations :: IORef (IntMap Animation)
24 , tmAnimationCounter :: IORef Int
25 , tmWidget :: Widget
26 }
27
28-- | An animation is an action that updates some state based on elapsing time.
29-- The first argument is the elapsed seconds since the prior call (or zero the
30-- first time) and the second argument is the current seconds elapsed since the
31-- first tick.
32--
33-- When the animation is done, Nothing is returned. Otherwise itself or a
34-- replacement action is returned to be invoked at the next frame.
35newtype Animation = Animation (Double -> Double -> IO (Maybe Animation))
36
37newAnimator :: Widget -> IO Animator
38newAnimator w = do
39 s <- newIORef 0.0
40 ff <- newIORef 0
41 cb <- newIORef Nothing
42 as <- newIORef IntMap.empty
43 cnt <- newIORef 0
44 return $ Animator s ff cb as cnt w
45
46getSeconds :: Animator -> IO Double
47getSeconds tk = readIORef (tmSeconds tk)
48
49addAnimation :: Animator -> Animation -> IO Int
50addAnimation tm action = do
51 k <- atomicModifyIORef' (tmAnimationCounter tm) $ \x -> (succ x, x)
52 m <- atomicModifyIORef' (tmAnimations tm) $ \m -> (IntMap.insert k action m,m)
53 when (IntMap.null m) $ do
54 cb <- widgetAddTickCallback (tmWidget tm) (tick tm)
55 writeIORef (tmCallback tm) (Just cb)
56 return k
57
58tick :: Animator -> Widget -> FrameClock -> IO Bool
59tick tm widget clock = widgetGetWindow widget >>= \case
60 Nothing -> do writeIORef (tmCallback tm) Nothing
61 return SOURCE_REMOVE
62 Just win -> do
63 windowInvalidateRect win Nothing False
64 micros <- frameClockGetFrameTime clock
65 ff <- atomicModifyIORef' (tmFirstFrame tm) $ \prev ->
66 if prev == 0 then (micros, micros)
67 else (prev, prev)
68 (prior,secs) <- atomicModifyIORef' (tmSeconds tm) $ \prior -> do
69 let secs = fromIntegral (micros - ff) / 1000000.0
70 in (secs,(prior,secs))
71 let delta | prior==0 = 0
72 | otherwise = secs - prior
73
74 as <- readIORef (tmAnimations tm)
75 as' <- mapM (\(Animation f) -> f delta secs) as
76 let as'' = IntMap.mapMaybe id as'
77 writeIORef (tmAnimations tm) as''
78 if IntMap.null as''
79 then do writeIORef (tmCallback tm) Nothing
80 return SOURCE_REMOVE
81 else return SOURCE_CONTINUE
82