{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RankNTypes #-} {-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- | -- Module : Wumpus.Basic.Kernel.Drawing.CtxPicture -- Copyright : (c) Stephen Tetley 2010-2011 -- License : BSD3 -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable -- Portability : GHC -- -- A Picture-with-implicit-context object. -- -- This is the corresponding type to Picture in the Wumpus-Core. -- -- Note - many of the composition functions are in -- /destructor form/. As Wumpus cannot make a Picture from an -- empty list of Pictures, /destructor form/ decomposes the -- list into the @head@ and @rest@ as arguments in the function -- signature, rather than take a possibly empty list and have to -- throw an error. -- -- TODO - PosImage no longer supports composition operators, so -- better names are up for grabs... -- -------------------------------------------------------------------------------- module Wumpus.Basic.Kernel.Drawing.CtxPicture ( CtxPicture , runCtxPicture , runCtxPictureU , drawTracing , udrawTracing , mapCtxPicture -- * Composition , uniteCenter , centeredAt ) where import Wumpus.Basic.Kernel.Base.BaseDefs import Wumpus.Basic.Kernel.Base.DrawingContext import Wumpus.Basic.Kernel.Drawing.TraceDrawing import Wumpus.Basic.Kernel.Objects.Anchors import Wumpus.Basic.Kernel.Objects.Concat import Wumpus.Core -- package: wumpus-core import Data.AdditiveGroup -- package: vector-space import Data.AffineSpace import Data.Monoid -- | A /Contextual/ Picture. -- -- > CtxPicture = DrawingContext -> Maybe Picture -- -- This type corresponds to the 'Picture' type in Wumpus-Core, but -- it is embedded with a 'DrawingContext' (for font properties, -- fill colour etc.). The DrawingContext is embedded so that font -- metrics - loaded in @IO@ can be passed into the pure world of -- 'TraceDrawing'. -- -- Internally a /context picture/ is a function from -- 'DrawingContext' to @(Maybe Picture)@. The @Maybe@ represents -- that it is possible to construct empty Pictures, even though -- @Wumpus-Core@ cannot render them. Just as the DrawingContext -- pushes font-metrics from the IO to the pure world, the Maybe -- lifts the problem of unrenderable Pictures into the API where -- client code must deal with it explicitly. -- -- (In practice, it is very unlikely a program will create empty -- pictures and @runCtxPictureU@ can be used without worry). -- -- -- Note - pictures are fixed to the unit @Double@ (representing -- PostScript points). Pictures are intentionally unsophisticated, -- any fine grained control of units should be delegated to the -- elements that build the picture (Graphics, LocGraphics, etc.). -- newtype CtxPicture = CtxPicture { getCtxPicture :: DrawingContext -> Maybe Picture } type instance DUnit CtxPicture = Double -- | 'runCtxPicture' : @ drawing_ctx * ctx_picture -> Maybe Picture @ -- -- Run a 'CtxPicture' with the supplied 'DrawingContext' -- producing a 'Picture'. -- -- The resulting Picture may be empty. Wumpus-Core cannot -- generate empty pictures as they have no bounding box, so the -- result is wrapped within a Maybe. This delegates reponsibility -- for handling empty pictures to client code. -- runCtxPicture :: DrawingContext -> CtxPicture -> Maybe Picture runCtxPicture ctx drw = getCtxPicture drw ctx -- | 'runCtxPictureU' : @ drawing_ctx * ctx_picture -> Picture @ -- -- /Unsafe/ version of 'runCtxPicture'. -- -- This function throws a runtime error when supplied with an -- empty CtxPicture. -- runCtxPictureU :: DrawingContext -> CtxPicture -> Picture runCtxPictureU ctx df = maybe fk id $ runCtxPicture ctx df where fk = error "runCtxPictureU - empty CtxPicture." -- | 'drawTracing' : @ trace_drawing -> CtxPicture @ -- -- Transform a 'TraceDrawing' into a 'CtxPicture'. -- drawTracing :: TraceDrawing u a -> CtxPicture drawTracing ma = CtxPicture $ \ctx -> liftToPictureMb $ execTraceDrawing ctx ma -- | 'udrawTracing' : @ scalar_unit_value * trace_drawing -> CtxPicture @ -- -- Variant of 'drawTracing' with a phantom first argument - the -- phantom identifies the unit type of the 'TraceDrawing'. It is -- not scurtinized at the value level. -- -- udrawTracing :: u -> TraceDrawing u a -> CtxPicture udrawTracing _ ma = CtxPicture $ \ctx -> liftToPictureMb $ execTraceDrawing ctx ma -- Note need Gen versions with user state... -- | 'mapCtxPicture' : @ trafo * ctx_picture -> CtxPicture @ -- -- Apply a picture transformation function to the 'Picture' -- warpped in a 'CtxPicture'. -- mapCtxPicture :: (Picture -> Picture) -> CtxPicture -> CtxPicture mapCtxPicture pf pic1 = CtxPicture $ \ctx -> fmap pf $ getCtxPicture pic1 ctx -------------------------------------------------------------------------------- -- Affine instances instance Rotate CtxPicture where rotate ang = mapCtxPicture (rotate ang) instance RotateAbout CtxPicture where rotateAbout ang pt = mapCtxPicture (rotateAbout ang pt) instance Scale CtxPicture where scale sx sy = mapCtxPicture (scale sx sy) instance Translate CtxPicture where translate dx dy = mapCtxPicture (translate dx dy) -------------------------------------------------------------------------------- -- Monoid -- | Avoid initial mempty for mconcat. -- instance Monoid CtxPicture where mempty = CtxPicture $ \_ -> Nothing mappend = moveSnd $ \_ _ -> V2 0 0 mconcat [] = mempty mconcat (a:as) = step a as where step ac [] = ac step ac (x:xs) = step (ac `mappend` x) xs -------------------------------------------------------------------------------- -- Extract /planes/. leftEdge :: BoundingBox Double -> Double leftEdge = point_x . ll_corner rightEdge :: BoundingBox Double -> Double rightEdge = point_x . ur_corner bottomEdge :: BoundingBox Double -> Double bottomEdge = point_y . ll_corner topEdge :: BoundingBox Double -> Double topEdge = point_y . ur_corner -------------------------------------------------------------------------------- -- Composition operators -- Naming convention - Wumpus-Core already prefixes operations -- on Pictures with pic. As the picture operators here work on a -- different type, they merit a different naming scheme. -- -- Unfortunately the @cxp_@ prefix is rather ugly... -- -- Directional names seem better than positional ones (less -- ambiguous as when used as binary operators). -- combineP2 :: (Picture -> Picture -> Picture) -> CtxPicture -> CtxPicture -> CtxPicture combineP2 op mf mg = CtxPicture $ \ctx -> fn (getCtxPicture mf ctx) (getCtxPicture mg ctx) where fn (Just a) (Just b) = Just $ a `op` b fn a Nothing = a fn Nothing b = b -- Note - the megaCombR operator is in some way an -- /anti-combinator/. It seems easier to think about composing -- drawings if we do work on the result Pictures directly rather -- than build combinators to manipulate CtxPictures. -- -- The idea of combining pre- and post- operating combinators -- makes me worry about circular programs even though I know -- lazy evaluation allows me to write them (in some cicumstances). -- moveSnd :: (DBoundingBox -> DBoundingBox -> DVec2) -> CtxPicture -> CtxPicture -> CtxPicture moveSnd mkV = combineP2 fn where fn pl pr = let v1 = mkV (boundary pl) (boundary pr) in pl `picOver` (picMoveBy pr v1) instance ZConcat CtxPicture where superior = mappend anterior = flip mappend -------------------------------------------------------------------------------- -- Composition infixr 6 `uniteCenter` -- | Draw @a@, move @b@ so its center is at the same center as -- @a@, @b@ is drawn over underneath in the zorder. -- -- > a `cxpUniteCenter` b -- uniteCenter :: CtxPicture -> CtxPicture -> CtxPicture uniteCenter = moveSnd $ \a b -> center a .-. center b -- -- Are combinator names less ambiguous if they name direction -- rather than position? -- instance Concat CtxPicture where hconcat = cxpRight vconcat = cxpBelow -- | > a `cxpRight` b -- -- Horizontal composition - position picture @b@ to the right of -- picture @a@. -- cxpRight :: CtxPicture -> CtxPicture -> CtxPicture cxpRight = moveSnd $ \a b -> hvec $ rightEdge a - leftEdge b -- | > a `cxpBelow` b -- -- Vertical composition - position picture @b@ /down/ from picture -- @a@. -- cxpBelow :: CtxPicture -> CtxPicture -> CtxPicture cxpBelow = moveSnd $ \a b -> vvec $ bottomEdge a - topEdge b -- | Center the picture at the supplied point. -- centeredAt :: CtxPicture -> DPoint2 -> CtxPicture centeredAt pic (P2 x y) = mapCtxPicture fn pic where fn p = let bb = boundary p dx = x - (boundaryWidth bb * 0.5) dy = y - (boundaryHeight bb * 0.5) in p `picMoveBy` vec dx dy -------------------------------------------------------------------------------- instance CatSpace CtxPicture where hspace = cxpRightSep vspace = cxpDownSep -- | > cxpRightSep n a b -- -- Horizontal composition - move @b@, placing it to the right -- of @a@ with a horizontal gap of @n@ separating the pictures. -- cxpRightSep :: Double -> CtxPicture -> CtxPicture -> CtxPicture cxpRightSep n = moveSnd $ \a b -> hvec $ n + (rightEdge a - leftEdge b) -- | > cxpDownSep n a b -- -- Vertical composition - move @b@, placing it below @a@ with a -- vertical gap of @n@ separating the pictures. -- cxpDownSep :: Double -> CtxPicture -> CtxPicture -> CtxPicture cxpDownSep n = moveSnd $ \a b -> vvec $ bottomEdge a - (topEdge b + n) -------------------------------------------------------------------------------- -- Aligning pictures instance Align CtxPicture where halign = cxpAlignH valign = cxpAlignV -- | > cxpAlignH align a b -- -- Horizontal composition - move @b@, placing it to the right -- of @a@ and align it with the top, center or bottom of @a@. -- cxpAlignH :: HAlign -> CtxPicture -> CtxPicture -> CtxPicture cxpAlignH HALIGN_TOP = moveSnd $ \a b -> northeast a .-. northwest b cxpAlignH HALIGN_CENTER = moveSnd $ \a b -> east a .-. west b cxpAlignH HALIGN_BASE = moveSnd $ \a b -> southeast a .-. southwest b -- | > cxpAlignV align a b -- -- Vertical composition - move @b@, placing it below @a@ -- and align it with the left, center or right of @a@. -- cxpAlignV :: VAlign -> CtxPicture -> CtxPicture -> CtxPicture cxpAlignV VALIGN_LEFT = moveSnd $ \a b -> southwest a .-. northwest b cxpAlignV VALIGN_CENTER = moveSnd $ \a b -> south a .-. north b cxpAlignV VALIGN_RIGHT = moveSnd $ \a b -> southeast a .-. northeast b instance AlignSpace CtxPicture where halignSpace = cxpAlignSpaceH valignSpace = cxpAlignSpaceV -- | > cxpAlignSpaceH align sep a b -- -- Spacing version of 'cxpAlignH' - move @b@ to the right of @a@ -- separated by @sep@ units, align @b@ according to @align@. -- cxpAlignSpaceH :: HAlign -> Double -> CtxPicture -> CtxPicture -> CtxPicture cxpAlignSpaceH align dx = go align where mv f g = moveSnd $ \a b -> hvec dx ^+^ (f a .-. g b) go HALIGN_TOP = mv northeast northwest go HALIGN_CENTER = mv east west go HALIGN_BASE = mv southeast southwest -- | > cxpAlignSpaceV align sep a b -- -- Spacing version of alignV - move @b@ below @a@ -- separated by @sep@ units, align @b@ according to @align@. -- cxpAlignSpaceV :: VAlign -> Double -> CtxPicture -> CtxPicture -> CtxPicture cxpAlignSpaceV align dy = go align where mv f g = moveSnd $ \a b -> vvec (-dy) ^+^ (f a .-. g b) go VALIGN_LEFT = mv southwest northwest go VALIGN_CENTER = mv south north go VALIGN_RIGHT = mv southeast northeast