diff options
author | Joe Crayne <joe@jerkface.net> | 2019-04-29 20:01:45 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2019-04-29 20:01:45 -0400 |
commit | ccb333bf33588dc578380b60c40b8bc963b84f42 (patch) | |
tree | 30b035eac56520747e85f325555d081c30addcb6 | |
parent | d04c6fb9334493b9afa23bc7df2cddbae7fd4903 (diff) |
Animator utility.
-rw-r--r-- | Animator.hs | 82 |
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 #-} | ||
3 | module Animator where | ||
4 | |||
5 | import Control.Monad | ||
6 | import Data.Int | ||
7 | import qualified Data.IntMap as IntMap | ||
8 | ;import Data.IntMap (IntMap) | ||
9 | import Data.IORef | ||
10 | import Data.Word | ||
11 | import GI.Gtk as Gtk | ||
12 | import GI.Gdk.Objects | ||
13 | import GI.GLib.Constants (pattern SOURCE_CONTINUE,pattern SOURCE_REMOVE) | ||
14 | |||
15 | |||
16 | -- | Type alias to mark a value returned by 'widgetAddTickCallback'. | ||
17 | type TickCallbackHandle = Word32 | ||
18 | |||
19 | data 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. | ||
35 | newtype Animation = Animation (Double -> Double -> IO (Maybe Animation)) | ||
36 | |||
37 | newAnimator :: Widget -> IO Animator | ||
38 | newAnimator 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 | |||
46 | getSeconds :: Animator -> IO Double | ||
47 | getSeconds tk = readIORef (tmSeconds tk) | ||
48 | |||
49 | addAnimation :: Animator -> Animation -> IO Int | ||
50 | addAnimation 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 | |||
58 | tick :: Animator -> Widget -> FrameClock -> IO Bool | ||
59 | tick 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 | |||