packages feed

hgeometry-0.7.0.0: interactive/RenderUtil.hs

{-# LANGUAGE OverloadedLabels #-}
module RenderUtil where

import           Control.Monad.IO.Class (MonadIO)
import           Control.Monad.Trans.Reader (runReaderT)
import           Data.GI.Base
import           Data.GI.Base.Signals (SignalInfo, HaskellCallbackType)
import           Data.IORef
import qualified Data.Text as T
import           Foreign.Ptr (castPtr)
import qualified GI.Cairo as GI.Cairo
import qualified GI.Gtk as Gtk
import           Graphics.Rendering.Cairo
import           Graphics.Rendering.Cairo.Canvas (Canvas)
import qualified Graphics.Rendering.Cairo.Canvas as Canvas
import qualified Graphics.Rendering.Cairo.Internal as Cairo.Internal
import           Graphics.Rendering.Cairo.Types (Cairo(Cairo))
import           Linear.V2 (V2(..))
import           Reactive.Banana
import           Reactive.Banana.Frameworks
import           Reactive.Banana.GI.Gtk




-- | This function bridges gi-cairo with the hand-written cairo
-- package. It takes a `GI.Cairo.Context` (as it appears in gi-cairo),
-- and a `Render` action (as in the cairo lib), and renders the
-- `Render` action into the given context.
renderWithContext      :: MonadIO m => GI.Cairo.Context -> Render a -> m a
renderWithContext ct r = liftIO $ withManagedPtr ct $ \p ->
                         runReaderT (Cairo.Internal.runRender r) (Cairo (castPtr p))


renderCanvas           :: MonadIO m => GI.Cairo.Context -> V2 Double -> Canvas a -> m a
renderCanvas ct size c = liftIO $ Canvas.withRenderer (renderWithContext ct) size c


showT :: Show a => a -> T.Text
showT = T.pack . show

-- | Get an 'Reactive.Banana.Event' from
-- a 'Data.GI.Base.Signals.SignalProxy' that produces one argument.
signalE1'
    ::
        ( HaskellCallbackType info ~ (a -> IO Bool)
        , SignalInfo info
        , Gtk.GObject self
        )
    => self
    -> SignalProxy self info
    -> (a -> IO b) -- ^ function to transform the Event with
    -> MomentIO (Event b)
signalE1' self signal h = signalEN self signal f >>= mapEventIO h
  where
    f g = \a -> g a >> return True -- we return True because the event has been
                                   -- handled, don't want to propagate it
                                   -- further


draw                :: Gtk.DrawingArea -> Behavior (Canvas ()) -> MomentIO ()
draw drawingArea bc = do
    canvasRef <- liftIO . newIORef =<< valueB bc -- gets the initial canvas

    -- set up reactive-banana to update the canvasRef on changes, and triger a
    -- redraw
    c <- valueBLater bc
    liftIOLater $ writeIORef canvasRef c
    e <- changes bc
    reactimate' $ (fmap $ \c' -> do liftIO $ writeIORef canvasRef c'
                                    #queueDraw drawingArea
                  ) <$> e

    -- registers drawing event handler
    _ <- on drawingArea #draw $ \context -> do
        w <- realToFrac . fromIntegral <$> #getAllocatedWidth  drawingArea
        h <- realToFrac . fromIntegral <$> #getAllocatedHeight drawingArea
        canvas <- readIORef canvasRef
        renderCanvas context (V2 w h) canvas
        pure True
    pure ()