wumpus-basic-0.21.0: src/Wumpus/Basic/Kernel/Drawing/TraceDrawing.hs
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS -Wall #-}
--------------------------------------------------------------------------------
-- |
-- Module : Wumpus.Basic.Kernel.Drawing.TraceDrawing
-- Copyright : (c) Stephen Tetley 2010-2011
-- License : BSD3
--
-- Maintainer : stephen.tetley@gmail.com
-- Stability : unstable
-- Portability : GHC
--
-- Drawing with /trace/ - a Writer like monad collecting
-- intermediate graphics - and /drawing context/ - a reader monad
-- of attributes - font_face, fill_colour etc.
--
--------------------------------------------------------------------------------
module Wumpus.Basic.Kernel.Drawing.TraceDrawing
(
-- * Collect primitives (writer-like monad)
GenTraceDrawing
, TraceDrawing
, DTraceDrawing
, runTraceDrawing
, execTraceDrawing
, evalTraceDrawing
, runGenTraceDrawing
, liftToPictureU
, liftToPictureMb
, mbPictureU
, trace
, fontDelta
, evalQuery
, draw
, drawi
, drawl
, drawli
, drawc
, drawci
, node
, nodei
, drawrc
, drawrci
) where
import Wumpus.Basic.Kernel.Base.BaseDefs
import Wumpus.Basic.Kernel.Base.DrawingContext
import Wumpus.Basic.Kernel.Base.QueryDC
import Wumpus.Basic.Kernel.Base.WrappedPrimitive
import Wumpus.Basic.Kernel.Drawing.Basis
import Wumpus.Basic.Kernel.Objects.Anchors
import Wumpus.Basic.Kernel.Objects.Connector
import Wumpus.Basic.Kernel.Objects.Image
import Wumpus.Basic.Kernel.Objects.LocImage
import Wumpus.Core -- package: wumpus-core
import Control.Applicative
import Control.Monad
import Data.Monoid
--------------------------------------------------------------------------------
-- Note - TraceDrawing run \once\ - it is supplied with the starting
-- environment (DrawingContext) and returns a Picture.
--
-- Other Wumpus monads (e.g. Turtle) will typically be run inside
-- the TraceDrawing monad as a local effect, rather than built into a
-- transformer stack.
--
newtype GenTraceDrawing st u a = GenTraceDrawing {
getGenTraceDrawing :: DrawingContext -> st -> (a, st, HPrim u) }
type instance DUnit (GenTraceDrawing st u a) = u
type instance UState (GenTraceDrawing st u) = st
type TraceDrawing u a = GenTraceDrawing () u a
type DTraceDrawing a = TraceDrawing Double a
-- Functor
instance Functor (GenTraceDrawing st u) where
fmap f ma = GenTraceDrawing $ \ctx s ->
let (a,s1,w1) = getGenTraceDrawing ma ctx s in (f a,s1,w1)
-- Applicative
instance Applicative (GenTraceDrawing st u) where
pure a = GenTraceDrawing $ \_ s -> (a, s, mempty)
mf <*> ma = GenTraceDrawing $ \ctx s ->
let (f,s1,w1) = getGenTraceDrawing mf ctx s
(a,s2,w2) = getGenTraceDrawing ma ctx s1
in (f a, s2, w1 `mappend` w2)
-- Monad
instance Monad (GenTraceDrawing st u) where
return a = GenTraceDrawing $ \_ s -> (a, s, mempty)
ma >>= k = GenTraceDrawing $ \ctx s ->
let (a,s1,w1) = getGenTraceDrawing ma ctx s
(b,s2,w2) = (getGenTraceDrawing . k) a ctx s1
in (b,s2,w1 `mappend` w2)
-- DrawingCtxM
instance DrawingCtxM (GenTraceDrawing st u) where
askDC = GenTraceDrawing $ \ctx s -> (ctx, s, mempty)
asksDC f = GenTraceDrawing $ \ctx s -> (f ctx, s, mempty)
localize upd ma = GenTraceDrawing $ \ctx s ->
getGenTraceDrawing ma (upd ctx) s
-- UserStateM
instance UserStateM (GenTraceDrawing st u) where
getState = GenTraceDrawing $ \_ s -> (s, s, mempty)
setState s = GenTraceDrawing $ \_ _ -> ((), s, mempty)
updateState upd = GenTraceDrawing $ \_ s -> ((), upd s, mempty)
runTraceDrawing :: TraceDrawing u a -> DrawingContext -> (a, HPrim u)
runTraceDrawing ma ctx = post $ getGenTraceDrawing ma ctx ()
where
post (a,_,w1) = (a,w1)
-- | Run the drawing returning only the output it produces, drop
-- any answer from the monadic computation.
--
execTraceDrawing :: TraceDrawing u a -> DrawingContext -> HPrim u
execTraceDrawing ma ctx = snd $ runTraceDrawing ma ctx
-- | Run the drawing ignoring the output it produces, return the
-- answer from the monadic computation.
--
-- Note - this useful for testing, generally one would want the
-- opposite behaviour (return the drawing, ignore than the
-- answer).
--
evalTraceDrawing :: TraceDrawing u a -> DrawingContext -> a
evalTraceDrawing ma ctx = fst $ runTraceDrawing ma ctx
runGenTraceDrawing :: GenTraceDrawing st u a -> DrawingContext -> st
-> (a,st,HPrim u)
runGenTraceDrawing = getGenTraceDrawing
-- | /Unsafe/ promotion of @HPrim@ to @Picture@.
--
-- If the HPrim is empty, a run-time error is thrown.
--
liftToPictureU :: HPrim u -> Picture
liftToPictureU hf =
let prims = hprimToList hf in if null prims then errK else frame prims
where
errK = error "toPictureU - empty prims list."
-- | /Safe/ promotion of @HPrim@ to @(Maybe Picture)@.
--
-- If the HPrim is empty, then @Nothing@ is returned.
--
liftToPictureMb :: HPrim u -> Maybe Picture
liftToPictureMb hf = let prims = hprimToList hf in
if null prims then Nothing else Just (frame prims)
-- | /Unsafe/ promotion of @(Maybe Picture)@ to @Picture@.
--
-- This is equivalent to:
--
-- > fromMaybe (error "empty") $ pic
--
-- This function is solely a convenience, using it saves one
-- import and a few characters.
--
-- If the supplied value is @Nothing@ a run-time error is thrown.
--
mbPictureU :: Maybe Picture -> Picture
mbPictureU Nothing = error "mbPictureU - empty picture."
mbPictureU (Just a) = a
-- Note - need an equivalent to Parsec\`s parseTest that provides
-- a very simple way to run graphics without concern for return
-- type or initial drawing context.
--------------------------------------------------------------------------------
-- TraceM
--
-- Note - @ state `mappend` a @ means the first expression in a
-- monadic drawing is the first element in the output file. It is
-- also \*\* at the back \*\* in the the Z-Order.
--
-- Some control over the Z-Order, possibly by adding /layers/ to
-- the drawing model would be valuable.
--
-- | Primitive operation - cf. tell in Reader monad.
--
trace :: HPrim u -> GenTraceDrawing st u ()
trace a = GenTraceDrawing $ \_ s -> ((), s, a)
fontDelta :: GenTraceDrawing st u a -> GenTraceDrawing st u a
fontDelta mf = GenTraceDrawing $ \ctx s ->
let (_,font_attrs) = runQuery textAttr ctx
(a,s1,w1) = getGenTraceDrawing mf ctx s
prim = fontDeltaContext font_attrs $ primGroup $ hprimToList w1
in (a, s1, singleH $ prim1 $ prim)
-- Note - this function is in the wrong module....
--
evalQuery :: DrawingCtxM m => Query u a -> m a
evalQuery df = askDC >>= \ctx -> return $ runQuery df ctx
-- | Draw a Graphic taking the drawing style from the
-- /drawing context/.
--
-- This function is the /forgetful/ version of 'drawi'.
-- Commonly, it is used to draw 'Graphic' objects which
-- have no /answer/.
--
draw :: Image u a -> GenTraceDrawing st u ()
draw gf = askDC >>= \ctx ->
let (_,w) = runImage gf ctx
in trace (singleH w) >> return ()
-- | Draw an Image taking the drawing style from the
-- /drawing context/.
--
-- The graphic representation of the Image is drawn in the Trace
-- monad, and the result is returned.
--
drawi :: Image u a -> GenTraceDrawing st u a
drawi gf = askDC >>= \ctx ->
let (a,w) = runImage gf ctx
in trace (singleH w) >> return a
-- | Draw a LocImage at the supplied Anchor taking the drawing
-- style from the /drawing context/.
--
-- This function is the /forgetful/ version of 'drawli'.
-- Commonly, it is used to draw 'LocGraphic' objects which
-- have no /answer/.
--
drawl :: InterpretUnit u
=> Anchor u -> LocImage u a -> GenTraceDrawing st u ()
drawl ancr img = drawli ancr img >> return ()
-- | Draw a LocImage at the supplied Point taking the drawing
-- style from the /drawing context/.
--
-- The graphic representation of the Image is drawn in the Trace
-- monad, and the result is returned.
--
drawli :: InterpretUnit u
=> Anchor u -> LocImage u a -> GenTraceDrawing st u a
drawli pt gf = askDC >>= \ctx ->
let (a,w) = runLocImage gf ctx pt
in trace (singleH w) >> return a
-- Design note - having @drawlti@ for LocThetaImage does not seem
-- compelling (at the moment). The thinking is that LocTheta
-- objects should be downcast to Loc objects before drawing.
--
-- Connectors however are be different.
--
-- PosImages would seem to be the same as LocThetaImages.
--
-- | Draw a ConnectorGraphic with the supplied Anchors taking the
-- drawing style from the /drawing context/.
--
-- This function is the /forgetful/ version of 'drawci'.
-- Commonly, it is used to draw 'ConnectorGraphic' objects which
-- have no /answer/.
--
drawc :: InterpretUnit u
=> Anchor u -> Anchor u -> ConnectorImage u a -> GenTraceDrawing st u ()
drawc an0 an1 img = drawci an0 an1 img >> return ()
-- | Draw a ConnectorImage with the supplied Points taking the
-- drawing style from the /drawing context/.
--
-- The graphic representation of the Image is drawn in the Trace
-- monad, and the result is returned.
--
drawci :: InterpretUnit u
=> Anchor u -> Anchor u -> ConnectorImage u a -> GenTraceDrawing st u a
drawci p0 p1 img = drawi (connect p0 p1 img)
-- | Draw the object with the supplied grid coordinate. The
-- actual position is scaled according to the
-- @snap_grid_factors@ in the /drawing context/.
--
-- This function is the /forgetful/ version of 'nodei'.
-- Commonly, it is used to draw 'LocGraphic' objects which
-- have no /answer/.
--
node :: ( Fractional u, InterpretUnit u)
=> (Int,Int) -> LocImage u a -> GenTraceDrawing st u ()
node coord gf = nodei coord gf >> return ()
-- | Draw the object with the supplied grid coordinate. The
-- actual position is scaled according to the
-- @snap_grid_factors@ in the /drawing context/.
--
nodei :: (Fractional u, InterpretUnit u)
=> (Int,Int) -> LocImage u a -> GenTraceDrawing st u a
nodei coord gf = askDC >>= \ctx ->
position coord >>= \pt ->
let (a,w) = runLocImage gf ctx pt
in trace (singleH w) >> return a
-- | Draw a connector between two objects. The projection of the
-- connector line is drawn on the line from center to center of
-- the objects, the actual start and end points of the drawn line
-- are the radial points on the objects borders that cross the
-- projected line.
--
-- This function is the /forgetful/ version of 'drawrci'.
-- Commonly, it is used to draw 'LocGraphic' objects which
-- have no /answer/.
--
drawrc :: ( Real u, Floating u, InterpretUnit u
, CenterAnchor a1, RadialAnchor a1
, CenterAnchor a2, RadialAnchor a2
, u ~ DUnit a1, u ~ DUnit a2
)
=> a1 -> a2 -> ConnectorImage u a -> GenTraceDrawing st u ()
drawrc a b gf = drawrci a b gf >> return ()
-- | Draw a connector between two objects. The projection of the
-- connector line is drawn on the line from center to center of
-- the objects, the actual start and end points of the drawn line
-- are the radial points on the objects borders that cross the
-- projected line.
--
drawrci :: ( Real u, Floating u, InterpretUnit u
, CenterAnchor a1, RadialAnchor a1
, CenterAnchor a2, RadialAnchor a2
, u ~ DUnit a1, u ~ DUnit a2
)
=> a1 -> a2 -> ConnectorImage u a -> GenTraceDrawing st u a
drawrci a b gf =
let (p0,p1) = radialConnectorPoints a b in drawi (connect p0 p1 gf)