packages feed

rhine-gloss-1.4: src/FRP/Rhine/Gloss/Pure/Combined.hs

{-# LANGUAGE RecordWildCards #-}

{- | A pure @gloss@ backend for Rhine,
with separated event and simulation loop.

To run pure Rhine apps with @gloss@,
write a signal network ('SN') in the 'GlossCombinedClock' and use 'flowGloss'.
As an easy starter, you can use the helper function 'buildGlossRhine'.
-}
module FRP.Rhine.Gloss.Pure.Combined where

-- rhine
import FRP.Rhine

-- rhine-gloss
import FRP.Rhine.Gloss.Common
import FRP.Rhine.Gloss.Pure

{- | The overall clock of a pure @rhine@ 'SN' that can be run by @gloss@.
   It is combined of two subsystems, the event part and the simulation part.
   @a@ is the type of subevents that are selected.
-}
type GlossCombinedClock a =
  SequentialClock
    (GlossEventClock a)
    GlossSimulationClock

-- ** Events

-- | The clock that ticks whenever a specific @gloss@ event occurs.
type GlossEventClock a = SelectClock GlossClock a

{- | Select the relevant events by converting them to @Just a@,
   and the irrelevant ones to 'Nothing'.
-}
glossEventSelectClock ::
  (Event -> Maybe a) ->
  GlossEventClock a
glossEventSelectClock selector =
  SelectClock
    { mainClock = GlossClock
    , select = (>>= selector)
    }

-- | Tick on every event.
glossEventClock :: GlossEventClock Event
glossEventClock = glossEventSelectClock Just

-- ** Simulation

-- | The clock that ticks for every @gloss@ simulation step.
type GlossSimulationClock = SelectClock GlossClock ()

glossSimulationClock :: GlossSimulationClock
glossSimulationClock = SelectClock {..}
  where
    mainClock = GlossClock
    select (Just _event) = Nothing
    select Nothing = Just ()

-- * Signal networks

{- |
The type of a valid 'Rhine' that can be run by @gloss@,
if you chose to separate events and simulation into two subsystems.
@a@ is the type of subevents that are selected.

All painting has to be done in 'GlossM', e.g. via the 'paint' method.

Typically, such a 'Rhine' is built something like this:

@
-- | Select only key press events
myEventClock :: GlossEventClock Key
myEventClock = glossEventSelectClock selector
  where
    selector (EventKey key _ _ _) = Just key
    selector _ = Nothing

myEventSubsystem :: ClSF GlossM GlossEventClock () MyType
myEventSubsystem = ...

mySim :: ClSF GlossM GlossSimulationClock [MyType] ()
mySim = ...

myGlossRhine :: GlossRhine a
myGlossRhine
  = myEventSubsystem @@ myEventClock >-- collect --> mySim @@ glossSimulationClock
@
-}
type GlossRhine a = Rhine GlossM (GlossCombinedClock a) () ()

{- | For most applications, it is sufficient to implement
a single signal function
that is called with a list of all relevant events
that occurred in the last tick.
-}
buildGlossRhine ::
  -- | The event selector
  (Event -> Maybe a) ->
  -- | The 'ClSF' representing the game loop.
  ClSF GlossM GlossSimulationClock [a] () ->
  GlossRhine a
buildGlossRhine selector clsfSim =
  timeInfoOf tag @@ glossEventSelectClock selector
    >-- collect
    --> clsfSim @@ glossSimulationClock