packages feed

animate-preview (empty) → 0.0.0

raw patch · 27 files changed

+1491/−0 lines, 27 filesdep +StateVardep +aesondep +animatesetup-changedbinary-added

Dependencies added: StateVar, aeson, animate, animate-preview, base, bytestring, containers, filepath, fsnotify, key-state, lens, linear, mtl, optparse-applicative, optparse-generic, random, safe, safe-exceptions, sdl2, sdl2-gfx, sdl2-image, sdl2-ttf, text, text-conversions, vector

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2018++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,25 @@+# Animate Preview++## OSX build++```+brew install sdl2+brew install sdl2_ttf+brew install sdl2_image+brew install sdl2_gfx+stack build+```++## Ubuntu build++```+sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-gfx-dev+stack build+```++## Windows build++```+stack exec -- pacman -S mingw64/mingw-w64-x86_64-pkg-config mingw64/mingw-w64-x86_64-SDL2 mingw64/mingw-w64-x86_64-SDL2_ttf mingw64/mingw-w64-x86_64-SDL2_image mingw64/mingw-w64-x86_64-SDL2_gfx+stack build+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ animate-preview.cabal view
@@ -0,0 +1,98 @@+-- This file has been generated from package.yaml by hpack version 0.20.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 51d99c14d61a1d85be7c9e79e08619cb9077082c77d374d45595f5a1cc88b81e++name:           animate-preview+version:        0.0.0+synopsis:       Preview tool for sprite animation+description:    Preview tool for sprite animation+category:       Game+homepage:       https://github.com/jxv/animate-preview#readme+bug-reports:    https://github.com/jxv/animate-preview/issues+maintainer:     Joe Vargas+copyright:      2018 Joe Vargas+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10++extra-source-files:+    package.yaml+    README.md+    stack.yaml++data-files:+    resource/ProggyClean.ttf++source-repository head+  type: git+  location: https://github.com/jxv/animate-preview++library+  hs-source-dirs:+      library+  default-extensions: DuplicateRecordFields FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving LambdaCase NamedFieldPuns ScopedTypeVariables OverloadedStrings DefaultSignatures MultiParamTypeClasses+  ghc-options: -Wall+  build-depends:+      StateVar+    , aeson+    , animate+    , base >=4.7 && <5+    , bytestring+    , containers+    , filepath+    , fsnotify+    , key-state+    , lens+    , linear+    , mtl+    , optparse-applicative+    , optparse-generic+    , random+    , safe+    , safe-exceptions+    , sdl2+    , sdl2-gfx+    , sdl2-image+    , sdl2-ttf+    , text+    , text-conversions+    , vector+  exposed-modules:+      Animate.Preview+      Animate.Preview.Animation+      Animate.Preview.Color+      Animate.Preview.Config+      Animate.Preview.Frame+      Animate.Preview.Input+      Animate.Preview.Loader+      Animate.Preview.Logger+      Animate.Preview.ManagerInput+      Animate.Preview.Mode+      Animate.Preview.Renderer+      Animate.Preview.Resource+      Animate.Preview.Runner+      Animate.Preview.Scalar+      Animate.Preview.Scene+      Animate.Preview.SDLInput+      Animate.Preview.SDLRenderer+      Animate.Preview.State+      Animate.Preview.Timer+  other-modules:+      Paths_animate_preview+  default-language: Haskell2010++executable animate-preview+  main-is: Main.hs+  hs-source-dirs:+      executable+  default-extensions: DuplicateRecordFields FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving LambdaCase NamedFieldPuns ScopedTypeVariables OverloadedStrings DefaultSignatures MultiParamTypeClasses+  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N+  build-depends:+      animate-preview+    , base >=4.7 && <5+  other-modules:+      Paths_animate_preview+  default-language: Haskell2010
+ executable/Main.hs view
@@ -0,0 +1,6 @@+module Main where++import qualified Animate.Preview++main :: IO ()+main = Animate.Preview.main
+ library/Animate/Preview.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DataKinds #-}+module Animate.Preview+  ( main+  ) where++import qualified SDL+import qualified SDL.Font as Font+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Reader (MonadReader, ReaderT, runReaderT)+import Control.Monad.State (MonadState, StateT, evalStateT)+import Control.Monad (when, forever, void)+import Control.Concurrent (threadDelay, forkIO, newMVar)+import Control.Exception.Safe (MonadThrow, MonadCatch)+import Data.Maybe (fromMaybe)+import Data.StateVar (get)+import Options.Generic+import System.FSNotify (withManager, watchDir, eventPath)+import System.FilePath (takeDirectory, takeFileName)++import Animate.Preview.Config+import Animate.Preview.Logger+import Animate.Preview.Loader+import Animate.Preview.Renderer+import Animate.Preview.SDLInput+import Animate.Preview.SDLRenderer+import Animate.Preview.ManagerInput+import Animate.Preview.Resource+import Animate.Preview.Runner+import Animate.Preview.Scene+import Animate.Preview.State+import Animate.Preview.Timer++data Options = Options+  { target :: String  <?> "file path with sprite data (YAML or JSON)"+  , image :: (Maybe String) <?> "Force sprite sheet image path"+  , scale :: (Maybe Float) <?> "Scale the sprite size"+  , highDpi :: Bool <?> "Use high DPI"+  , watch :: Bool <?> "Watch files and reload on change"+  } deriving (Show, Generic)++instance ParseRecord Options where+  parseRecord = parseRecordWithModifiers lispCaseModifiers++main :: IO ()+main = do+  options <- getRecord "options"+  let highDpi' = unHelpful $ highDpi options++  SDL.initialize [SDL.InitVideo]+  Font.initialize+  window <- SDL.createWindow+    "Animate Preview"+    SDL.defaultWindow+      { SDL.windowHighDPI = highDpi'+      , SDL.windowResizable = True+      }+  renderer <- SDL.createRenderer window (-1) SDL.defaultRenderer+  resources <- loadResources highDpi' renderer+  loaded <- newMVar Nothing+  current <- newMVar Nothing++  winSize <- fmap fromIntegral <$> get (SDL.windowSize window)+  drawSize <- fmap fromIntegral <$> SDL.glGetDrawableSize window++  let settings = Settings+        { sTarget = unHelpful $ target options+        , sSpritesheet = unHelpful $ image options+        , sScale = fromMaybe 1 (unHelpful $ scale options)+        }++  let cfg = Config+        { cWindow = window+        , cRenderer = renderer+        , cResources = resources+        , cHighDpi = highDpi' && (winSize /= drawSize)+        , cSettings = settings+        , cCurrent = current+        , cLoaded = loaded+        }++  when (unHelpful $ watch options) $ do+    runWatcherAndReloader cfg (sTarget settings)+    case sSpritesheet settings of+      Nothing -> return ()+      Just imgPath -> runWatcherAndReloader cfg imgPath+  +  let v = initVars winSize drawSize+  runAnimatePreview cfg v (reload >> mainLoop)+  SDL.destroyWindow window+  freeResources resources+  Font.quit+  SDL.quit++runWatcherAndReloader :: Config -> String -> IO ()+runWatcherAndReloader cfg filename = void $ forkIO $ withManager $ \mgr -> do+  -- start a watching job (in the background)+  void $ watchDir+    mgr+    (takeDirectory filename)+    (\event -> takeFileName (eventPath event) == takeFileName filename)+    (\_ -> runWatcher cfg reload)+  -- sleep forever (until interrupted)+  forever $ threadDelay 1000000++newtype AnimatePreview a = AnimatePreview (ReaderT Config (StateT Vars IO) a)+  deriving (Functor, Applicative, Monad, MonadReader Config, MonadState Vars, MonadIO, MonadThrow, MonadCatch)++runAnimatePreview :: Config -> Vars -> AnimatePreview a -> IO a+runAnimatePreview config v (AnimatePreview m) = evalStateT (runReaderT m config) v++instance Logger AnimatePreview+instance SDLRenderer AnimatePreview+instance SDLInput AnimatePreview+instance HasInput AnimatePreview+instance Renderer AnimatePreview+instance Scene AnimatePreview+instance Loader AnimatePreview+instance Timer AnimatePreview++newtype Watcher a = Watcher (ReaderT Config IO a)+  deriving (Functor, Applicative, Monad, MonadReader Config, MonadIO, MonadThrow, MonadCatch)++instance Logger Watcher+instance Loader Watcher++runWatcher :: Config -> Watcher a -> IO a+runWatcher cfg (Watcher m) = runReaderT m cfg
+ library/Animate/Preview/Animation.hs view
@@ -0,0 +1,17 @@+module Animate.Preview.Animation where++import qualified Animate+import Text.Printf (printf)++import Data.Aeson (FromJSON, ToJSON)++type Animations key = Animate.Animations key (Animate.SpriteClip key) Seconds++newtype Seconds = Seconds Float+  deriving (Eq, Num, ToJSON, FromJSON, Fractional, Ord)++instance Show Seconds where+  show (Seconds s) = printf "%.3f" s ++ "s"++clamp :: (Fractional a, Ord a) => a -> a -> a -> a+clamp cur min' max' = if cur > max' then max' else (if cur < min' then min' else cur)
+ library/Animate/Preview/Color.hs view
@@ -0,0 +1,45 @@+module Animate.Preview.Color where++import Linear+import Data.Word (Word8)++data Mono+  = Mono'Black+  | Mono'Gray+  | Mono'White+  deriving (Show, Eq, Enum, Bounded)++toggleMono :: Mono -> Mono+toggleMono = \case+  Mono'White -> Mono'Black+  m -> succ m++fromMono :: Mono -> (V4 Word8, V4 Word8)+fromMono = \case+  Mono'White -> (V4 0xff 0xff 0xff 0xff, V4 0xdd 0xdd 0xdd 0xff)+  Mono'Gray -> (V4 0x88 0x88 0x88 0xff, V4 0x66 0x66 0x66 0xff)+  Mono'Black -> (V4 0x22 0x22 0x22 0xff, V4 0x00 0x00 0x00 0xff)++data Color+  = Color'Red+  | Color'Green+  | Color'Blue+  | Color'Magenta+  | Color'Yellow+  | Color'Cyan+  deriving (Show, Eq, Enum, Bounded)++fromColor :: Color -> V4 Word8+fromColor = \case+  Color'Red -> V4 0xff 0x00 0x00 0xff+  Color'Green -> V4 0x00 0xff 0x00 0xff+  Color'Blue -> V4 0x00 0x00 0xff 0xff+  Color'Magenta -> V4 0xff 0x00 0xff 0xff+  Color'Yellow -> V4 0xff 0xff 0x00 0xff+  Color'Cyan -> V4 0x00 0xff 0xff 0xff++toggleColors :: Maybe Color -> Maybe Color+toggleColors Nothing = Just Color'Red+toggleColors (Just c) = case c of+    Color'Cyan -> Nothing+    _ -> Just $ succ c
+ library/Animate/Preview/Config.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE ConstraintKinds #-}+module Animate.Preview.Config where++import qualified SDL+import qualified Animate+import Data.Text (Text)+import Control.Monad.Reader (MonadReader)+import Control.Concurrent++import Animate.Preview.Animation+import Animate.Preview.Resource++data Settings = Settings+  { sTarget :: String+  , sSpritesheet :: Maybe String+  , sScale :: Float+  }++data Loaded = Loaded+  { lTextToInt :: Text -> Maybe Int+  , lIntToText :: Int -> Text+  , lSpriteSheet :: Animate.SpriteSheet Int SDL.Texture Seconds+  , lTotalKeys :: Int+  }++data Current = Current+  { cPos :: Animate.Position Int Seconds+  , cKeyName :: Text+  } deriving (Show, Eq)++data Config = Config+  { cWindow :: SDL.Window+  , cRenderer :: SDL.Renderer+  , cResources :: Resources+  , cHighDpi :: Bool+  , cSettings :: Settings+  , cCurrent :: MVar (Maybe Current)+  , cLoaded :: MVar (Maybe Loaded)+  }++type R m = MonadReader Config m
+ library/Animate/Preview/Frame.hs view
@@ -0,0 +1,11 @@+module Animate.Preview.Frame where++frameDeltaSeconds :: Fractional a => a+frameDeltaSeconds = 0.016667++frameDeltaMilliseconds :: Int+frameDeltaMilliseconds = 16++msps, fps :: Int+msps = 1000+fps = 60
+ library/Animate/Preview/Input.hs view
@@ -0,0 +1,39 @@+module Animate.Preview.Input where++import KeyState+import Linear++data Input = Input+  { iSpace :: KeyState Int+  , iUp :: KeyState Int+  , iDown :: KeyState Int+  , iLeft :: KeyState Int+  , iRight :: KeyState Int+  , iPrevKeyFrame :: KeyState Int+  , iNextKeyFrame :: KeyState Int+  , iScaleReset :: KeyState Int+  , iScaleMouseUp :: Int+  , iScaleMouseDown :: Int+  , iScaleUp :: KeyState Int+  , iScaleDown :: KeyState Int+  , iEscape :: KeyState Int+  , iReload :: KeyState Int+  , iOrigin :: KeyState Int+  , iOutline :: KeyState Int+  , iBackground :: KeyState Int+  , iMousePos :: V2 Int+  , iMouseClick :: KeyState Int+  , iMouseMiddleClick :: KeyState Int+  , iCenterOrigin :: KeyState Int+  , iAccelReset :: KeyState Int+  , iFaster :: KeyState Int+  , iSlower :: KeyState Int+  , iInfo :: KeyState Int+  , iMode :: KeyState Int+  , iQuit :: Bool+  } deriving (Show, Eq)++initInput :: Input+initInput = Input a a a a a a a a 0 0 a a a a a a a (V2 0 0) a a a a a a a a False+  where+    a = initKeyState
+ library/Animate/Preview/Loader.hs view
@@ -0,0 +1,84 @@+module Animate.Preview.Loader where++import qualified Data.Map as Map+import qualified Data.Vector as V+import qualified SDL+import Control.Monad.IO.Class (MonadIO(..))+import Control.Applicative ((<|>))+import Control.Monad.Reader (asks)+import Control.Concurrent (modifyMVar_, readMVar)+import Data.Text.Conversions (toText)+import Data.Maybe (fromMaybe)+import System.IO.Error++import qualified Animate+import Animate.Preview.Animation+import Animate.Preview.Resource+import Animate.Preview.Config+import Animate.Preview.Logger++class Monad m => Loader m where+  load :: m Bool+  default load :: (R m, MonadIO m, Logger m) => m Bool+  load = do+    ren <- asks cRenderer+    target <- asks (sTarget . cSettings)+    maybeForceImage <- asks (sSpritesheet . cSettings)+    spriteSheetInfo <- liftIO $ catchIOError+      (Just <$> Animate.readSpriteSheetInfoYAML target)+      (const $ return Nothing)+    case spriteSheetInfo :: Maybe (Animate.SpriteSheetInfo Int Seconds) of+      Nothing -> do+        setLoaded Nothing+        logText $ "Not Loaded: " `mappend` toText target+        return False+      Just ssi -> do+        let ssi' = ssi+              { Animate.ssiImage = fromMaybe (Animate.ssiImage ssi) maybeForceImage+              , Animate.ssiAlpha = Nothing <|> Animate.ssiAlpha ssi }+        let animations = Animate.ssiAnimations ssi'+        if Map.null animations+          then do+            setLoaded Nothing+            logText $ "No Animations: " `mappend` toText target+            return False+          else do+            tex' <- liftIO $ loadTexture ren (Animate.ssiImage ssi') (Animate.ssiAlpha ssi')+            case tex' of+              Nothing -> do+                setLoaded Nothing+                logText $ "No Image: " `mappend` toText (Animate.ssiImage ssi')+                return False+              Just tex -> do+                let intToText i = fst $ Map.elemAt i animations+                let textToInt t = Map.lookup t $ Map.fromList $ zip (map fst $ Map.toList animations) [0..]+                let animations' = Animate.Animations $ V.fromList $ do+                      (_  , frames) <- Map.toList animations+                      return $ V.fromList $ do+                        (frameIdx, delay) <- frames+                        return $ Animate.Frame (Animate.ssiClips ssi' !! frameIdx) delay+                let spriteSheet = Animate.SpriteSheet animations' tex+                let totalKeys = V.length $ Animate.unAnimations animations'+                let loaded = Loaded textToInt intToText spriteSheet totalKeys+                setLoaded (Just loaded)+                logText $ "Loaded: " `mappend` toText target+                return True+    where+      loadTexture r path c = do+        s' <- loadSurface' path c+        case s' of+          Nothing -> return Nothing+          Just s -> do+            t <- SDL.createTextureFromSurface r s+            SDL.freeSurface s+            return (Just t)++setLoaded :: (R m, MonadIO m) => Maybe Loaded -> m ()+setLoaded l = do+  m <- asks cLoaded+  liftIO $ modifyMVar_ m (\_ -> return l)++getLoaded :: (R m, MonadIO m) => m (Maybe Loaded)+getLoaded = do+  m <- asks cLoaded+  liftIO $ readMVar m
+ library/Animate/Preview/Logger.hs view
@@ -0,0 +1,14 @@+module Animate.Preview.Logger where++import qualified Data.Text.IO as T+import Control.Monad.IO.Class (MonadIO(..))+import Data.Text (Text)++class Monad m => Logger m where+  logText :: Text -> m ()+  default logText :: MonadIO m => Text -> m ()+  logText = liftIO . T.putStrLn++  logShow :: Show a => a -> m ()+  default logShow :: (MonadIO m, Show a) => a -> m ()+  logShow = liftIO . print
+ library/Animate/Preview/ManagerInput.hs view
@@ -0,0 +1,81 @@+module Animate.Preview.ManagerInput where++import qualified SDL+import Control.Monad.State+import Control.Lens (view)+import KeyState+import Linear++import Animate.Preview.Input+import Animate.Preview.SDLInput+import Animate.Preview.State++class Monad m => HasInput m where+  updateInput :: m ()+  default updateInput :: (HasInput m, SDLInput m, MonadIO m) => m ()+  updateInput = do+    input <- getInput+    events <- pollEventPayloads+    mousePos <- getMousePos+    mouseClick <- getMouseClick+    middleClick <- getMouseMiddleClick+    setInput (stepControl events (mouseClick, middleClick) input) { iMousePos = mousePos }++  setInput :: Input -> m ()+  default setInput :: S m => Input -> m ()+  setInput input = modify (\v -> v { vInput = input })++  getInput :: m Input+  default getInput :: S m => m Input+  getInput = gets vInput++stepControl :: [SDL.EventPayload] -> (Bool, Bool) -> Input -> Input+stepControl events (mouseClick, middleClick) i = i+  { iSpace = next [SDL.KeycodeSpace] iSpace+  , iUp = next [SDL.KeycodeUp, SDL.KeycodeK] iUp+  , iDown = next [SDL.KeycodeDown, SDL.KeycodeJ] iDown+  , iLeft = next [SDL.KeycodeLeft, SDL.KeycodeH] iLeft+  , iRight = next [SDL.KeycodeRight, SDL.KeycodeL] iRight+  , iPrevKeyFrame = next [SDL.KeycodeP] iPrevKeyFrame+  , iNextKeyFrame = next [SDL.KeycodeN] iNextKeyFrame+  , iScaleReset = next [SDL.KeycodeS] iScaleReset+  , iScaleMouseUp = mouseEvent GT+  , iScaleMouseDown = mouseEvent LT+  , iScaleUp = next [SDL.KeycodePlus, SDL.KeycodeEquals] iScaleUp+  , iScaleDown = next [SDL.KeycodeMinus] iScaleDown+  , iEscape = next [SDL.KeycodeEscape] iEscape+  , iReload = next [SDL.KeycodeR] iReload+  , iOrigin = next [SDL.KeycodeT] iOrigin+  , iOutline = next [SDL.KeycodeO] iOutline+  , iBackground = next [SDL.KeycodeB] iBackground+  , iMouseMiddleClick = updateKeyState 1 (iMouseMiddleClick i) middleClick+  , iMouseClick = updateKeyState 1 (iMouseClick i) mouseClick+  , iCenterOrigin = next [SDL.KeycodeC] iCenterOrigin+  , iFaster = next [SDL.KeycodeF] iFaster+  , iSlower = next [SDL.KeycodeD] iSlower+  , iAccelReset = next [SDL.KeycodeA] iAccelReset+  , iInfo = next [SDL.KeycodeI] iInfo+  , iMode = next [SDL.KeycodeM] iMode+  , iQuit = elem SDL.QuitEvent events+  }+  where+    next xs f = nextKeystate events 1 xs (f i)+    mouseEvent ordering = fromIntegral $ maximum $ (0:) $ flip map events $ \case+      SDL.MouseWheelEvent dat -> let+        scrollAmount = view _y $ SDL.mouseWheelEventPos dat+        threshold = 0+        in if abs scrollAmount > threshold+          then case SDL.mouseWheelEventDirection dat of+            SDL.ScrollNormal -> if compare scrollAmount 0 == ordering then abs scrollAmount else 0+            SDL.ScrollFlipped -> if compare 0 scrollAmount == ordering then abs scrollAmount else 0+          else 0+      _ -> 0++nextKeystate :: [SDL.EventPayload] -> Int -> [SDL.Keycode] -> KeyState Int -> KeyState Int+nextKeystate events count keycodes keystate+  | or $ map pressed keycodes = pressedKeyState+  | or $ map released keycodes = releasedKeyState+  | otherwise = maintainKeyState count keystate+  where+    released keycode = or $ map (keycodeReleased keycode) events+    pressed keycode = or $ map (keycodePressed keycode) events
+ library/Animate/Preview/Mode.hs view
@@ -0,0 +1,6 @@+module Animate.Preview.Mode where++data Mode+  = Mode'Playback+  | Mode'Stepper+  deriving (Show, Eq)
+ library/Animate/Preview/Renderer.hs view
@@ -0,0 +1,153 @@+module Animate.Preview.Renderer where++import qualified Animate+import qualified SDL+import qualified SDL.Primitive as Gfx+import qualified Data.Map as Map+import Foreign.C.Types+import SDL.Vect+import Control.Monad.Reader (asks)+import Control.Monad.State (modify)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Maybe (fromMaybe)+import Data.StateVar (($=))+import Foreign.C.Types (CFloat(..))+import Data.Text (Text)+import Data.Text.Conversions (fromText)+import Data.StateVar (get)+import Data.List (foldl')++import Animate.Preview.Config+import Animate.Preview.Resource+import Animate.Preview.Animation+import Animate.Preview.Color+import Animate.Preview.SDLRenderer+import Animate.Preview.State++class Monad m => Renderer m where+  updateScreenInfo :: m ()+  default updateScreenInfo :: (R m, S m, MonadIO m) => m ()+  updateScreenInfo = do+    w <- asks cWindow+    winSize <- liftIO $ get (SDL.windowSize w)+    drawSize <- liftIO $ SDL.glGetDrawableSize w+    modify $ \v -> v { vDrawSize = fromIntegral <$> drawSize, vWinSize = fromIntegral <$> winSize }++  clearScreen :: m ()+  default clearScreen :: (SDLRenderer m, R m) => m ()+  clearScreen = do+    renderer <- asks cRenderer+    clearRenderer renderer++  drawScreen :: m ()+  default drawScreen :: (SDLRenderer m, R m) => m ()+  drawScreen = do+    renderer <- asks cRenderer+    presentRenderer renderer++  drawBackground :: V2 Int -> Mono -> m ()+  default drawBackground :: (SDLRenderer m, R m, MonadIO m) => V2 Int -> Mono -> m ()+  drawBackground (V2 w h) mono = do+    renderer <- asks cRenderer+    let (lite, dark) = fromMono mono+    let indices = do+          x <- [0..(w `div` 10)]+          y <- [0..(h `div` 10)]+          let x0 = x * 10+              x1 = x * 10 + 10+          let y0 = y * 10+              y1 = y * 10 + 10+          let color = if (x + y) `mod` 2 == 0 then lite else dark+          return (V2 x0 y0, V2 x1 y1, color) +    flip mapM_ indices $ \(a,b,c) ->+      liftIO $ Gfx.fillRectangle renderer (fromIntegral <$> a) (fromIntegral <$> b) c+  +  drawAniSprite :: Animate.SpriteSheet Int SDL.Texture Seconds -> Maybe Color -> Float -> Animate.SpriteClip Int -> (Int, Int) -> m ()+  default drawAniSprite :: (SDLRenderer m, R m, MonadIO m) => Animate.SpriteSheet Int SDL.Texture Seconds -> Maybe Color -> Float -> Animate.SpriteClip Int -> (Int, Int) -> m ()+  drawAniSprite as = drawSprite (const as)++  drawCrosshair :: (Int, Int) -> Color -> m ()+  default drawCrosshair :: (MonadIO m, R m) => (Int, Int) -> Color -> m ()+  drawCrosshair (x,y) color = do+    ren <- asks cRenderer+    let radius = 8+    let diameter = fromIntegral $ radius * 2+    liftIO $ do+      let color' = fromColor color+      Gfx.horizontalLine ren (fromIntegral <$> V2 (x - radius) y) diameter color'+      Gfx.verticalLine ren (fromIntegral <$> V2 x (y - radius)) diameter color'++  drawText :: (Int, Int) -> Text -> m ()+  default drawText :: (SDLRenderer m, R m, MonadIO m) => (Int, Int) -> Text -> m ()+  drawText xy text = do+    glyphMap <- asks (rGlyphMap . cResources)+    glyphSize <- asks (rGlyphSize . cResources)+    let instructions = buildGlyphIntructions glyphSize glyphMap (fromText text)+    let (x,y) = xy+    flip mapM_ instructions $ \(tex, (x',y')) -> do+      let xy' = (x + x', y + y')+      drawTextureSprite (const tex) xy'++buildGlyphIntructions :: Int -> Map.Map Char Glyph -> [Char] -> [(SDL.Texture, (Int, Int))]+buildGlyphIntructions _glyphSize glyphMap str = fst $ foldl' appendGlyph ([], (0,0)) str+  where+    appendGlyph+      :: ([(SDL.Texture, (Int, Int))], (Int,Int))+      -> Char+      -> ([(SDL.Texture, (Int, Int))], (Int,Int)) +    appendGlyph (glyphs, (x,y)) ch = case Map.lookup ch glyphMap of+      Nothing -> (glyphs, (x,y))+      Just glyph -> let+        tex = gTexture glyph+        (xmin, _xmax, _ymin, _ymax, adv) = gMetrics glyph+        x' = x + xmin+        y' = y+        in ((tex, (x', y')) : glyphs, (x + xmin + adv, y))++drawTextureSprite :: (SDLRenderer m, R m) => (Config -> SDL.Texture) -> (Int, Int) -> m ()+drawTextureSprite getTex (x,y) = do+  renderer <- asks cRenderer+  tex <- asks getTex+  SDL.TextureInfo{textureWidth,textureHeight} <- queryTexture tex+  let dim = V2 textureWidth textureHeight+  drawTexture+    renderer+    tex+    Nothing+    (Just $ SDL.Rectangle (SDL.P $ SDL.V2 (fromIntegral x) (fromIntegral y)) dim)++drawSprite :: (SDLRenderer m, R m, MonadIO m) => (Config -> Animate.SpriteSheet key SDL.Texture Seconds) -> Maybe Color -> Float -> Animate.SpriteClip key -> (Int, Int) -> m ()+drawSprite ss outline scalar' clip (x,y) = do+  let scalar = pure (CFloat scalar')+  renderer <- asks cRenderer+  sheet <- asks (Animate.ssImage . ss)+  let scaleDown n = fmap round $ (fmap fromIntegral n) / scalar+  let scaleUp n = fmap round $ (fmap fromIntegral n) * scalar+  let clip'@(SDL.Rectangle _ dim) = rectFromClip clip+  let offset = offsetFromClip clip+  let loc = (+) <$> offset <*> (scaleDown $ V2 x y)+  case outline of+    Nothing -> return ()+    Just outline' -> liftIO $ Gfx.rectangle renderer (scaleUp loc) (scaleUp $ loc + dim) (fromColor outline')+  -- set scale for sprite+  liftIO $ SDL.rendererScale renderer $= scalar+  drawTexture+    renderer+    sheet+    (Just clip')+    (Just $ SDL.Rectangle (SDL.P loc) dim)+  -- reset scale from sprite+  liftIO $ SDL.rendererScale renderer $= (V2 1 1)++getSpriteAnimations :: R m => (Config -> Animate.SpriteSheet key SDL.Texture Seconds) -> m (Animations key)+getSpriteAnimations ss = asks (Animate.ssAnimations . ss)++rectFromClip :: Animate.SpriteClip key -> SDL.Rectangle CInt+rectFromClip Animate.SpriteClip{scX,scY,scW,scH} = SDL.Rectangle (SDL.P (V2 (num scX) (num scY))) (V2 (num scW) (num scH))+  where+    num = fromIntegral++offsetFromClip :: Animate.SpriteClip key -> V2 CInt+offsetFromClip Animate.SpriteClip{scOffset} = fromMaybe+  (V2 0 0)+  ((\(x,y) -> fromIntegral <$> V2 (-x) (-y)) <$> scOffset)
+ library/Animate/Preview/Resource.hs view
@@ -0,0 +1,109 @@+module Animate.Preview.Resource where++import qualified SDL+import qualified SDL.Font as Font+import qualified SDL.Image as Image+import qualified Animate+import qualified SDL.Raw.Video as Raw+import qualified SDL.Internal.Numbered as Numbered+import qualified Data.Map as Map+import Control.Monad (filterM)+import Data.Text (Text)+import Data.Maybe (catMaybes)+import Data.StateVar (($=))+import SDL.Vect+import System.IO.Error (catchIOError)+import Paths_animate_preview (getDataFileName)++data Resources = Resources+  { rFont :: Font.Font+  , rGlyphMap :: Map.Map Char Glyph+  , rGlyphSize :: Int+  }++data Glyph = Glyph+  { gChar :: Char+  , gMetrics :: (Int, Int, Int, Int, Int)+  , gTexture :: SDL.Texture+  }++-- | Produce a new 'SDL.Surface' based on an existing one, but+-- optimized for blitting to the specified 'SDL.PixelFormat'.+convertSurface :: SDL.Surface -> SDL.PixelFormat -> IO SDL.Surface+convertSurface (SDL.Surface s _) pixFmt = do+  fmt <- Raw.allocFormat (Numbered.toNumber pixFmt)+  surface <- SDL.Surface <$> Raw.convertSurface s fmt 0 <*> pure Nothing+  surface <$ Raw.freeFormat fmt++loadSurface' :: FilePath -> Maybe Animate.Color -> IO (Maybe SDL.Surface)+loadSurface' path alpha = do+  surface0' <- catchIOError+    (Just <$> Image.load path)+    (const $ return Nothing)+  case surface0' of+    Nothing -> return Nothing+    Just surface0 -> do+      surface <- convertSurface surface0 SDL.RGBA8888+      SDL.freeSurface surface0+      case alpha of+        Just (r,g,b) -> SDL.surfaceColorKey surface $= (Just $ V4 r g b 0x00)+        Nothing -> return ()+      return (Just surface)++alphaColorDef :: Animate.Color+alphaColorDef = (0xff,0x00,0xff)++createText :: Bool -> SDL.Renderer -> Font.Font -> Text -> IO SDL.Texture+createText highDpi ren font text = do+  Font.setHinting font Font.None+  Font.setOutline font (if highDpi then 4 else 2)+  outline <- Font.solid font (V4 0 0 0 0) text+  Font.setOutline font 0+  inline <- Font.solid font (V4 255 255 255 255) text+  _ <- SDL.surfaceBlit inline Nothing outline (Just $ SDL.P (if highDpi then 4 else 2))+  tex <- SDL.createTextureFromSurface ren outline+  SDL.freeSurface inline+  SDL.freeSurface outline+  return tex++createGlyph :: Bool -> SDL.Renderer -> Font.Font -> Char -> IO (Maybe Glyph)+createGlyph highDpi ren font ch = do+  metrics' <- Font.glyphMetrics font ch+  --+  case metrics' of+    Nothing -> return Nothing+    Just metrics@(xmin, xmax, _ymin, _ymax, _adv) -> do+      if (xmax - xmin > 0) || ch == ' '+        then do+          Font.setHinting font Font.None+          Font.setOutline font (if highDpi then 4 else 2)+          outline <- Font.solidGlyph font (V4 0 0 0 0) ch+          Font.setOutline font 0+          inline <- Font.solidGlyph font (V4 255 255 255 255) ch+          _ <- SDL.surfaceBlit inline Nothing outline (Just $ SDL.P (if highDpi then 4 else 2))+          tex <- SDL.createTextureFromSurface ren outline+          SDL.freeSurface inline+          SDL.freeSurface outline+          --+          return . Just $ Glyph ch metrics tex+        else return Nothing++loadResources :: Bool -> SDL.Renderer -> IO Resources+loadResources highDpi ren = do+  fileName <- getDataFileName "resource/ProggyClean.ttf"+  let glyphSize = if highDpi then 32 else 16+  font <- Font.load fileName glyphSize+  putStrLn "Font cache: filtering glyphs"+  availableChars <- filterM (Font.glyphProvided font) [minBound..maxBound]+  putStrLn "Font cache: creating textures"+  glyphs <- mapM (createGlyph highDpi ren font) (' ':availableChars)+  let glyphs' = map (\g -> (gChar g, g)) (catMaybes glyphs)+  return Resources+    { rFont = font+    , rGlyphMap = Map.fromList glyphs'+    , rGlyphSize = glyphSize+    }++freeResources :: Resources -> IO ()+freeResources r = do+  Font.free (rFont r)
+ library/Animate/Preview/Runner.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE TemplateHaskell #-}+module Animate.Preview.Runner where++import Control.Monad (unless)+import Control.Monad.State (gets)+import KeyState++import Animate.Preview.Config+import Animate.Preview.Logger+import Animate.Preview.Renderer+import Animate.Preview.Input+import Animate.Preview.Scene+import Animate.Preview.ManagerInput+import Animate.Preview.State+import Animate.Preview.Timer++mainLoop :: (R m, S m, Logger m, Renderer m, HasInput m, Scene m, Timer m) => m ()+mainLoop = do+  ticks <- startTicks+  updateScreenInfo+  drawSize <- gets vDrawSize+  background <- gets vBackground+  updateInput+  input <- getInput+  clearScreen+  drawBackground drawSize background+  sceneStep+  drawScreen+  let quit = iQuit input || ksStatus (iEscape input) == KeyStatus'Pressed+  delayTicks ticks+  unless quit mainLoop
+ library/Animate/Preview/SDLInput.hs view
@@ -0,0 +1,52 @@+module Animate.Preview.SDLInput where++import qualified SDL+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.State (gets)+import Linear++import Animate.Preview.State++keycodePressed :: SDL.Keycode -> SDL.EventPayload -> Bool+keycodePressed keycode event = case event of+  SDL.KeyboardEvent SDL.KeyboardEventData{keyboardEventKeysym = SDL.Keysym{keysymKeycode = code}, keyboardEventKeyMotion = motion, keyboardEventRepeat } ->+    code == keycode &&+    motion == SDL.Pressed &&+    not keyboardEventRepeat+  _ -> False++keycodeReleased :: SDL.Keycode -> SDL.EventPayload -> Bool+keycodeReleased keycode event = case event of+  SDL.KeyboardEvent SDL.KeyboardEventData{keyboardEventKeysym = SDL.Keysym{keysymKeycode = code}, keyboardEventKeyMotion = motion, keyboardEventRepeat } ->+    code == keycode &&+    motion == SDL.Released &&+    not keyboardEventRepeat+  _ -> False++class Monad m => SDLInput m where+  pollEventPayloads :: m [SDL.EventPayload]+  default pollEventPayloads :: MonadIO m => m [SDL.EventPayload]+  pollEventPayloads = liftIO $ map SDL.eventPayload <$> SDL.pollEvents++  getMousePos :: m (V2 Int)+  default getMousePos :: (S m, MonadIO m) => m (V2 Int)+  getMousePos = do+    drawSize <- gets vDrawSize+    winSize <- gets vWinSize+    SDL.P pos <- liftIO SDL.getAbsoluteMouseLocation+    let pos' = div <$> ((*) <$> drawSize <*> (fmap fromIntegral pos)) <*> winSize+    return $ fmap fromIntegral pos'++  getMouseClick :: m Bool+  default getMouseClick :: MonadIO m => m Bool+  getMouseClick = do+    test <- liftIO SDL.getMouseButtons+    return $ test (SDL.ButtonExtra 0)+    -- There's an offby one bug in `sdl2` which doesn't test SDL.ButtonLeft as expected+    -- It has been since fixed but not uploaded to hackage: https://github.com/haskell-game/sdl2/commit/eb7accdc2ae6349f262e758a93e5c62ee75d0e14++  getMouseMiddleClick :: m Bool+  default getMouseMiddleClick :: MonadIO m => m Bool+  getMouseMiddleClick = do+    test <- liftIO SDL.getMouseButtons+    return $ test (SDL.ButtonExtra 1) -- Same comment as above
+ library/Animate/Preview/SDLRenderer.hs view
@@ -0,0 +1,27 @@+module Animate.Preview.SDLRenderer where++import qualified SDL+import Data.StateVar (($=))+import Control.Monad.IO.Class (MonadIO(..))+import Foreign.C.Types+import SDL.Vect+import Control.Monad (void)++class Monad m => SDLRenderer m where+  presentRenderer :: SDL.Renderer -> m ()+  default presentRenderer :: MonadIO m => SDL.Renderer -> m ()+  presentRenderer = SDL.present++  clearRenderer :: SDL.Renderer -> m ()+  default clearRenderer :: MonadIO m => SDL.Renderer -> m ()+  clearRenderer ren = do+    SDL.rendererDrawColor ren $= V4 0x00 0x00 0x00 0xff+    liftIO $ void $ SDL.clear ren++  queryTexture :: SDL.Texture -> m SDL.TextureInfo+  default queryTexture ::  MonadIO m => SDL.Texture -> m SDL.TextureInfo+  queryTexture = SDL.queryTexture++  drawTexture :: SDL.Renderer -> SDL.Texture -> Maybe (SDL.Rectangle CInt)-> Maybe (SDL.Rectangle CInt) -> m ()+  default drawTexture :: MonadIO m => SDL.Renderer -> SDL.Texture -> Maybe (SDL.Rectangle CInt) -> Maybe (SDL.Rectangle CInt) -> m ()+  drawTexture renderer tex maybeClip maybeLoc = SDL.copy renderer tex maybeClip maybeLoc
+ library/Animate/Preview/Scalar.hs view
@@ -0,0 +1,43 @@+module Animate.Preview.Scalar where++data Scalar+  = Scalar'None+  | Scalar'Down Int+  | Scalar'Up Int+  deriving (Show, Eq)++scalarToSeconds :: Scalar -> Float+scalarToSeconds = \case+  Scalar'None -> 1+  Scalar'Down n -> 1 - (0.1 * fromIntegral n)+  Scalar'Up n -> 1 + (0.5 * fromIntegral n)++asSpeedString :: Scalar -> String+asSpeedString = \case+  Scalar'None -> "1x"+  Scalar'Down n -> "0." ++ show (10 - n) ++ "x"+  Scalar'Up n -> show (n `div` 2 + 1) ++ "." ++ (if mod n 2 == 0 then  "0" else "5") ++ "x"++scalarToSpriteScale :: Scalar -> Float+scalarToSpriteScale = \case+  Scalar'None -> 1+  Scalar'Down n -> 1 - (0.1 * fromIntegral n)+  Scalar'Up n -> 1 + (0.1 * fromIntegral n)++asScaleString :: Scalar -> String+asScaleString = \case+  Scalar'None -> "1x"+  Scalar'Down n -> "0." ++ show (10 - n) ++ "x"+  Scalar'Up n -> show (n `div` 10 + 1) ++ "." ++ show (mod n 10) ++ "x"++incrementScalar :: Int -> Scalar -> Scalar+incrementScalar upper = \case+  Scalar'None -> Scalar'Up 1+  Scalar'Up n -> Scalar'Up (min upper (n + 1))+  Scalar'Down n -> if n <= 1 then Scalar'None else Scalar'Down (n - 1)++decrementScalar :: Int -> Scalar -> Scalar+decrementScalar lower = \case+  Scalar'None -> Scalar'Down 1+  Scalar'Down n -> Scalar'Down (min lower (n + 1))+  Scalar'Up n -> if n <= 1 then Scalar'None else Scalar'Up (n - 1)
+ library/Animate/Preview/Scene.hs view
@@ -0,0 +1,309 @@+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.Frame+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+  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 +  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+  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 * 4)  $ toText $ concat ["Anim:"]+      drawText (ofsX, ofsY + lineSpacing * 5)  $ toText $ concat ["  Key:"]+      drawText (ofsX, ofsY + lineSpacing * 6)  $ toText $ concat ["  Time:"]+      drawText (ofsX, ofsY + lineSpacing * 7)  $ toText $ concat ["Pos:"]+      drawText (ofsX, ofsY + lineSpacing * 8)  $ toText $ concat ["  Frame:"]+      drawText (ofsX, ofsY + lineSpacing * 9)  $ toText $ concat ["  Time:"]+      drawText (ofsX, ofsY + lineSpacing * 10) $ toText $ concat ["  Points:"]+      drawText (ofsX, ofsY + lineSpacing * 11) $ toText $ concat ["  Size:"]+      drawText (ofsX, ofsY + lineSpacing * 12) $ 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 * 4)  $ toText $ concat ["Anim:"]+            drawText (ofsX, ofsY + lineSpacing * 5)  $ toText $ concat ["  Key: \"", fromText keyName, "\" ", show $ Animate.pKey pos, " [", show $ lTotalKeys loaded - 1, "]"]+            drawText (ofsX, ofsY + lineSpacing * 6)  $ toText $ concat ["  Time: ", show totalSeconds]+            drawText (ofsX, ofsY + lineSpacing * 7)  $ toText $ concat ["Pos:"]+            drawText (ofsX, ofsY + lineSpacing * 8)  $ toText $ concat ["  Frame: ", show $ Animate.pFrameIndex pos,  " [", show $ framesLen - 1, "]"]+            drawText (ofsX, ofsY + lineSpacing * 9)  $ toText $ concat ["  Time: ", show $ Animate.pCounter pos, " [", show $ frameDelay, "]"]+            drawText (ofsX, ofsY + lineSpacing * 10) $ toText $ concat ["  Points: (", show x0, ",", show y0, ") (", show x1, ",", show y1, ")"]+            drawText (ofsX, ofsY + lineSpacing * 11) $ toText $ concat ["  Size: (", show w, ",", show h, ")"]+            drawText (ofsX, ofsY + lineSpacing * 12) $ 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) ("Scale: " `mappend` toText (asScaleString scale))+    drawText (ofsX, ofsY + lineSpacing * 3) ("Accel: " `mappend` toText (asSpeedString accel))+  where+    lineSpacing' x = if x then 26 else 13+    ofsX = 6+    ofsY = 6
+ library/Animate/Preview/State.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ConstraintKinds #-}+module Animate.Preview.State where++import Control.Lens+import Control.Monad.State (MonadState)+import Linear++import Animate.Preview.Input+import Animate.Preview.Color+import Animate.Preview.Scalar+import Animate.Preview.Mode++data Vars = Vars+  { vInput :: Input+  , vBackground :: Mono+  , vOriginColor :: Maybe Color+  , vOutlineColor :: Maybe Color+  , vOrigin :: V2 Int+  , vAccel :: Scalar+  , vScale :: Scalar+  , vInfoShown :: Bool+  , vMode :: Mode+  , vDrawSize :: V2 Int+  , vWinSize :: V2 Int+  }++initVars :: V2 Int -> V2 Int -> Vars+initVars winSize drawSize = Vars+  { vInput = initInput+  , vBackground = Mono'Gray+  , vOriginColor = Just Color'Red+  , vOutlineColor = Just Color'Green+  , vOrigin = div <$> drawSize <*> 2+  , vAccel = Scalar'None+  , vScale = Scalar'None+  , vInfoShown = True+  , vMode = Mode'Playback+  , vWinSize = winSize+  , vDrawSize = drawSize+  }++makeClassy ''Vars++type S m = MonadState Vars m
+ library/Animate/Preview/Timer.hs view
@@ -0,0 +1,19 @@+module Animate.Preview.Timer where++import qualified SDL+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad (when)++import Animate.Preview.Frame++class Monad m => Timer m where+  startTicks :: m Int+  default startTicks :: MonadIO m => m Int+  startTicks = fromIntegral <$> liftIO SDL.ticks++  delayTicks :: Int -> m ()+  default delayTicks :: MonadIO m => Int -> m ()+  delayTicks start = liftIO $ do+    end <- fromIntegral <$> SDL.ticks+    let ms = (msps - (end -start) * fps) `div` fps+    when (ms > 0) (SDL.delay $ fromIntegral ms)
+ package.yaml view
@@ -0,0 +1,65 @@+name: animate-preview+version: '0.0.0'+github: jxv/animate-preview+license: BSD3+category: Game+synopsis: Preview tool for sprite animation+description: Preview tool for sprite animation+maintainer: Joe Vargas+copyright: 2018 Joe Vargas+data-files:+- resource/ProggyClean.ttf+extra-source-files:+- package.yaml+- README.md+- stack.yaml+ghc-options: -Wall+default-extensions:+- DuplicateRecordFields+- FlexibleContexts+- FlexibleInstances+- GeneralizedNewtypeDeriving+- LambdaCase+- NamedFieldPuns+- ScopedTypeVariables+- OverloadedStrings+- DefaultSignatures+- MultiParamTypeClasses+library:+  dependencies:+    - base >=4.7 && <5+    - aeson+    - animate+    - bytestring+    - containers+    - filepath+    - fsnotify+    - key-state+    - linear+    - lens+    - mtl+    - optparse-applicative+    - optparse-generic+    - random+    - safe+    - safe-exceptions+    - sdl2+    - sdl2-image+    - sdl2-ttf+    - sdl2-gfx+    - StateVar+    - text+    - text-conversions+    - vector+  source-dirs: library+executables:+  animate-preview:+    dependencies:+    - base >=4.7 && <5+    - animate-preview+    ghc-options:+    - -rtsopts+    - -threaded+    - -with-rtsopts=-N+    main: Main.hs+    source-dirs: executable
+ resource/ProggyClean.ttf view

binary file changed (absent → 41208 bytes)

+ stack.yaml view
@@ -0,0 +1,10 @@+resolver: lts-11.3+packages:+- '.'+extra-deps:+- 'sdl2-mixer-1.1.0'+- 'sdl2-ttf-2.0.2'+- 'animate-0.6.1'+- 'key-state-0.2.0'+flags: {}+extra-package-dbs: []