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)