packages feed

casui-0.3: Casui/Draw.hs

module Casui.Draw where

{-# OPTIONS_GHC -XPackageImports #-}

import Graphics.UI.Gtk hiding (Arrow)
import Graphics.UI.Gtk.Gdk.GC
import Numeric
import Data.Bits
import Control.Monad.Trans
import Control.Applicative
import Casui.Utils
import Control.Arrow
import Data.List
import Control.Monad
import Data.Maybe

import Casui.Debug

-- deriving instance Show Rectangle

data Drawing t = LineD Point Point
          | GlyphD GlyphItem Double (Int, Int)
          | GroupD [DrawAttrib] [(Point, DrawInfo t)]
          | TagD t (Drawing t)
            deriving Show

data DrawAttrib = ColorA Color deriving Show

data DrawInfo t = DrawInfo {
      diBox :: Rectangle,
      diDrawing :: Drawing t
      -- ...
    } deriving Show

{-instance Show Color where
    showsPrec _ (Color r g b) = shows $ "#" ++ hexcolor r ++ hexcolor g ++ hexcolor b
	where hexcolor = pad . flip showHex "" . flip shiftR 8
	      pad [x] = ['0',x]
	      pad xs = xs-}

instance Show GlyphItem where
    show l = "<Glyph>"

-- todo: returned (c, scale) defines order on children, the [DrawInfo]
-- should use same order and be purged as it is used
newtype Draw t a = Draw {
      drawF :: Double
            -> PangoContext
            -> (DrawInfo t -> DrawInfo t -> DrawInfo t)
            -> IO (Maybe (DrawInfo t), a)
    }

instance Monad (Draw t) where
    return a = Draw $ \_ _ _ -> return (Nothing, a)
    d >>= f = Draw $ \s c m -> do (d, a) <- drawF d s c m
                                  (e, a') <- drawF (f a) s c m
                                  return (case (d, e) of
                                            (Just x, Just y) -> Just $ m x y
                                            (Just x, _) -> Just x
                                            (_, Just y) -> Just y
                                            _ -> Nothing
                                         , a')

instance MonadIO (Draw t) where
    liftIO a = Draw $ \_ _ _ -> (,) Nothing <$> a

instance Functor (Draw t) where
    fmap f = (>>= return . f)

runDraw :: Draw t a -> Double -> PangoContext -> IO a
runDraw d s c = snd <$> drawF d s c squashed

emptyDI :: DrawInfo t
emptyDI = DrawInfo (Rectangle 0 0 0 0) (GroupD [] [])

emptyp :: DrawInfo t -> Bool
emptyp (DrawInfo _ (GroupD _ [])) = True
emptyp _ = False

squashed :: DrawInfo t -> DrawInfo t -> DrawInfo t
squashed = groupDIs 0 0

groupDIs :: (Int, Int) -> (Int, Int) -> DrawInfo t -> DrawInfo t -> DrawInfo t
groupDIs dxy0@(px0,py0) dxy1@(px1,py1)
         d0@(DrawInfo r0@(Rectangle x0 y0 w0 h0) _)
         d1@(DrawInfo r1@(Rectangle x1 y1 w1 h1) _) =
             let rx0 = x0 + px0
                 rx1 = x1 + px1
                 ry0 = y0 + py0
                 ry1 = y1 + py1
                 --dxy0 = 0
                 --dxy1 = (rx1 - rx0, ry1 - ry0)
                 lx = min px0 px1 --rx0 rx1
                 by = min py0 py1 --ry0 ry1
                 rx = max (px0 + w0) (px1 + w1) -- (rx0 + w0) (rx1 + w1)
                 ty = max (py0 + h0) (py1 + h1) -- (ry0 + h0) (ry1 + h1)
                 tw = rx - lx
                 th = ty - by
                 --splat p (DrawInfo _ (GroupD [] l)) = map (first (+p)) l -- don't ignore diBox
                 splat p d = [(p, d)] in
             trace (show (dxy0, r0, dxy1, r1, tw, th)) $
             DrawInfo (Rectangle 0 0 tw th) -- (Rectangle rx0 ry0 tw th)
                      (GroupD [] $ splat dxy0 d0 ++ splat dxy1 d1)

getScale :: Draw t Double
getScale = Draw $ \s _ _ -> return (Nothing, s)

getPangoContext :: Draw t PangoContext
getPangoContext = Draw $ \_ c _ -> return (Nothing, c)

putDI :: DrawInfo t -> Draw t ()
putDI d = Draw $ \_ _ _ -> return (Just d, ())

removeDI :: Draw t a -> Draw t (Maybe (DrawInfo t), a)
removeDI d = Draw $ \s c m ->
             do (e, a) <- drawF d s c m
                return (Nothing, (e,a))

removeDI_ d = fst <$> removeDI d

getDI :: Draw t a -> Draw t (Maybe (DrawInfo t), a)
getDI d = do
  (e, a) <- removeDI d
  maybe (return ()) putDI e
  return (e, a)

scaleBy :: Double -> Draw t a -> Draw t a
scaleBy sc d = Draw $ \s -> drawF d (s * sc)

withScale :: Double -> Draw t a -> Draw t a
withScale sc d = Draw $ \s -> drawF d sc

diPos :: DrawInfo t -> (Int, Int)
diPos (DrawInfo (Rectangle x y _ _) _) = (x, y)

diSize :: DrawInfo t -> (Int, Int)
diSize (DrawInfo (Rectangle _ _ w h) _) = (w, h)

displaying :: (DrawInfo t -> DrawInfo t -> DrawInfo t) -> Draw t a -> Draw t a
displaying m d = Draw $ \s c _ -> drawF d s c m

tag :: t -> Draw t d -> Draw t d
tag t d = Draw $ \s c m ->
          first (Just . tagDI t . fromMaybe emptyDI) <$> drawF d s c m

tagDI :: t -> DrawInfo t -> DrawInfo t
tagDI t di@(DrawInfo { diDrawing = d }) = di { diDrawing = TagD t d }


draw :: (DrawableClass d, Show t) => d -> GC -> (Int, Int) -> DrawInfo t -> IO ()
draw dw gc p = draw' dw gc p . diDrawing

draw' :: (DrawableClass d, Show t) => d -> GC -> (Int, Int) -> Drawing t -> IO ()
draw' dw gc (x,y) (LineD (x1,y1) (x2,y2)) = drawLine dw gc (x1+x, y1+y) (x2+x, y2+y)
draw' dw gc (x,y) (GlyphD t a (ox, oy)) = drawGlyphs dw gc (x-ox) (y-oy) t
draw' dw gc (x,y) (GroupD [] l) = mapM_ f l
    where f ((sx,sy),d) = draw dw gc (x+sx, y+sy) d
draw' dw gc (x,y) (GroupD a l) = do save <- modifyGC gc a; mapM_ f l; gcSetValues gc save
    where f ((sx,sy),d) = draw dw gc (x+sx, y+sy) d
draw' dw gc p (TagD _ d) = draw' dw gc p d

modifyGC :: GC -> [DrawAttrib] -> IO GCValues
modifyGC gc l = do s <- gcGetValues gc; gcSetValues gc $ foldl f s l; return s
    where f a (ColorA c') = a {foreground = c'}

layoutSetFontSize :: PangoLayout -> Double -> IO ()
layoutSetFontSize layout size = do
  text <- layoutGetText layout
  layoutSetAttributes layout [AttrSize 0 (length text) size]

glyphItemSize :: GlyphItem -> IO (Int, Int)
glyphItemSize g = do
  (_, Rectangle _ _ w h) <- glyphItemPixelExtents g
  return (w,h)

glyphItemOffset :: GlyphItem -> IO (Int, Int)
glyphItemOffset = glyphItemOffset' True

glyphItemOffset' t g = do
  (Rectangle a b _ _, Rectangle x y _ _) <- glyphItemPixelExtents g
  return $ if t then (x,y) else (a,b)

glyphItemPixelExtents :: GlyphItem -> IO (Rectangle, Rectangle)
glyphItemPixelExtents g = do
  (PangoRectangle a b c d, PangoRectangle e f g h) <- glyphItemExtents g
  return (Rectangle (round a) (round b) (round c) (round d),
          Rectangle (round e) (round f) (round g) (round h))

glyphItemBBox :: GlyphItem -> IO Rectangle
glyphItemBBox g = snd <$> glyphItemPixelExtents g

glyphItemBBox' inkOrLogical g = (if inkOrLogical then snd else fst) <$> glyphItemPixelExtents g

-- todo: dynamic
normalFontSize :: Double
normalFontSize = 32

minFontSize :: Double
minFontSize = 6

scaleMultiplier :: Double
scaleMultiplier = 0.7

-- |Horizontal box combiner
nextTo :: DrawInfo t -> DrawInfo t -> DrawInfo t
nextTo d@(DrawInfo (Rectangle _ _ w _) _) =
           groupDIs 0 (w, 0) d

superscript :: DrawInfo t -> DrawInfo t -> DrawInfo t
superscript
  d1@(DrawInfo (Rectangle _ _ w1 h1) _)
  d2@(DrawInfo (Rectangle _ _ w2 h2) _) =
    groupDIs (0, h2 `div` 2)
            (w1, 0)
            d1 d2
    
leftSuperscript :: DrawInfo t -> DrawInfo t -> DrawInfo t
leftSuperscript
  d1@(DrawInfo (Rectangle _ _ w1 h1) _)
  d2@(DrawInfo (Rectangle _ _ w2 h2) _) =
    groupDIs 0
            (w1, h1`div`2)
            d1 d2

centeredNextTo :: DrawInfo t -> DrawInfo t -> DrawInfo t
centeredNextTo  d@(DrawInfo (Rectangle x0 y0 w0 h0) _)
       e@(DrawInfo (Rectangle x1 y1 w1 h1) _) =
           let th = max h0 h1
           in groupDIs
                  (0, (th - h0) `div` 2)
                  (w0, (th - h1) `div` 2)
                  d e


-- |Centered vertical box combiner
centeredUnder :: DrawInfo t -> DrawInfo t -> DrawInfo t
centeredUnder  d@(DrawInfo (Rectangle x0 y0 w0 h0) _)
       e@(DrawInfo (Rectangle x1 y1 w1 h1) _) =
           let tw = max w0 w1
           in groupDIs
                  ((tw - w0) `div` 2, 0) --(div (tw-w0) 2 - x0, 0)
                  ((tw - w1) `div` 2, h0) --(div (tw-w1) 2 - x1, h0 - y0 + y1)
                  d e
       

getFontSize :: Draw t Double
getFontSize = max minFontSize . (normalFontSize *) <$> getScale

text :: String -> Draw t ()
text str = advancedText True str

advancedText inkOrLogical str = do
  size <- getFontSize
  context <- getPangoContext
  dis <- liftIO $ do
           pangois <- pangoItemize context str [AttrSize 0 (length str) size]
           ascents <- map ascent <$> mapM pangoItemGetFontMetrics pangois
           glyphs <- mapM pangoShape pangois
           offsets <- mapM (glyphItemOffset' inkOrLogical) glyphs
           boxes <- mapM (glyphItemBBox' inkOrLogical) glyphs
           return $ zipWith DrawInfo boxes $ zipWith3 GlyphD glyphs ascents offsets
  displaying nextTo $ mapM_ putDI dis

parenLeft  = "(⎛⎜⎝"
parenRight = ")⎞⎟⎠"

vstretch :: String -> Int -> Draw t ()
vstretch [small,top,middle,bottom] heighti =
  let height = trn "height: " $ realToFrac heighti in
     if height < 2 * normalFontSize
     then {- withScale (height / normalFontSize) $-} text [small]
     else displaying centeredUnder $ do
       advancedText True [top]
       advancedText True [middle]
       advancedText True [bottom]
       
addParens :: Draw t a -> Draw t a
addParens d = displaying nextTo $ do
  (mdi, b) <- removeDI d
  case mdi of
    Nothing -> text "()" >> return b
    Just di -> displaying centeredNextTo $ do
      let height = rectHeight $ diBox di
      vstretch parenLeft height
      putDI di
      vstretch parenRight height
      return b

getTagsPos :: Point -> Drawing t -> [(Point, t)]
getTagsPos p (TagD t di) = [(p, t)]
getTagsPos p (GroupD _ l) = concatMap (uncurry getTagsPos . ((+p) *** diDrawing)) l
getTagsPos _ _ = []

rectWidth :: Rectangle -> Int
rectWidth (Rectangle _ _ w _) = w

rectHeight :: Rectangle -> Int
rectHeight (Rectangle _ _ _ h) = h

line :: Int -> Int -> Int -> Int -> Draw t ()
line x0 y0 x1 y1 = putDI (DrawInfo (Rectangle x0 y0 (x1-x0) (y1-y0)) (LineD (x0,y0) (x1,y1)))