packages feed

zwirn-0.2.3.1: app/zwirnmill/Animation.hs

module Animation where

import Brick (Widget)
import qualified Brick.Animation as A
import Brick.BChan (writeBChan)
import Brick.Types (EventM)
import Brick.Widgets.Core (fill, hBox, txt, vBox)
import Control.Exception (IOException, try)
import Control.Monad (forM)
import Control.Monad.RWS
import Data.List (sort)
import qualified Data.Map as Map
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Editor.Core (OutputType (..))
import Graphics.UI.TinyFileDialogs (inputBox, selectFolderDialog)
import Lens.Micro (Lens', lens, (&), (.~), (^.))
import System.Directory (listDirectory)
import System.FilePath ((</>))
import qualified Text.Read as T
import UI.Core (AnimationConfig (..), AppEvent (..), AppState (..), Content (..), Name (..), Window (Window))

drawAnimationWindow :: AppState -> (Int, Int) -> Maybe (A.Animation AppState Name) -> Widget Name
drawAnimationWindow as (_, sy) = A.renderAnimation (const $ vBox $ replicate sy (fill ' ')) as

loadClipFromDirectory :: FilePath -> EventM Name AppState (Maybe (A.Clip AppState Name))
loadClipFromDirectory dir = do
  mfiles <- liftIO (try (listDirectory dir) :: IO (Either IOException [FilePath]))
  case mfiles of
    Left _ -> return Nothing
    Right unfiles -> do
      let files = sort unfiles
      frames <- fmap concat $ forM files $ \f -> liftIO $ do
        result <- try (TIO.readFile (dir </> f)) :: IO (Either IOException T.Text)
        case result of
          Left _ -> return []
          Right content -> return [content]
      let toClip c st = case Map.lookup Animator (asWindows st) of
            Just (Window _ (_, sy) _ _ _) -> vBox $ map (\l -> hBox [txt l, fill ' ']) ls ++ rest
              where
                ls = T.lines c
                rest = replicate (max 0 (sy - length ls)) $ fill ' '
            Nothing -> vBox $ map txt $ T.lines c
      case frames of
        [] -> do
          chan <- gets asChan
          liftIO $ writeBChan chan (UpdateOutput (OutputError, "Failed to load animation."))
          return Nothing
        _ -> return $ Just $ A.newClip (map toClip frames)

toggleAnimation :: EventM Name AppState ()
toggleAnimation = do
  mgr <- gets asAnimationManager
  wm <- gets asWindows
  case Map.lookup Animator wm of
    Just (Window _ _ (AnimationContent Nothing (AnimationConfig i p)) _ _) -> do
      mc <- loadClipFromDirectory p
      case mc of
        Just c -> A.startAnimation mgr c (fromIntegral i) A.Loop animatorWindowL
        Nothing -> return ()
    Just (Window _ _ (AnimationContent (Just a) _) _ _) -> A.stopAnimation mgr a
    _ -> return ()

startAnimation :: EventM Name AppState ()
startAnimation = do
  mgr <- gets asAnimationManager
  wm <- gets asWindows
  case Map.lookup Animator wm of
    Just (Window _ _ (AnimationContent _ (AnimationConfig i p)) _ _) -> do
      mc <- loadClipFromDirectory p
      case mc of
        Just c -> A.startAnimation mgr c (fromIntegral i) A.Loop animatorWindowL
        Nothing -> return ()
    _ -> return ()

stopAnimation :: EventM Name AppState ()
stopAnimation = do
  mgr <- gets asAnimationManager
  wm <- gets asWindows
  case Map.lookup Animator wm of
    Just (Window _ _ (AnimationContent (Just a) _) _ _) -> A.stopAnimation mgr a
    _ -> return ()

asWindowsL :: Lens' AppState (Map.Map Name Window)
asWindowsL = lens asWindows (\st newMap -> st {asWindows = newMap})

animatorWindowL :: Lens' AppState (Maybe (A.Animation AppState Name))
animatorWindowL = lens getter setter
  where
    getter st = case Map.lookup Animator (st ^. asWindowsL) of
      Just (Window _ _ (AnimationContent a _) _ _) -> a
      _ -> Nothing

    setter st newAnim =
      let alt (Just (Window x y (AnimationContent _ conf) z l)) = Just $ Window x y (AnimationContent newAnim conf) z l
          alt x = x
       in st & asWindowsL .~ Map.alter alt Animator (st ^. asWindowsL)

changeAnimation :: EventM Name AppState ()
changeAnimation = do
  mPath <- liftIO $ selectFolderDialog "Choose a folder containing animation frames" ""
  case mPath of
    Just path -> do
      let alt (Just (Window x y (AnimationContent a (AnimationConfig i _)) z l)) = Just $ Window x y (AnimationContent a (AnimationConfig i (T.unpack path))) z l
          alt _ = Nothing
      stopAnimation
      modify $ \as -> as {asWindows = Map.alter alt Animator $ asWindows as}
      startAnimation
    Nothing -> return ()

changeFramerate :: EventM Name AppState ()
changeFramerate = do
  mframe <- ((T.readMaybe . T.unpack) =<<) <$> liftIO (inputBox "" "Choose a framerate in milliseconds" (Just "150"))
  case mframe of
    Just frame -> do
      let alt (Just (Window x y (AnimationContent a (AnimationConfig _ p)) z l)) = Just $ Window x y (AnimationContent a (AnimationConfig frame p)) z l
          alt _ = Nothing
      stopAnimation
      modify $ \as -> as {asWindows = Map.alter alt Animator $ asWindows as}
      startAnimation
    Nothing -> return ()