keid-frp-banana-0.1.2.1: src/Engine/ReactiveBanana/Widget.hs
module Engine.ReactiveBanana.Widget
( Widget(..)
, Widget'
, wrap
, Instance(..)
, install
-- * Combinators
, drawB
, draw_
, collectB
, clumpWithB
, clumpWith
-- ** These
, clump
, clumpB
, attachLeft
, attachRight
) where
import RIO
import Data.These (These(..))
import Data.These.Combinators (justHere, justThere)
import Reactive.Banana qualified as RB
import Reactive.Banana.Frameworks qualified as RBF
import Engine.ReactiveBanana.Utils (delay, ($>>))
-- * Construction
-- | A composable pair of actions for planning and running stages.
data Widget e b = Widget
{ plug :: RBF.MomentIO (RB.Event e, RB.Behavior b)
, draw :: IO ()
}
-- | A "Widget" that doesn't transform the event it produces.
type Widget' a = Widget a a
instance Semigroup b => Semigroup (Widget () b) where
a <> b = clumpWithB (<>) a b
instance Semigroup b => Semigroup (Widget Void b) where
a <> b = clumpWith absurd absurd absurd (<>) a b
-- | Widget is covariant functor over its "output" behavior.
instance Functor (Widget e) where
fmap f w = Widget
{ plug = do
(e, b) <- w.plug
pure (e, fmap f b)
, draw = w.draw
}
-- | Widget is covariant functor over its "output" events and behavior.
instance Bifunctor Widget where
bimap f g w = Widget
{ plug = do
(e, b) <- w.plug
pure (fmap f e, fmap g b)
, draw =
w.draw
}
-- | Like 'bimap', but wraps draw function too.
--
-- Parent @draw@ can be called at wrapper discretion.
wrap
:: (RB.Event e -> RB.Event i)
-> (RB.Behavior b -> RB.Behavior c)
-> (IO () -> IO ())
-> Widget e b
-> Widget i c
wrap f g c w = Widget
{ plug = do
(e, b) <- w.plug
pure (f e, g b)
, draw =
c w.draw
}
-- | A widget without events of its own, sampling external behavior.
drawB
:: RB.Behavior b
-> IO ()
-> Widget Void b
drawB b d = Widget
{ plug = pure (RB.never, b)
, draw = d
}
draw_ :: IO () -> Widget Void ()
draw_ = drawB mempty
collectB
:: Traversable t
=> t (RBF.MomentIO (Widget e b))
-> RBF.MomentIO (Widget () (t b))
collectB ws = do
widgets <- sequenceA ws
pure Widget
{ plug = do
plugs <- traverse (.plug) widgets
event <- delay $ foldr (($>>) . fst) RB.never plugs
pure
( event
, traverse snd plugs
)
, draw =
traverse_ (.draw) widgets
}
clumpWithB
:: (lb -> rb -> b)
-> Widget le lb
-> Widget re rb
-> Widget () b
clumpWithB =
clumpWith (const ()) (const ()) (\_ _ -> ())
clumpWith
:: (ae -> ce)
-> (be -> ce)
-> (ae -> be -> ce)
-> (ab -> bb -> cb)
-> Widget ae ab
-> Widget be bb
-> Widget ce cb
clumpWith me1 me2 me12 mb a b = Widget
{ plug = do
(ae, ab) <- a.plug
(be, bb) <- b.plug
pure
( RB.mergeWith me1 me2 me12 ae be
, liftA2 mb ab bb
)
, draw =
a.draw <> b.draw
}
attachLeft
:: Widget ae ab
-> Widget be bb
-> Widget ae ab
attachLeft l r =
wrap filterHere (fmap fst) id $
clump l r
attachRight
:: Widget ae ab
-> Widget be bb
-> Widget be bb
attachRight l r =
wrap filterThere (fmap snd) id $
clump l r
clump
:: Widget ae ab
-> Widget b bb
-> Widget (These ae b) (ab, bb)
clump = clumpB (,)
clumpB
:: (ab -> bb -> b)
-> Widget ae ab
-> Widget be bb
-> Widget (These ae be) b
clumpB = clumpWith This That These
filterHere :: RB.Event (These a b) -> RB.Event a
filterHere = RB.filterJust . fmap justHere
filterThere :: RB.Event (These a b) -> RB.Event b
filterThere = RB.filterJust . fmap justThere
-- * Running
data Instance e b = Instance
{ event :: RB.Event e
, current :: RB.Behavior b
, draw :: IO ()
}
deriving (Functor)
install :: RBF.MomentIO (Widget e b) -> RBF.MomentIO (Instance e b)
install action = do
widget <- action
(e, b) <- widget.plug
pure Instance
{ event = e
, current = b
, draw = widget.draw
}