{-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- | -- Module : Wumpus.Basic.DrawingComposition -- Copyright : (c) Stephen Tetley 2010 -- License : BSD3 -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable -- Portability : GHC -- -- Composition operators for Drawings. -- -- Note - some operations can produce empty drawings... -- -------------------------------------------------------------------------------- module Wumpus.Basic.DrawingComposition ( -- * Composition over , under , centric , nextToH , nextToV , atPoint , centeredAt , zconcat , hcat , vcat , hspace , vspace , hsep , vsep -- * Compose with alignment , alignH , alignV , alignHSep , alignVSep , hcatA , vcatA , hsepA , vsepA ) where import Wumpus.Basic.Graphic import Wumpus.Core -- package: wumpus-core import Data.AdditiveGroup import Data.AffineSpace import Data.List ( foldl' ) -------------------------------------------------------------------------------- -- Extract anchors boundaryExtr :: (BoundingBox u -> a) -> Picture u -> a boundaryExtr f = f . boundary -- Operations on bounds -- | The center of a picture. -- boundaryCenter :: Fractional u => Picture u -> Point2 u boundaryCenter = boundaryExtr center -- | Extract the mid point of the top edge. -- boundaryN :: Fractional u => Picture u -> Point2 u boundaryN = boundaryExtr north -- | Extract the mid point of the bottom edge. -- boundaryS :: Fractional u => Picture u -> Point2 u boundaryS = boundaryExtr south -- | Extract the mid point of the left edge. -- boundaryE :: Fractional u => Picture u -> Point2 u boundaryE = boundaryExtr east -- | Extract the mid point of the right edge. -- boundaryW :: Fractional u => Picture u -> Point2 u boundaryW = boundaryExtr west -- | Extract the top-left corner. -- boundaryNW :: Fractional u => Picture u -> Point2 u boundaryNW = boundaryExtr northwest -- | Extract the top-right corner. -- boundaryNE :: Picture u -> Point2 u boundaryNE = boundaryExtr ur_corner -- | Extract the bottom-left corner. -- boundarySW :: Picture u -> Point2 u boundarySW = boundaryExtr ll_corner -- | Extract the bottom-right corner. -- boundarySE :: Fractional u => Picture u -> Point2 u boundarySE = boundaryExtr southeast boundaryLeftEdge :: Picture u -> u boundaryLeftEdge = boundaryExtr (point_x . ll_corner) boundaryRightEdge :: Picture u -> u boundaryRightEdge = boundaryExtr (point_x . ur_corner) boundaryBottomEdge :: Picture u -> u boundaryBottomEdge = boundaryExtr (point_y . ll_corner) boundaryTopEdge :: Picture u -> u boundaryTopEdge = boundaryExtr (point_y . ur_corner) -- Note - do not export the empty drawing. It is easier to -- pretend it doesn't exist. -- empty_drawing :: (Real u, Floating u, FromPtSize u) => Drawing u empty_drawing = drawTracing $ return () -------------------------------------------------------------------------------- -- Composition operators -- 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 Drawings. -- -- 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). -- -- Picture /mega-combiner/ - moves only the second argument aka the -- right picture. -- megaCombR :: (Num u, Ord u) => (Picture u -> a) -> (Picture u -> a) -> (a -> a -> Picture u -> Picture u) -> Drawing u -> Drawing u -> Drawing u megaCombR qL qR trafoR = drawingConcat fn where fn pic1 pic2 = let a = qL pic1 b = qR pic2 p2 = trafoR a b pic2 in pic1 `picOver` p2 -- | > a `over` b -- -- Place \'drawing\' a over b. The idea of @over@ here is in -- terms z-ordering, nither picture a or b are actually moved. -- over :: (Num u, Ord u) => Drawing u -> Drawing u -> Drawing u over = drawingConcat picOver -- | > a `under` b -- -- Similarly @under@ draws the first drawing behind -- the second but move neither. -- under :: (Num u, Ord u) => Drawing u -> Drawing u -> Drawing u under = flip over -- | Move in both the horizontal and vertical. -- move :: (Num u, Ord u) => Vec2 u -> Drawing u -> Drawing u move v = modifyDrawing (\p -> p `picMoveBy` v) -------------------------------------------------------------------------------- -- Composition infixr 5 `nextToV` infixr 6 `nextToH`, `centric` -- | Draw @a@, move @b@ so its center is at the same center as -- @a@, @b@ is drawn over underneath in the zorder. -- -- > a `centeric` b -- -- centric :: (Fractional u, Ord u) => Drawing u -> Drawing u -> Drawing u centric = megaCombR boundaryCenter boundaryCenter moveFun where moveFun p1 p2 pic = let v = p1 .-. p2 in pic `picMoveBy` v -- | > a `nextToH` b -- -- Horizontal composition - move @b@, placing it to the right -- of @a@. -- nextToH :: (Num u, Ord u) => Drawing u -> Drawing u -> Drawing u nextToH = megaCombR boundaryRightEdge boundaryLeftEdge moveFun where moveFun a b pic = pic `picMoveBy` hvec (a - b) -- | > a `nextToV` b -- -- Vertical composition - move @b@, placing it below @a@. -- nextToV :: (Num u, Ord u) => Drawing u -> Drawing u -> Drawing u nextToV = megaCombR boundaryBottomEdge boundaryTopEdge moveFun where moveFun a b drw = drw `picMoveBy` vvec (a - b) -- | Place the picture at the supplied point. -- -- `atPoint` was previous the `at` operator. -- atPoint :: (Num u, Ord u) => Drawing u -> Point2 u -> Drawing u p `atPoint` (P2 x y) = move (V2 x y) p -- | Center the picture at the supplied point. -- centeredAt :: (Fractional u, Ord u) => Drawing u -> Point2 u -> Drawing u centeredAt d (P2 x y) = modifyDrawing fn d 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 -- | Concatenate the list of drawings. -- -- No pictures are moved. -- zconcat :: (Real u, Floating u, FromPtSize u) => [Drawing u] -> Drawing u zconcat [] = empty_drawing zconcat (d:ds) = foldl' over d ds -- | Concatenate the list pictures @xs@ horizontally. -- hcat :: (Real u, Floating u, FromPtSize u) => [Drawing u] -> Drawing u hcat [] = empty_drawing hcat (d:ds) = foldl' nextToH d ds -- | Concatenate the list of pictures @xs@ vertically. -- vcat :: (Real u, Floating u, FromPtSize u) => [Drawing u] -> Drawing u vcat [] = empty_drawing vcat (d:ds) = foldl' nextToV d ds -------------------------------------------------------------------------------- -- | > hspace n a b -- -- Horizontal composition - move @b@, placing it to the right -- of @a@ with a horizontal gap of @n@ separating the pictures. -- hspace :: (Num u, Ord u) => u -> Drawing u -> Drawing u -> Drawing u hspace n = megaCombR boundaryRightEdge boundaryLeftEdge moveFun where moveFun a b pic = pic `picMoveBy` hvec (n + a - b) -- | > vspace n a b -- -- Vertical composition - move @b@, placing it below @a@ with a -- vertical gap of @n@ separating the pictures. -- vspace :: (Num u, Ord u) => u -> Drawing u -> Drawing u -> Drawing u vspace n = megaCombR boundaryBottomEdge boundaryTopEdge moveFun where moveFun a b pic = pic `picMoveBy` vvec (a - b - n) -- | > hsep n xs -- -- Concatenate the list of pictures @xs@ horizontally with -- @hspace@ starting at @x@. The pictures are interspersed with -- spaces of @n@ units. -- hsep :: (Real u, Floating u, FromPtSize u) => u -> [Drawing u] -> Drawing u hsep _ [] = empty_drawing hsep n (d:ds) = foldl' (hspace n) d ds -- | > vsep n xs -- -- Concatenate the list of pictures @xs@ vertically with -- @vspace@ starting at @x@. The pictures are interspersed with -- spaces of @n@ units. -- vsep :: (Real u, Floating u, FromPtSize u) => u -> [Drawing u] -> Drawing u vsep _ [] = empty_drawing vsep n (d:ds) = foldl' (vspace n) d ds -------------------------------------------------------------------------------- -- Aligning pictures alignMove :: (Num u, Ord u) => Point2 u -> Point2 u -> Picture u -> Picture u alignMove p1 p2 pic = pic `picMoveBy` (p1 .-. p2) -- | > alignH 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@. -- alignH :: (Fractional u, Ord u) => HAlign -> Drawing u -> Drawing u -> Drawing u alignH HTop = megaCombR boundaryNE boundaryNW alignMove alignH HCenter = megaCombR boundaryE boundaryW alignMove alignH HBottom = megaCombR boundarySE boundarySW alignMove -- | > alignV align a b -- -- Vertical composition - move @b@, placing it below @a@ -- and align it with the left, center or right of @a@. -- alignV :: (Fractional u, Ord u) => VAlign -> Drawing u -> Drawing u -> Drawing u alignV VLeft = megaCombR boundarySW boundaryNW alignMove alignV VCenter = megaCombR boundaryS boundaryN alignMove alignV VRight = megaCombR boundarySE boundaryNE alignMove alignMove2 :: (Num u, Ord u) => Vec2 u -> Point2 u -> Point2 u -> Picture u -> Picture u alignMove2 v p1 p2 pic = pic `picMoveBy` (v ^+^ (p1 .-. p2)) -- | > alignHSep align sep a b -- -- Spacing version of alignH - move @b@ to the right of @a@ -- separated by @sep@ units, align @b@ according to @align@. -- alignHSep :: (Fractional u, Ord u) => HAlign -> u -> Drawing u -> Drawing u -> Drawing u alignHSep HTop dx = megaCombR boundaryNE boundaryNW (alignMove2 (hvec dx)) alignHSep HCenter dx = megaCombR boundaryE boundaryW (alignMove2 (hvec dx)) alignHSep HBottom dx = megaCombR boundarySE boundarySW (alignMove2 (hvec dx)) -- | > alignVSep align sep a b -- -- Spacing version of alignV - move @b@ below @a@ -- separated by @sep@ units, align @b@ according to @align@. -- alignVSep :: (Fractional u, Ord u) => VAlign -> u -> Drawing u -> Drawing u -> Drawing u alignVSep VLeft dy = megaCombR boundarySW boundaryNW (alignMove2 $ vvec (-dy)) alignVSep VCenter dy = megaCombR boundaryS boundaryN (alignMove2 $ vvec (-dy)) alignVSep VRight dy = megaCombR boundarySE boundaryNE (alignMove2 $ vvec (-dy)) -- | Variant of 'hcat' that aligns the pictures as well as -- concatenating them. -- hcatA :: (Real u, Floating u, FromPtSize u) => HAlign -> [Drawing u] -> Drawing u hcatA _ [] = empty_drawing hcatA ha (d:ds) = foldl' (alignH ha) d ds -- | Variant of 'vcat' that aligns the pictures as well as -- concatenating them. -- vcatA :: (Real u, Floating u, FromPtSize u) => VAlign -> [Drawing u] -> Drawing u vcatA _ [] = empty_drawing vcatA va (d:ds) = foldl' (alignV va) d ds -- | Variant of @hsep@ that aligns the pictures as well as -- concatenating and spacing them. -- hsepA :: (Real u, Floating u, FromPtSize u) => HAlign -> u -> [Drawing u] -> Drawing u hsepA _ _ [] = empty_drawing hsepA ha n (d:ds) = foldl' op d ds where a `op` b = alignHSep ha n a b -- | Variant of @vsep@ that aligns the pictures as well as -- concatenating and spacing them. -- vsepA :: (Real u, Floating u, FromPtSize u) => VAlign -> u -> [Drawing u] -> Drawing u vsepA _ _ [] = empty_drawing vsepA va n (d:ds) = foldl' op d ds where a `op` b = alignVSep va n a b