keid-frp-banana-0.1.1.0: src/Engine/ReactiveBanana/Course.hs
{-# LANGUAGE RecursiveDo #-}
{- | A process that is triggered, stepped for a while, then finished.
Useful to drive animations and filter other events and behaviors.
@
-- Set up a 1 second countdown
(startingE, startedE, starting) <-
Course.setup (startE $> 1.0) $
tickE $> \old ->
if old > dt then
Right (old - dt)
else
Left ()
-- Prevent click events after starting the countdown
let clicks = fmap (Course.whenIdle starting) allClicks
@
-}
module Engine.ReactiveBanana.Course
( Course(..)
, setup
-- * General state-aware event filters
, when
, unless
-- * Course state event filters
, whenIdle
, whenActive
, whenFinished
-- * Course state predicates
, isIdle
, isActive
, isFinished
) where
import RIO hiding (when, unless)
import Data.Maybe (fromJust)
import Data.These (These(..))
import Reactive.Banana qualified as RB
import Reactive.Banana.Frameworks qualified as RBF
data Course a
= Idle -- ^ Waiting for a trigger event
| Active a -- ^ Processing step events
| Finished -- ^ A final event has fired
deriving (Eq, Ord, Show, Functor)
setup
:: RB.Event a -- ^ Trigger event
-> RB.Event (a -> Either final a) -- ^ Step event
-> RBF.MomentIO (RB.Event a, RB.Event final, RB.Behavior (Course a)) -- ^ (active event, finished event, current state)
setup triggerE stepE = mdo
(e, b) <- RB.mapAccum Idle $
fmap dispatch $
RB.merge triggerE (whenActive b stepE)
(activeE, fireActive) <- RBF.newEvent
RBF.reactimate $ (e RB.@> b) <&> \case
Active a ->
fireActive a
_ ->
pure ()
finishedE <- RB.once $
fmap fromJust $ RB.filterE isJust e
pure (activeE, finishedE, b)
where
dispatch = \case
This initial -> \case
Idle ->
(Nothing, Active initial)
Active{} ->
error "trigger not filtered when active"
Finished ->
error "trigger not filtered when finished"
That step -> \case
Idle ->
error "tick not filtered when idle"
Active current ->
case step current of
Left done ->
(Just done, Finished)
Right next ->
(Nothing, Active next)
Finished ->
error "tick not filtered when finished"
These{} ->
error "tick happened on top of trigger"
when :: (Course a -> Bool) -> RB.Behavior (Course a) -> RB.Event e -> RB.Event e
when pred course =
RB.filterApply (course <&> const . pred)
unless :: (Course a -> Bool) -> RB.Behavior (Course a) -> RB.Event e -> RB.Event e
unless pred course =
RB.filterApply (course <&> const . not . pred)
whenIdle :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenIdle = when isIdle
whenActive :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenActive = when isActive
whenFinished :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenFinished = when isFinished
isIdle :: Course a -> Bool
isIdle = \case
Idle -> True
_ -> False
isActive :: Course a -> Bool
isActive = \case
Active{} -> True
_ -> False
isFinished :: Course a -> Bool
isFinished = \case
Finished -> True
_ -> False