animate-preview-0.1.2: library/Animate/Preview/Scene.hs
module Animate.Preview.Scene where
import qualified Animate
import qualified Data.Vector as V
import Control.Concurrent (readMVar, modifyMVar_)
import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Reader (asks)
import Control.Monad.State (modify, gets)
import Data.Text.Conversions (toText, fromText)
import KeyState
import Linear
import Animate.Preview.Animation
import Animate.Preview.Config
import Animate.Preview.Renderer
import Animate.Preview.Input
import Animate.Preview.State
import Animate.Preview.ManagerInput
import Animate.Preview.Color
import Animate.Preview.Logger
import Animate.Preview.Scalar
import Animate.Preview.Mode
import Animate.Preview.Loader
class Monad m => Scene m where
sceneStep :: m ()
default sceneStep :: (R m, S m, Renderer m, HasInput m, Logger m, Loader m, MonadIO m) => m ()
sceneStep = do
updateMode
updateReload
toggleVisuals
updateOrigin
updateKeyFrame
updateSpeed
updateScale
updateInfo
updateAnimation
drawScene
updateKeyFrame :: (R m, HasInput m, MonadIO m) => m ()
updateKeyFrame = do
input <- getInput
loaded' <- getLoaded
case loaded' of
Nothing -> return ()
Just loaded -> do
let animations = (Animate.ssAnimations . lSpriteSheet) loaded
when (isPressed $ iNextKeyFrame input) $ modifyCurrent' $ \c -> let
key = unsafeNextKey (Animate.pKey $ cPos c) animations
keyName = lIntToText loaded key
in Current (Animate.initPosition key) keyName
when (isPressed $ iPrevKeyFrame input) $ modifyCurrent' $ \c -> let
key = unsafePrevKey (Animate.pKey $ cPos c) animations
keyName = lIntToText loaded key
in Current (Animate.initPosition key) keyName
unsafeNextKey :: Int -> Animate.Animations a b c -> Int
unsafeNextKey n (Animate.Animations a) = (1 + n) `mod` (V.length a)
unsafePrevKey :: Int -> Animate.Animations a b c -> Int
unsafePrevKey n (Animate.Animations a) = (n - 1) `mod` (V.length a)
updateMode :: (S m, HasInput m) => m ()
updateMode = do
input <- getInput
when (onceThenFire $ iMode input) $ modify $ \v -> v { vMode = if vMode v == Mode'Playback then Mode'Stepper else Mode'Playback }
updateReload :: (S m, R m, HasInput m, Loader m, MonadIO m) => m ()
updateReload = do
input <- getInput
mreload <- asks cReload
watchReload <- liftIO $ readMVar mreload
when watchReload $ do
reload
liftIO $ modifyMVar_ mreload $ \_ -> return False
when (isPressed $ iReload input) reload
reload :: (R m, Loader m, MonadIO m) => m ()
reload = do
_ <- load
loaded' <- getLoaded
case loaded' of
Nothing -> return ()
Just loaded -> do
modifyCurrent $ \current ->
case current of
Nothing -> Just $ Current (Animate.initPosition 0) (lIntToText loaded 0)
Just c -> Just $ reloadCurrent loaded c
reloadCurrent :: Loaded -> Current -> Current
reloadCurrent l c
| lIntToText l (Animate.pKey . cPos $ c) == cKeyName c = case lTextToInt l (cKeyName c) of
Nothing -> reset
Just k -> c
{ cPos = (cPos c)
{ Animate.pKey = k
, Animate.pFrameIndex = mod
(Animate.pFrameIndex (cPos c))
(V.length $ Animate.framesByAnimation (Animate.ssAnimations $ lSpriteSheet l) k)
, Animate.pCounter = 0
}
}
| otherwise = reset
where
reset = Current (Animate.initPosition 0) (lIntToText l 0)
updateAnimation :: (R m, S m, Renderer m, HasInput m, MonadIO m) => m ()
updateAnimation = do
mode <- gets vMode
loaded' <- getLoaded
frameDeltaSeconds <- asks cFrameDeltaSeconds
case loaded' of
Nothing -> return ()
Just loaded -> do
let animations = (Animate.ssAnimations . lSpriteSheet) loaded
current' <- getCurrent
case current' of
Nothing -> return ()
Just Current{cPos=pos} -> case mode of
Mode'Stepper -> do
input <- getInput
when (onceThenFire $ iFaster input) $ modifyCurrent' $ \c -> c { cPos = forceNextFrameIndex animations pos }
when (onceThenFire $ iSlower input) $ modifyCurrent' $ \c -> c { cPos = forcePrevFrameIndex animations pos }
Mode'Playback -> do
accel <- gets vAccel
let accelScalar = scalarToSeconds accel
let pos' = Animate.stepPosition animations pos (frameDeltaSeconds * (Seconds accelScalar))
modifyCurrent' $ \c -> c { cPos = pos' }
unsafeForceNextFrameIndex :: V.Vector (Animate.Frame loc delay) -> Animate.FrameIndex -> Animate.FrameIndex
unsafeForceNextFrameIndex frames idx = if idx + 1 >= len then 0 else idx + 1
where len = V.length frames
unsafeForcePrevFrameIndex :: V.Vector (Animate.Frame loc delay) -> Animate.FrameIndex -> Animate.FrameIndex
unsafeForcePrevFrameIndex frames idx = if idx <= 0 then len - 1 else idx - 1
where len = V.length frames
forceNextFrameIndex :: Enum key => Animate.Animations key loc delay -> Animate.Position key delay -> Animate.Position key delay
forceNextFrameIndex a p = p { Animate.pFrameIndex = unsafeForceNextFrameIndex frames (Animate.pFrameIndex p) }
where
frames = Animate.framesByAnimation a (Animate.pKey p)
forcePrevFrameIndex :: Enum key => Animate.Animations key loc delay -> Animate.Position key delay -> Animate.Position key delay
forcePrevFrameIndex a p = p { Animate.pFrameIndex = unsafeForcePrevFrameIndex frames (Animate.pFrameIndex p) }
where
frames = Animate.framesByAnimation a (Animate.pKey p)
updateSpeed :: (S m, HasInput m) => m ()
updateSpeed = do
input <- getInput
mode <- gets vMode
case mode of
Mode'Stepper -> return ()
Mode'Playback -> do
let up = onceThenFire (iFaster input)
let down = onceThenFire (iSlower input)
let reset = isPressed (iAccelReset input)
let change s
| reset = Scalar'None
| up && not down = incrementScalar 18 s
| down && not up = decrementScalar 9 s
| otherwise = s
modify $ \v -> v { vAccel = change (vAccel v) }
onceThenFire :: KeyState Int -> Bool
onceThenFire ks = isPressed ks || (isHeld ks && counterGreater 20 ks)
counterGreater :: Int -> KeyState Int -> Bool
counterGreater n ks = case ksCounter ks of
Nothing -> False
Just counter -> counter > n
updateInfo :: (S m, HasInput m) => m ()
updateInfo = do
input <- getInput
when (onceThenFire $ iInfo input) $ modify $ \v -> v { vInfoShown = not (vInfoShown v) }
updateScale :: (S m, HasInput m) => m ()
updateScale = do
input <- getInput
let up = onceThenFire (iScaleUp input) || iScaleMouseUp input > 0
let down = onceThenFire (iScaleDown input) || iScaleMouseDown input > 0
let reset = isPressed (iScaleReset input) || isPressed (iMouseMiddleClick input)
let increment = replicate (max 1 (iScaleMouseUp input)) (incrementScalar 90)
let decrement = replicate (max 1 (iScaleMouseDown input)) (decrementScalar 9)
let change s
| reset = Scalar'None
| up && not down = fapply increment s
| down && not up = fapply decrement s
| otherwise = s
modify $ \v -> v { vScale = change (vScale v) }
modifyCurrent' :: (R m, MonadIO m) => (Current -> Current) -> m ()
modifyCurrent' f = do
m <- asks cCurrent
liftIO $ modifyMVar_ m (\c -> return $ fmap f c)
modifyCurrent :: (R m, MonadIO m) => (Maybe Current -> Maybe Current) -> m ()
modifyCurrent f = do
m <- asks cCurrent
liftIO $ modifyMVar_ m (\c -> return $ f c)
setCurrent :: (R m, MonadIO m) => Maybe Current -> m ()
setCurrent c = do
m <- asks cCurrent
liftIO $ modifyMVar_ m (\_ -> return c)
getCurrent :: (R m, MonadIO m) => m (Maybe Current)
getCurrent = do
m <- asks cCurrent
liftIO $ readMVar m
fapply :: [a -> a] -> a -> a
fapply xs a = case xs of [] -> a; (y:ys) -> fapply ys (y a)
updateOrigin :: (R m, S m, HasInput m) => m ()
updateOrigin = do
input <- getInput
-- Move with keys
let d = 5
when (onceThenFire $ iUp input) $ modify $ \v -> v { vOrigin = vOrigin v + V2 0 (-d) }
when (onceThenFire $ iDown input) $ modify $ \v -> v { vOrigin = vOrigin v + V2 0 d }
when (onceThenFire $ iLeft input) $ modify $ \v -> v { vOrigin = vOrigin v + V2 (-d) 0 }
when (onceThenFire $ iRight input) $ modify $ \v -> v { vOrigin = vOrigin v + V2 d 0 }
-- Recenter origin
when (isPressed $ iCenterOrigin input) $ do
drawSize <- gets vDrawSize
modify $ \v -> v { vOrigin = div <$> drawSize <*> 2 }
-- Origin by mouse click
when (isTouched $ iMouseClick input) $ modify $ \v -> v
{ vOrigin = iMousePos input }
toggleVisuals :: (S m, HasInput m) => m ()
toggleVisuals = do
input <- getInput
let toggleBackground = isPressed (iBackground input)
let toggleOrigin = isPressed (iOrigin input)
let toggleOutline = isPressed (iOutline input)
modify $ (\v -> v
{ vBackground = (if toggleBackground then toggleMono else id) (vBackground v)
, vOriginColor = (if toggleOrigin then toggleColors else id) (vOriginColor v)
, vOutlineColor = (if toggleOutline then toggleColors else id) (vOutlineColor v)
})
drawScene :: (R m, S m, Renderer m, HasInput m, MonadIO m) => m ()
drawScene = do
V2 x y <- gets vOrigin
current <- getCurrent
originColor <- gets vOriginColor
outlineColor <- gets vOutlineColor
fps <- asks cFps
accel <- gets vAccel
mode <- gets vMode
infoShown <- gets vInfoShown
scale <- gets vScale
let scalar = scalarToSpriteScale scale
loaded' <- getLoaded
highDpi <- asks cHighDpi
let lineSpacing = lineSpacing' highDpi
case loaded' of
Nothing -> when infoShown $ do
drawText (ofsX, ofsY + lineSpacing * 5) $ toText $ concat ["Anim:"]
drawText (ofsX, ofsY + lineSpacing * 6) $ toText $ concat [" Key:"]
drawText (ofsX, ofsY + lineSpacing * 7) $ toText $ concat [" Time:"]
drawText (ofsX, ofsY + lineSpacing * 8) $ toText $ concat ["Pos:"]
drawText (ofsX, ofsY + lineSpacing * 9) $ toText $ concat [" Frame:"]
drawText (ofsX, ofsY + lineSpacing * 10) $ toText $ concat [" Time:"]
drawText (ofsX, ofsY + lineSpacing * 11) $ toText $ concat [" Points:"]
drawText (ofsX, ofsY + lineSpacing * 12) $ toText $ concat [" Size:"]
drawText (ofsX, ofsY + lineSpacing * 13) $ toText $ concat [" Offset: "]
Just loaded -> do
case current of
Nothing -> return ()
Just c -> do
let pos = cPos c
let animations = (Animate.ssAnimations . lSpriteSheet) loaded
let frames = Animate.framesByAnimation animations (Animate.pKey pos)
let frame = frames V.! Animate.pFrameIndex pos
let frameDelay = Animate.fDelay frame
let frameClip = Animate.fLocation frame
let (x0,y0) = (Animate.scX frameClip, Animate.scY frameClip)
let (w,h) = (Animate.scW frameClip, Animate.scH frameClip)
let (x1,y1) = (x0 + w, y0 + h)
let framesLen = V.length frames
let totalSeconds = sum $ map Animate.fDelay (V.toList frames)
let loc = Animate.currentLocation animations pos
drawAniSprite (lSpriteSheet loaded) outlineColor scalar loc (x, y)
when infoShown $ do
let keyName = cKeyName c
drawText (ofsX, ofsY + lineSpacing * 5) $ toText $ concat ["Anim:"]
drawText (ofsX, ofsY + lineSpacing * 6) $ toText $ concat [" Key: \"", fromText keyName, "\" ", show $ Animate.pKey pos, " [", show $ lTotalKeys loaded - 1, "]"]
drawText (ofsX, ofsY + lineSpacing * 7) $ toText $ concat [" Time: ", show totalSeconds]
drawText (ofsX, ofsY + lineSpacing * 8) $ toText $ concat ["Pos:"]
drawText (ofsX, ofsY + lineSpacing * 9) $ toText $ concat [" Frame: ", show $ Animate.pFrameIndex pos, " [", show $ framesLen - 1, "]"]
drawText (ofsX, ofsY + lineSpacing * 10) $ toText $ concat [" Time: ", show $ Animate.pCounter pos, " [", show $ frameDelay, "]"]
drawText (ofsX, ofsY + lineSpacing * 11) $ toText $ concat [" Points: (", show x0, ",", show y0, ") (", show x1, ",", show y1, ")"]
drawText (ofsX, ofsY + lineSpacing * 12) $ toText $ concat [" Size: (", show w, ",", show h, ")"]
drawText (ofsX, ofsY + lineSpacing * 13) $ toText $ concat $ [" Offset: "] ++ case Animate.scOffset frameClip of
Nothing -> []
Just (ox,oy) -> ["(", show ox, ",", show oy, ")"]
case originColor of
Nothing -> return ()
Just originColor' -> drawCrosshair (x,y) originColor'
-- HUD
when infoShown $ do
settings <- asks cSettings
drawText (ofsX, ofsY + lineSpacing * 0) ("Mode: " `mappend` if mode == Mode'Playback then "Playback" else "Stepper")
drawText (ofsX, ofsY + lineSpacing * 1) ("File: " `mappend` toText (sTarget settings))
drawText (ofsX, ofsY + lineSpacing * 2) ("FPS: " `mappend` toText (show fps))
drawText (ofsX, ofsY + lineSpacing * 3) ("Scale: " `mappend` toText (asScaleString scale))
drawText (ofsX, ofsY + lineSpacing * 4) ("Accel: " `mappend` toText (asSpeedString accel))
where
lineSpacing' x = if x then 26 else 13
ofsX = 6
ofsY = 6