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 19) 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