packages feed

reanimate-1.1.3.0: examples/expression.hs

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

import           Codec.Picture                   (PixelRGBA8 (PixelRGBA8))
import           Control.Arrow                   ((&&&))
import           Control.Lens                    ((.~))
import           Control.Monad                   (when)
import           Data.Foldable                   (forM_)
import qualified Data.Text                       as T
import           Reanimate
import           Reanimate.Builtin.Documentation (docEnv)
import           Reanimate.LaTeX                 (mathChunks)
import           Reanimate.Scene

import           Data.Bifoldable                 (Bifoldable (..), biforM_)
import           Data.Bifunctor                  (Bifunctor (..))
import           Data.Bitraversable              (Bitraversable (..))

data Expr op v
  = Var v
  | BinOp (Expr op v) op (Expr op v)
  | Paren op (Expr op v) op
  deriving (Show, Eq)

data Op = Add | Minus | LeftParen | RightParen deriving (Show, Eq)

oNewExpr :: Scene s (Expr (Op, Object s SVG) (T.Text, Object s SVG))
oNewExpr = bothAlongside (mapM (oNew . translate (-4) 0 . scale 2) . mathChunks)
         $ bimap (id &&& op2LaTeX) (id &&& id) expr
  where bothAlongside f = fmap runBiAlongside . fmap runBoth . f . Both . BiAlongside
        expr = Var "x" `minus` paren (Var "y" `add` Var "x")

main :: IO ()
main = reanimate $ docEnv $ mapA (withFillOpacity 1 . withStrokeWidth 0) $ scene $ do
  expr <- oNewExpr
  forM_ (Both $ BiAlongside expr) (`oShowWith` oDraw)
  let changeColor t = oContext .~ withFillColorPixel (PixelRGBA8 (round (t * 255)) 0 0 255)
  let changeColorRev t = changeColor (1 - t)
  waitOn $ biforM_ expr pure $ \(x, o) ->
    when (x == "x") $ fork $ do
      oTween o 1 changeColor
      oTween o 1 changeColorRev
  forM_ (Both $ BiAlongside expr) (fork . (`oHideWith` oFadeOut))

-- Some supportive functions.

add, minus :: Expr Op v -> Expr Op v -> Expr Op v
add x y = BinOp x Add y
minus x y = BinOp x Minus y

paren :: Expr Op v -> Expr Op v
paren x = Paren LeftParen x RightParen

op2LaTeX :: Op -> T.Text
op2LaTeX Add        = "+"
op2LaTeX Minus      = "-"
op2LaTeX LeftParen  = "("
op2LaTeX RightParen = ")"

-- Some supportive types and instances.

instance Bifunctor Expr where
  bimap _ g (Var x)        = Var (g x)
  bimap f g (BinOp x op y) = BinOp (bimap f g x) (f op) (bimap f g y)
  bimap f g (Paren l x r)  = Paren (f l) (bimap f g x) (f r)

instance Bifoldable Expr where
  bifoldMap _ g (Var x)        = g x
  bifoldMap f g (BinOp x op y) = bifoldMap f g x <> f op <> bifoldMap f g y
  bifoldMap f g (Paren l x r)  = f l <> bifoldMap f g x <> f r

instance Bitraversable Expr where
  bitraverse _ g (Var x)        = Var <$> g x
  bitraverse f g (BinOp x op y) = BinOp <$> bitraverse f g x <*> f op <*> bitraverse f g y
  bitraverse f g (Paren l x r)  = Paren <$> f l <*> bitraverse f g x <*> f r

-- | Given a bifunctor @f@, make a functor @Both f@ where @Both f a ~ f a a@.
newtype Both f a = Both { runBoth :: f a a }

instance Bifunctor p => Functor (Both p) where
  fmap f = Both . bimap f f . runBoth

instance Bifoldable p => Foldable (Both p) where
  foldMap f = bifoldMap f f . runBoth

instance Bitraversable p => Traversable (Both p) where
  traverse f = fmap Both . bitraverse f f . runBoth

-- | Make a bifunctor @f (a, -) (b, -)@ (namely @BiAlongside a b f@) out of @f@.
newtype BiAlongside a b f x y = BiAlongside { runBiAlongside :: f (a, x) (b, y) }

instance Bifunctor p => Bifunctor (BiAlongside a b p) where
  bimap f g = BiAlongside . bimap (second f) (second g) . runBiAlongside

instance Bifoldable p => Bifoldable (BiAlongside a b p) where
  bifoldMap f g = bifoldMap (f . snd) (g . snd) . runBiAlongside

instance Bitraversable p => Bitraversable (BiAlongside a b p) where
  bitraverse f g = fmap BiAlongside . bitraverse (traverse f) (traverse g) . runBiAlongside