packages feed

brillo-2.0.0: Brillo/Internals/Interface/Animate.hs

module Brillo.Internals.Interface.Animate (animateWithBackendIO)
where

import Brillo.Data.Color
import Brillo.Data.Controller
import Brillo.Data.Picture
import Brillo.Data.ViewPort
import Brillo.Data.ViewState
import Brillo.Internals.Interface.Animate.State qualified as AN
import Brillo.Internals.Interface.Animate.Timing
import Brillo.Internals.Interface.Backend
import Brillo.Internals.Interface.Callback qualified as Callback
import Brillo.Internals.Interface.Common.Exit
import Brillo.Internals.Interface.ViewState.KeyMouse
import Brillo.Internals.Interface.ViewState.Motion
import Brillo.Internals.Interface.ViewState.Reshape
import Brillo.Internals.Interface.Window
import Brillo.Rendering
import Control.Monad
import Data.IORef
import GHC.Float (double2Float)
import System.Mem


animateWithBackendIO ::
  (Backend a) =>
  -- | Initial State of the backend
  a ->
  -- | Whether to allow the image to be panned around.
  Bool ->
  -- | Display mode.
  Display ->
  -- | Background color.
  Color ->
  {-| Function to produce the next frame of animation.
    It is passed the time in seconds since the program started.
  -}
  (Float -> IO Picture) ->
  -- | Eat the controller.
  (Controller -> IO ()) ->
  IO ()
animateWithBackendIO
  backend
  pannable
  display
  backColor
  frameOp
  eatController =
    do
      --
      viewSR <- newIORef viewStateInit
      animateSR <- newIORef AN.stateInit
      renderS_ <- initState
      renderSR <- newIORef renderS_

      let displayFun backendRef = do
            -- extract the current time from the state
            timeS <- animateSR `getsIORef` AN.stateAnimateTime

            -- call the user action to get the animation frame
            picture <- frameOp (double2Float timeS)

            renderS <- readIORef renderSR
            portS <- viewStateViewPort <$> readIORef viewSR

            windowSize <- getWindowDimensions backendRef

            -- render the frame
            displayPicture
              windowSize
              backColor
              renderS
              (viewPortScale portS)
              (applyViewPortToPicture portS picture)

            -- perform GC every frame to try and avoid long pauses
            performGC

      let callbacks =
            [ Callback.Display (animateBegin animateSR)
            , Callback.Display displayFun
            , Callback.Display (animateEnd animateSR)
            , Callback.Idle (\s -> postRedisplay s)
            , callback_exit ()
            , callback_viewState_motion viewSR
            , callback_viewState_reshape
            ]
              ++ ( if pannable
                     then [callback_viewState_keyMouse viewSR]
                     else []
                 )

      createWindow backend display backColor callbacks $
        \backendRef ->
          eatController $
            Controller
              { controllerSetRedraw =
                  postRedisplay backendRef
              , controllerModifyViewPort =
                  \modViewPort ->
                    do
                      viewState <- readIORef viewSR
                      port' <- modViewPort $ viewStateViewPort viewState
                      let viewState' = viewState{viewStateViewPort = port'}
                      writeIORef viewSR viewState'
                      postRedisplay backendRef
              , controllerSetCursor =
                  \cursorShape ->
                    setCursor backendRef cursorShape
              }


getsIORef :: IORef a -> (a -> r) -> IO r
getsIORef ref fun =
  liftM fun $ readIORef ref