packages feed

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

{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}

{-# HLINT ignore "Use camelCase" #-}

module Brillo.Internals.Interface.Interact (interactWithBackend)
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.Backend
import Brillo.Internals.Interface.Callback qualified as Callback
import Brillo.Internals.Interface.Event
import Brillo.Internals.Interface.ViewState.Reshape
import Brillo.Internals.Interface.Window
import Brillo.Rendering
import Data.IORef
import System.Mem


interactWithBackend ::
  (Backend a) =>
  -- | Initial state of the backend.
  a ->
  -- | Display config.
  Display ->
  -- | Background color.
  Color ->
  -- | The initial world.
  world ->
  -- | A function to produce the current picture.
  (world -> IO Picture) ->
  -- | A function to handle input events.
  (Event -> world -> IO world) ->
  -- | Eat the controller
  (Controller -> IO ()) ->
  IO ()
interactWithBackend
  backend
  displayMode
  background
  worldStart
  worldToPicture
  worldHandleEvent
  eatController =
    do
      viewSR <- newIORef viewStateInit
      worldSR <- newIORef worldStart
      renderS <- initState
      renderSR <- newIORef renderS

      let displayFun backendRef = do
            world <- readIORef worldSR
            picture <- worldToPicture world

            renderS' <- readIORef renderSR
            viewState <- readIORef viewSR
            let viewPort = viewStateViewPort viewState

            windowSize <- getWindowDimensions backendRef

            displayPicture
              windowSize
              background
              renderS'
              (viewPortScale viewPort)
              (applyViewPortToPicture viewPort picture)

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

      let callbacks =
            [ Callback.Display displayFun
            , -- Viewport control with mouse
              callback_keyMouse worldSR viewSR worldHandleEvent
            , callback_motion worldSR worldHandleEvent
            , callback_drop worldSR worldHandleEvent
            , callback_reshape worldSR worldHandleEvent
            ]

      -- When we create the window we can pass a function to get a
      -- reference to the backend state. Using this we make a controller
      -- so the client can control the window asynchronously.
      createWindow backend displayMode background callbacks $
        \backendRef ->
          eatController $
            Controller
              { controllerSetRedraw =
                  do 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
              }


-- | Callback for KeyMouse events.
callback_keyMouse ::
  -- | ref to world state
  IORef world ->
  IORef ViewState ->
  -- | fn to handle input events
  (Event -> world -> IO world) ->
  Callback
callback_keyMouse worldRef viewRef eventFn =
  KeyMouse (handle_keyMouse worldRef viewRef eventFn)


handle_keyMouse ::
  IORef a ->
  t ->
  (Event -> a -> IO a) ->
  KeyboardMouseCallback
handle_keyMouse worldRef _ eventFn backendRef key keyState keyMods pos =
  do
    ev <- keyMouseEvent backendRef key keyState keyMods pos
    world <- readIORef worldRef
    world' <- eventFn ev world
    writeIORef worldRef world'
    postRedisplay backendRef


-- | Callback for Motion events.
callback_motion ::
  -- | ref to world state
  IORef world ->
  -- | fn to handle input events
  (Event -> world -> IO world) ->
  Callback
callback_motion worldRef eventFn =
  Motion (handle_motion worldRef eventFn)


handle_motion ::
  IORef a ->
  (Event -> a -> IO a) ->
  MotionCallback
handle_motion worldRef eventFn backendRef pos =
  do
    ev <- motionEvent backendRef pos
    world <- readIORef worldRef
    world' <- eventFn ev world
    writeIORef worldRef world'
    postRedisplay backendRef


callback_drop ::
  IORef world ->
  (Event -> world -> IO world) ->
  Callback
callback_drop worldRef eventFn =
  Drop (handle_drop worldRef eventFn)


handle_drop ::
  IORef a ->
  (Event -> a -> IO a) ->
  DropCallback
handle_drop worldRef eventFn backendRef paths = do
  ev <- dropEvent backendRef paths
  world <- readIORef worldRef
  world' <- eventFn ev world
  writeIORef worldRef world'


-- | Callback for Handle reshape event.
callback_reshape ::
  IORef world ->
  (Event -> world -> IO world) ->
  Callback
callback_reshape worldRef eventFN =
  Reshape (handle_reshape worldRef eventFN)


handle_reshape ::
  IORef world ->
  (Event -> world -> IO world) ->
  ReshapeCallback
handle_reshape worldRef eventFn backendRef (width, height) =
  do
    world <- readIORef worldRef
    world' <- eventFn (EventResize (width, height)) world
    writeIORef worldRef world'
    viewState_reshape backendRef (width, height)
    postRedisplay backendRef