packages feed

fenfire-0.1: Cairo.fhs

-- For (instance (Cairo cx r, Monoid m) => Monoid (cx m)):
{-# OPTIONS_GHC -fallow-undecidable-instances -fallow-incoherent-instances #-}
-- More, implied by the previous on GHC 6.6 but needed for earlier:
{-# OPTIONS_GHC -fallow-overlapping-instances #-}
module Cairo where

-- Copyright (c) 2006-2007, Benja Fallenstein, Tuukka Hastrup
-- This file is part of Fenfire.
-- 
-- Fenfire is free software; you can redistribute it and/or modify it under
-- the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
-- 
-- Fenfire is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-- Public License for more details.
-- 
-- You should have received a copy of the GNU General
-- Public License along with Fenfire; if not, write to the Free
-- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-- MA  02111-1307  USA

import Utils

import Control.Applicative
import Control.Monad

import Data.Monoid (Monoid(mappend, mempty))

import Graphics.UI.Gtk hiding (Point, Size, Layout, Color, get, fill)
import qualified Graphics.Rendering.Cairo as C
import Graphics.Rendering.Cairo.Matrix (Matrix(Matrix))
import qualified Graphics.Rendering.Cairo.Matrix as Matrix
import Graphics.UI.Gtk.Cairo

data Color = Color Double Double Double Double
type Size  = (Double, Double)
type Point = (Double, Double)
type Rect  = (Matrix, Size)

type Render a = C.Render a
newtype Path  = Path { renderPath :: Render () }    deriving Monoid

class (Applicative cx, Monoid r) => 
      Cairo cx r | cx -> r, r -> cx where
    cxAsk    :: cx Rect
    cxLocal  :: cx Rect -> Endo (cx a)
    
    cxWrap   :: EndoM cx (Render ()) -> Endo r
    cxLocalR :: cx Rect -> Endo r

    cxRender :: cx (Render ()) -> r
    cxRender r = cxWrap (const r) mempty
    
instance Monoid (Render ()) where
    mempty = return ()
    mappend = (>>)

instance (Applicative m, Monoid o) => Monoid (m o) where
    mempty = pure mempty
    mappend = liftA2 mappend
        
instance Cairo ((->) Rect) (Rect -> Render ()) where
    cxAsk = id
    cxLocal f m r = m (f r)
    
    cxWrap f ren r = f (ren r) r
    cxLocalR f ren r = ren (f r)

newtype InContext a b = InContext { appContext :: a -> b } deriving Monoid

instance Cairo cx r => Cairo (Comp ((->) a) cx) (InContext a r) where
    cxAsk = Comp (const cxAsk)
    cxLocal (Comp f) (Comp m) = Comp $ \a -> cxLocal (f a) (m a)
    
    cxWrap f c = InContext $ \a -> cxWrap (\ren -> (fromComp $ f ren) a)
                                          (c `appContext` a)
    cxLocalR f c = InContext $ \a -> cxLocalR (fromComp f a) (c `appContext` a)

cxMatrix :: Cairo cx r => cx Matrix
cxMatrix = fmap fst cxAsk

cxSize :: Cairo cx r => cx Size
cxSize = fmap snd cxAsk


[black, gray, lightGray, white] = [Color x x x 1 | x <- [0, 0.5, 0.9, 1]]


fill :: Cairo cx r => cx Path -> r
fill p = cxRender $ forA2 p cxMatrix $ \p' m -> do
    renderPath p'; C.save; C.transform m; C.fill; C.restore

stroke :: Cairo cx r => cx Path -> r
stroke p = cxRender $ forA2 p cxMatrix $ \p' m -> do
    renderPath p'; C.save; C.transform m; C.stroke; C.restore

paint :: Cairo cx r => r
paint = cxRender $ pure C.paint

clip :: Cairo cx r => cx Path -> Endo r
clip p = cxWrap $ \ren -> ffor p $ \p' -> do
    C.save; renderPath p'; C.clip; ren; C.restore
    
    
withColor :: Cairo cx r => cx Color -> Endo r
withColor c = cxWrap $ \ren -> ffor c $ \(Color r g b a) -> do
    C.save; C.setSourceRGBA r g b a; ren; C.restore
    
withDash :: Cairo cx r => cx [Double] -> cx Double -> Endo r
withDash a b = cxWrap $ \ren -> #(C.save >> C.setDash !a !b >> ren >> C.restore)
    
transform :: Cairo cx r => cx (Endo Matrix) -> Endo r
transform f = cxLocalR #(!f Matrix.identity * !cxMatrix, !cxSize)

-- | Moves a renderable by x and y.
--
translate :: Cairo cx r => cx Double -> cx Double -> Endo r
translate x y = transform $ liftA2 Matrix.translate x y

-- | Moves a renderable to the specific point p.
--
translateTo :: Cairo cx r => cx Point -> Endo r
translateTo p = translate x y where
    (x,y) = funzip #(Matrix.transformPoint (Matrix.invert !cxMatrix) !p)

-- | Rotates a renderable by angle.
--
rotate :: Cairo cx r => cx Double -> Endo r
rotate angle = transform $ fmap Matrix.rotate angle

-- | Scales a renderable by sx and sy.
--
scale2 :: Cairo cx r => cx Double -> cx Double -> Endo r
scale2 sx sy = transform $ liftA2 Matrix.scale sx sy
    
-- | Scales a renderable by sc.
--
scale :: Cairo cx r => cx Double -> Endo r
scale sc = scale2 sc sc


between :: Cairo cx r => cx Point -> cx Point -> Endo r
between p1 p2 = translate #(avg !x1 !x2) #(avg !y1 !y2)
              . rotate #(atan2 (!y2 - !y1) (!x2 - !x1)) 
    where (x1,y1) = funzip p1; (x2,y2) = funzip p2


point :: Cairo cx r => cx Double -> cx Double -> cx Point
point x y = #(Matrix.transformPoint !cxMatrix (!x,!y))

anchor :: Cairo cx r => cx Double -> cx Double -> cx Point
anchor x y = #(Matrix.transformPoint !cxMatrix (!x * !w, !y * !h))
    where (w,h) = funzip cxSize

center :: Cairo cx r => cx Point
center = anchor #0.5 #0.5

closePath :: Cairo cx r => cx Path
closePath = pure $ Path $ C.closePath

arc :: Cairo cx r => cx Point -> cx Double -> cx Double -> cx Double -> cx Path
arc p a b c = #(Path $ do
    let (x,y) = Matrix.transformPoint (Matrix.invert !cxMatrix) !p
    C.save; C.transform !cxMatrix; C.arc x y !a !b !c; C.restore)

arcNegative :: Cairo cx r => cx Point -> cx Double -> cx Double -> cx Double -> 
               cx Path
arcNegative p a b c = #(Path $ do
    let (x,y) = Matrix.transformPoint (Matrix.invert !cxMatrix) !p
    C.save; C.transform !cxMatrix; C.arcNegative x y !a !b !c; C.restore)

circle :: Cairo cx r => cx Point -> cx Double -> cx Path
circle p r = arc p r #0 #(2*pi)

curveTo :: Cairo cx r => cx Point -> cx Point -> cx Point -> cx Path
curveTo p1 p2 p3 = forA3 p1 p2 p3 $ \(x1,y1) (x2,y2) (x3,y3) ->
                       Path $ C.curveTo x1 y1 x2 y2 x3 y3

moveTo :: Cairo cx r => cx Point -> cx Path
moveTo p = ffor p $ \(x,y) -> Path $ do C.moveTo x y

lineTo :: Cairo cx r => cx Point -> cx Path
lineTo p = ffor p $ \(x,y) -> Path $ do C.lineTo x y

line :: (Cairo cx r, Monoid (cx Path)) => cx Point -> cx Point -> cx Path
line p1 p2 = moveTo p1 & lineTo p2

extents :: (Cairo cx r, Monoid (cx Path)) => cx Path
extents = moveTo (anchor #0 #0) & lineTo (anchor #0 #1) & lineTo (anchor #1 #1)
        & lineTo (anchor #1 #0) & lineTo (anchor #0 #0)