packages feed

reactivity-0.2.3.0: src/FRP/Reactivity/Hook.hs

{-# LANGUAGE Unsafe, GeneralizedNewtypeDeriving, DeriveFunctor, FlexibleInstances, Rank2Types, FunctionalDependencies, OverlappingInstances, UndecidableInstances #-}

module FRP.Reactivity.Hook (Resource(..), Hook,
-- | * Versions of UI functions that can be modified by hooks.
hCreate, hCreate',
-- | * Hook readers
getFilter, window, getResource, tellReturn, getReturns, getReturns1,
-- | * Running
ty, runHook) where

import Foreign.Ptr
import Control.Applicative
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.Trans
import Control.Monad
import Data.Monoid
import FRP.Reactivity.Combinators
import FRP.Reactivity.UI
import FRP.Reactivity.Draw
import Codec.BMP
import Graphics.Win32

newtype Hook r x m t = Hook { unHook :: WriterT (Event Window) (ReaderT (Window, r, x -> x) m) t } deriving (Monad, MonadPlus, MonadFix, Functor, Applicative, Alternative, MonadIO)

instance MonadTrans (Hook r x) where
	lift = Hook . lift . lift

instance (MonadPlus m) => Monoid (Hook r x m t) where
	mempty = mzero
	mappend = mplus

-- | Versions of UI functions that can be modified by hooks.
{-# INLINE hCreate #-}
hCreate :: (Monoid r, Resource r) => WndClass -> r -> Hook r (Event Message) Act t -> Hook r (Event Message) Act t
hCreate wndclass r hook = do
	parent <- window
	r2 <- getResource
	f <- getFilter
	w <- lift $ create parent wndclass (toBeh (r2 <> r))
	tellReturn (return w)
	(x, y) <- lift $ runReaderT (runWriterT (unHook hook)) (w { event = f (event w) }, r2, f)
	tellReturn y
	return x

{-# INLINE hCreate' #-}
hCreate' wndclass r = hCreate wndclass r window

-- | Filters are hooks modifying the event stream the caller sees.
{-# INLINE getFilter #-}
getFilter :: (Monad m) => Hook r x m (x -> x)
getFilter = Hook $ liftM (\(_, _, f) -> f) $ lift ask

-- | Resources are a way of augmenting the behaviors used by subsequent controls.
{-# INLINE getResource #-}
getResource :: (Monad m) => Hook r x m r
getResource = Hook $ liftM (\(_, r, _) -> r) $ lift ask

{-# INLINE tellReturn #-}
tellReturn r = Hook $ tell r

{-# INLINE getReturns #-}
getReturns :: (Monoid r, Monad m) => r -> (x -> x) -> Hook r x m t -> Hook r x m (t, Event Window)
getReturns r f h = do
	w <- window
	r2 <- getResource
	f2 <- getFilter
	lift $ runReaderT (runWriterT (unHook h)) (w, r <> r2, f . f2)

{-# INLINE getReturns1 #-}
getReturns1 r f h = do
	w <- window
	lift $ runReaderT (runWriterT (unHook h)) (w, r, f)

-- | 'window' gives the identity of the parent window.
{-# INLINE window #-}
window :: (Monad m) => Hook r x m Window
window = Hook $ liftM (\(w, _, _) -> w) $ lift ask

-- | Functional that fixes the usual type parameters.
{-# INLINE ty #-}
ty :: Hook (Behavior Appearance) (Event Message) Act t -> Hook (Behavior Appearance) (Event Message) Act t
ty = id

-- | Runs a procedure in the Hook monad transformer.
{-# INLINE runHook #-}
runHook ~(Hook h) = runReaderT (runWriterT h) (desktop, mempty, id)

err = error "FRP.Hook.toBeh: no start value"

-- | These are common ways of converting data to a behavior.
class Resource r where
	toBeh :: r -> Behavior Appearance

instance Resource (Behavior Appearance) where
	toBeh = id

instance Resource (Event Appearance) where
	toBeh = stepper err

instance Resource (Appearance, Event (Appearance -> Appearance)) where
	toBeh (x, e) = stepper x $ corec (\x f t -> (f x, f x, t)) x e

instance Resource Appearance where
	toBeh = pure

instance Resource (Draw ()) where
	toBeh d = pure (Appearance d "" (0, 0, 0, 0))

instance Resource BMP where
	toBeh = toBeh . mask 1 (0, 0)

instance Resource String where
	toBeh s = pure (Appearance (return ()) s (0, 0, 0, 0))

instance Monoid BMP where
	mempty = newBitmap (0, 0)
	mappend b b2 = fst $ onNewBitmap (wid `max` wid2, ht + ht2) $ do
		mask 1 (0, 0) b
		mask 1 (0, ht) b2 where
		(wid, ht) = askDims' b
		(wid2, ht2) = askDims' b2

instance Monoid Appearance where
	mempty = Appearance (return ()) "" (0, 0, 0, 0)
	mappend a a2 = Appearance (draw a >> draw a2)
		(text a ++ text a2)
		(x1 `max` x3, y1 `max` y3, x2 `max` x4, y2 `max` y4) where
		(x1, y1, x2, y2) = rect a
		(x3, y3, x4, y4) = rect a2

instance Monoid (Draw ()) where
	mempty = return ()
	mappend = (>>)