packages feed

reanimate-0.4.2.0: examples/voice_advanced.hs

#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ApplicativeDo #-}
module Main where

import           Control.Monad
import qualified Data.Text                     as T
import           Reanimate
import           Reanimate.Voice
import           Reanimate.Builtin.Documentation
import           Geom2D.CubicBezier                       ( QuadBezier(..)
                                                          , evalBezier
                                                          , Point(..)
                                                          )
import           Graphics.SvgTree                         ( ElementRef(..) )

transcript :: Transcript
transcript = loadTranscript "voice_advanced.txt"

main :: IO ()
main = reanimate $ sceneAnimation $ do
  bg <- newSpriteSVG $ mkBackgroundPixel rtfdBackgroundColor
  spriteZ bg (-100)
  newSpriteSVG_ $ mkGroup
    [withStrokeColor "black" $ mkLine (-screenWidth, 0) (screenWidth, 0)]

  centerTxt <- textHandler

  flashEffect
  circleEffect
  squareEffect
  finalEffect

  waitOn $ forM_ (transcriptWords transcript) $ \tword -> fork $ do
    wait (wordStart tword)
    writeVar centerTxt $ wordReference tword

  wait 2

wordDuration :: TWord -> Double
wordDuration tword = wordEnd tword - wordStart tword

--
finalEffect :: Scene s ()
finalEffect = fork $ do
  let begin  = findWord transcript ["final"] "circles"
      ends   = findWords transcript ["final"] "flash"
      path   = QuadBezier (Point 6 (-radius)) (Point 0 6) (Point (-6) (-radius))
      radius = 0.3
  wait (wordStart begin)
  ss <- fork $ replicateM 3 (circleSprite radius path <* wait 0.2)
  mapM_ (flip spriteZ (-1)) ss
  forM_ (zip ss ends) $ \(s, end) -> fork $ do
    spriteMap s flipXAxis
    wait (wordStart end - wordStart begin)
    destroySprite s

-- square effect
squareEffect :: Scene s ()
squareEffect = fork $ do
  let
    begin = findWord transcript [] "square"
    end   = findWord transcript ["middle"] "square"
    path =
      QuadBezier (Point 6 (-size / 2)) (Point 0 6) (Point (-6) (-size / 2))
    size = 1
  wait (wordStart begin)
  s <- squareSprite size path
  spriteMap s (rotate 180)
  spriteZ s (-1)
  wait (wordStart end + wordDuration end / 2 - wordStart begin)
  destroySprite s

-- circle effect
circleEffect :: Scene s ()
circleEffect = fork $ do
  let begin  = findWord transcript [] "circle"
      end    = findWord transcript ["middle"] "circle"
      path   = QuadBezier (Point 6 (-radius)) (Point 0 6) (Point (-6) (-radius))
      radius = 0.3
  wait (wordStart begin)
  s <- circleSprite radius path
  spriteZ s (-1)
  wait (wordStart end + wordDuration end / 2 - wordStart begin)
  destroySprite s

-- flash effect
flashEffect :: Scene s ()
flashEffect = forM_ (findWords transcript [] "flash") $ \flashWord -> fork $ do
  wait (wordStart flashWord)
  flash <- newSpriteSVG $ mkBackground "black"
  spriteTween flash (wordDuration flashWord)
    $ \t -> withGroupOpacity (fromToS 0 0.7 $ (powerS 2 . reverseS) t)
  wait (wordDuration flashWord)
  destroySprite flash

--------------------------------------------------------------------------
-- Helpers and sprites

textHandler :: Scene s (Var s T.Text)
textHandler = simpleVar render T.empty
 where
  render txt =
    let txtSvg      = translate 0 (-0.25) $ centerX $ latex txt
        activeWidth = svgWidth txtSvg + 0.5
    in  mkGroup
          [ withStrokeWidth 0 $ withFillColorPixel rtfdBackgroundColor $ mkRect
            activeWidth
            1
          , txtSvg
          , withStrokeColor "black"
            $ mkLine (activeWidth / 2, 0.5) (activeWidth / 2, -0.5)
          , withStrokeColor "black"
            $ mkLine (-activeWidth / 2, 0.5) (-activeWidth / 2, -0.5)
          ]

circleSprite :: Double -> QuadBezier Double -> Scene s (Sprite s)
circleSprite radius path = newSprite $ do
  t <- spriteT
  d <- spriteDuration
  pure
    $ let Point x y = evalBezier path (t / d)
      in  mkGroup
            [ mkClipPath "circle-mask"
            $ removeGroups
            $ translate 0 (screenHeight / 2)
            $ withFillColorPixel rtfdBackgroundColor
            $ mkRect screenWidth screenHeight
            , withClipPathRef (Ref "circle-mask") $ translate x y $ mkCircle
              radius
            ]

squareSprite :: Double -> QuadBezier Double -> Scene s (Sprite s)
squareSprite size path = newSprite $ do
  t <- spriteT
  d <- spriteDuration
  pure
    $ let Point x y = evalBezier path (t / d)
      in  mkGroup
            [ mkClipPath "square-mask"
            $ removeGroups
            $ translate 0 (screenHeight / 2)
            $ withFillColorPixel rtfdBackgroundColor
            $ mkRect screenWidth screenHeight
            , withClipPathRef (Ref "square-mask")
            $ translate x y
            $ rotate (t / d * 360)
            $ withFillOpacity 0
            $ withStrokeColor "black"
            $ mkRect size size
            ]