sifflet-0.1.5: TreeLayout.hs
module TreeLayout
(VColor(..), white, cream, black, lightBlue, lightBlueGreen,
lightGray, mediumGray, darkGray,
yellow, darkBlueGreen, blueGreen,
Style(..), VFont(..), defaultStyle, style0, style1, style2, style3,
wstyle,
styleIncreasePadding,
FontTextExtents(..), setFont, ftExtents,
TextBox(..), makeTextBox, tbWidth, tbHeight, tbBottom, drawTextBox,
tbCenter, tbTextCenter, tbBoxCenter, offsetTextBoxCenters,
tbSetWidth,
setColor,
treeSizes,
GNode(..), treeGNodes, gnodeText, gnodeTextBB,
Iolet(..), ioletCenter, makeIolets, makeIoletsRow,
pointInIolet,
IoletCounter, zeroIoletCounter,
makeGNode,
TreeLayout, LayoutNode(..), layoutNodeSource, layoutRootBB, layoutTreeBB,
treeLayout,
treeLayoutPaddedSize, treeLayoutSize, treeLayoutWidth,
layoutTreeMoveCenterTo,
pointInLayoutNode, pointInGNode, findRect,
Draw(..), DrawMode(..), modeTextCol, modeEdgeCol, modeFillCol,
drawBox,
treeLayoutWiden
)
where
import System.IO.Unsafe
import Graphics.Rendering.Cairo hiding (translate)
import Graphics.UI.Gtk (Rectangle)
import Control.Monad (when)
import Data.Traversable () -- imports only instance declarations
import Geometry
import Tree as T hiding (tree)
import Util
data VColor = ColorRGB Double Double Double |
ColorRGBA Double Double Double Double
black, white, cream, lightBlue, lightBlueGreen :: VColor
black = ColorRGB 0 0 0
white = ColorRGB 1 1 1
cream = ColorRGB 0.94902 0.94902 0.82745
lightBlue = ColorRGB 0.43529 0.43922 0.95686
lightBlueGreen = ColorRGB 0 1 1
lightGray, mediumGray, darkGray :: VColor
lightGray = ColorRGBA 0.75 0.75 0.75 0.5
mediumGray = ColorRGB 0.5 0.5 0.5
darkGray = ColorRGB 0.25 0.25 0.25
yellow, darkBlueGreen, blueGreen :: VColor
yellow = ColorRGB 0.9 0.9 0
darkBlueGreen = ColorRGB 0 0.3 0.3
blueGreen = ColorRGB 0 0.4 0.4
-- darkRed = ColorRGB 0.5 0 0, -- dark red
data VFont = VFont {vfontFace :: String, vfontSlant :: FontSlant,
vfontWeight :: FontWeight,
vfontSize :: Double}
data Style =
Style {styleFont :: VFont,
lineWidth :: Double,
textMargin :: Double, -- in box on all four sides
hpad, vpad :: Double, -- internode (interbox) spacing
exomargin :: Double, -- margin of the tree within the window
vtinypad :: (Double, Double), -- node-edge spacing (above, below)
styleFramePad :: Double, -- pad between frames
styleNormalTextColor, styleNormalFillColor,
styleNormalEdgeColor,
styleActiveTextColor, styleActiveFillColor,
styleActiveEdgeColor,
styleSelectedTextColor, styleSelectedFillColor,
styleSelectedEdgeColor,
styleTetherColor :: VColor,
-- auxiliary text style, e.g., the value of an ExprNode
styleAuxOffset :: Position, -- center-to-center offset dx dy
styleAuxColor :: VColor,
styleAuxFont :: VFont,
styleIoletRadius :: Double, -- *** could extend to other shapes
styleShowNodeBoxes :: Bool, -- draw a box around expr nodes
styleShowNodePorts :: Bool -- draw the I/O ports of expr nodes
}
styleIncreasePadding :: Style -> Double -> Style
-- increase hpad, vpad of style by multiplication
styleIncreasePadding style factor =
style {hpad = factor * hpad style,
vpad = factor * vpad style}
style0, style1, style2, style3, defaultStyle :: Style
style0 =
let green = ColorRGB 0.1 0.9 0.1
veryDarkGray = ColorRGB 0.1 0.1 0.1
brighterGreen = ColorRGB 0.5 0.95 0.5
in Style {styleFont = VFont "serif" FontSlantNormal FontWeightNormal 18,
lineWidth = 2,
textMargin = 18.0, -- text within its box
hpad = 27, vpad = 36, -- inter-node separation
exomargin = 0,
vtinypad = (4.5, 4.5), -- node-edge separation
styleFramePad = 35, -- padding between frames
-- Foreground and background colors
styleNormalTextColor = green,
styleNormalEdgeColor = green,
styleNormalFillColor = veryDarkGray,
styleActiveTextColor = brighterGreen,
styleActiveEdgeColor = brighterGreen,
styleActiveFillColor = mediumGray,
styleSelectedTextColor = blueGreen,
styleSelectedEdgeColor = blueGreen,
styleSelectedFillColor = darkGray,
styleTetherColor = white,
styleAuxOffset = Position 0.0 (-20.0),
styleAuxColor = lightGray,
styleAuxFont =
VFont "serif" FontSlantItalic FontWeightNormal 12,
styleIoletRadius = 5,
styleShowNodeBoxes = True, -- draw a box around expr nodes
styleShowNodePorts = True -- draw the I/O ports of expr nodes
}
style1 =
let veryDarkBlue = ColorRGB 0 0 0.1
pinkIHope = ColorRGB 1 0.9 0.9
in style0 {styleNormalTextColor = veryDarkBlue,
styleNormalEdgeColor = veryDarkBlue,
styleNormalFillColor = lightGray,
styleActiveTextColor = pinkIHope,
styleActiveEdgeColor = pinkIHope
}
style2 =
let darkPink = ColorRGB 1 0.5 0.5
in style1 {styleNormalFillColor = white,
styleActiveTextColor = darkPink,
styleActiveEdgeColor = darkPink}
style3 =
let semiTranspBlue = ColorRGBA 0 0.2 0.9 0.5
in style0 {exomargin = 10,
styleNormalTextColor = yellow,
styleNormalEdgeColor = yellow,
styleNormalFillColor = darkBlueGreen,
styleActiveTextColor = yellow,
styleActiveEdgeColor = yellow,
styleActiveFillColor = blueGreen,
styleAuxColor = semiTranspBlue
}
defaultStyle = style3
wstyle :: Style
wstyle =
style3 {
-- smaller to allow multiple frames on one canvas
styleFont = VFont "serif" FontSlantNormal FontWeightNormal 14,
textMargin = 8.0,
hpad = 10.0, vpad = 24.0,
vtinypad = (0.0, 0.0),
exomargin = 10.0, -- between tree and window edges
styleFramePad = 15,
-- colors
styleNormalTextColor = yellow,
styleNormalEdgeColor = mediumGray,
styleNormalFillColor = darkBlueGreen,
styleActiveTextColor = yellow,
styleActiveEdgeColor = mediumGray,
styleActiveFillColor = blueGreen,
styleSelectedTextColor = black,
styleSelectedEdgeColor = lightBlueGreen,
styleSelectedFillColor = lightBlueGreen,
styleTetherColor =
ColorRGBA 0.7 0.7 0.7 0.3, -- light gray
-- node values
styleAuxOffset = Position 0 (-16),
styleAuxFont = VFont "serif" FontSlantItalic FontWeightNormal 12,
styleAuxColor = ColorRGBA 0.9 0.5 0.5 1.0,
-- ColorRGBA 1 0.9 0.9 0.75
styleIoletRadius = 5
}
pointInLayoutNode :: Position -> LayoutNode e -> Bool
pointInLayoutNode point = pointInGNode point . nodeGNode
pointInGNode :: Position -> GNode e -> Bool
pointInGNode point = pointInBB point . gnodeNodeBB
-- UNUSED: findNode (except in findRect)
-- find the node, if any, containing a point
-- ****** might need to adjust this to take iolets into account ******
findNode :: Position -> TreeLayout e -> Maybe (LayoutNode e)
findNode point (T.Node node@(LayoutNode _rootGNode treeBB) sublayouts) =
if pointInBB point treeBB
then if pointInLayoutNode point node
then Just node
else findInSubs point sublayouts
else Nothing
where findInSubs :: Position -> [TreeLayout e] -> Maybe (LayoutNode e)
findInSubs _p [] = Nothing
findInSubs p (l:ls) =
let found = findNode p l in
case found of
(Just _) -> found
Nothing -> findInSubs p ls
-- UNUSED: findRect
findRect :: Position -> TreeLayout e -> Maybe Rectangle
findRect point tlo =
case findNode point tlo of
Nothing -> Nothing
Just node -> Just (bbToRect (gnodeNodeBB (nodeGNode node)))
setFont :: VFont -> Render ()
setFont (VFont face slant weight size) = do
selectFontFace face slant weight
setFontSize size
data FontTextExtents = FontTextExtents {
fontAscent::Double, fontDescent::Double, fontHeight::Double,
fontMaxXadvance::Double, fontMaxYadvance::Double,
textXbearing::Double, textYbearing::Double,
extTextWidth::Double, extTextHeight::Double,
textXadvance::Double, textYadvance::Double}
deriving (Eq, Read, Show)
-- Rationale for unsafePerformIO:
-- font extents of a string will not change unless the font itself
-- changes, which is extremely unlikely
styleTextExtents :: Style -> String -> FontTextExtents
styleTextExtents style str =
unsafePerformIO $
withImageSurface FormatARGB32 0 0 $ \ surface ->
renderWith surface $
setFont (styleFont style) >>
ftExtents str
-- | ftExtents: used for what?
ftExtents :: String -> Render FontTextExtents
ftExtents text = do
FontExtents asc desc fheight maxxadv maxyadv <- fontExtents
TextExtents xbear ybear twidth theight xadv yadv <- textExtents text
return (FontTextExtents asc desc fheight maxxadv maxyadv
xbear ybear twidth theight xadv yadv)
-- UNUSED
-- -- | measureText is not used; should it be?
-- measureText :: Style -> String -> Size
-- measureText style str = extentsSize (styleTextExtents style str)
-- -- | extentsSize: unused; should it be?
-- extentsSize :: FontTextExtents -> Size
-- extentsSize ftExts = Size (textXadvance ftExts) (fontHeight ftExts)
data TextBox = TextBox {tbText :: String,
tbTextBB :: BBox, -- BBox of text without margins
tbBoxBB :: BBox -- of text with margins
}
deriving (Eq, Read, Show)
makeTextBox :: Style -> String -> TextBox
makeTextBox style text =
let extents = styleTextExtents style text
margin = textMargin style
textW = textXadvance extents
textH = fontHeight extents + fontDescent extents
boxW = textW + 2.0 * margin -- max (textW + 2.0 * margin) minWidth
boxH = textH + 2.0 * margin
boxX = 0
boxY = 0
textX = (boxW - textW) / 2.0
-- raise text slightly, by a fraction of its font descent
-- (why? because it just looks better!)
-- Maybe this should be made an aspect of style?
raise = 0.5 * fontDescent extents
textY = fontHeight extents + (boxH - textH) / 2.0 - raise
textBB = BBox textX textY textW textH
boxBB = BBox boxX boxY boxW boxH
in TextBox text textBB boxBB
instance Widen TextBox where
widen tb@(TextBox _text textBB boxBB) minWidth =
let w = bbWidth boxBB
in if w >= minWidth
then tb
else let dw = minWidth - w
in tb {tbTextBB = translate (dw / 2) 0 textBB,
tbBoxBB = widen boxBB minWidth}
tbWidth :: TextBox -> Double
tbWidth = bbWidth .tbBoxBB
tbSetWidth :: TextBox -> Double -> TextBox
tbSetWidth tbox w =
let TextBox text textBB boxBB = tbox
boxBB' = bbSetWidth boxBB w
(dx, dy) = positionDelta (bbCenter boxBB) (bbCenter boxBB')
in TextBox text (translate dx dy textBB) boxBB'
tbHeight :: TextBox -> Double
tbHeight = bbHeight . tbBoxBB
tbBottom :: TextBox -> Double
tbBottom = bbBottom . tbBoxBB
-- The geometric "center" of a TextBox is the center of its "BoxBB"
tbCenter :: TextBox -> Position
tbCenter = tbBoxCenter
tbBoxCenter :: TextBox -> Position
tbBoxCenter = bbCenter . tbBoxBB
-- and the center of its text should have the same x coordinate,
-- but due to the way text is positioned, maybe not the same y.
-- But do we ever need this?
tbTextCenter :: TextBox -> Position
tbTextCenter = bbCenter . tbTextBB
offsetTextBoxCenters :: Position -> TextBox -> TextBox -> TextBox
offsetTextBoxCenters offset anchor floater =
-- Position the floater text box so that its center differs
-- from the anchor text box by the given offset.
-- (The offset argument comes first, like dx dy in translate.)
let Position ax ay = tbBoxCenter anchor
Position fx fy = tbBoxCenter floater
Position ox oy = offset
-- translate floater by (dx, dy), where fx + dx = ax + ox,
-- so dx = ax + ox - fx; and dy similarly
dx = ax + ox - fx
dy = ay + oy - fy
in translate dx dy floater
-- GNode = graphical node, suitable for drawing
data GNode e = GNode {gnodeValue :: e,
gnodeTextBoxes :: [TextBox], -- one or two only
gnodeNodeBB :: BBox, -- encloses all textboxes
gnodeInlets :: [Iolet], -- input connectors
gnodeOutlets :: [Iolet] -- output connectors
}
deriving (Eq)
gnodeText :: GNode e -> String
gnodeText = tbText . head . gnodeTextBoxes
gnodeTextBB :: GNode e -> BBox
gnodeTextBB = tbTextBB . head . gnodeTextBoxes
instance (Show e) => Show (GNode e) where
show (GNode v tbs nodeBB inlets outlets) =
par "GNode" [show v, show tbs, show nodeBB, show inlets, show outlets]
-- An IoletCounter returns (no. of inlets, no. of outlets)
type IoletCounter e = e -> (Int, Int)
-- A sort of "default" IoletCounter returning zeroes
zeroIoletCounter :: IoletCounter e
zeroIoletCounter _node = (0, 0)
-- An IoletsMaker ???
-- type IoletsMaker e = Style -> e -> ([Position], [Position])
makeGNode :: (Repr e) => Style -> IoletCounter e -> e -> GNode e
makeGNode style countIolets value =
-- The function countIolets creates a tuple (# of inlets, # of outlets)
-- for the node, of course being (0, 0) if we don't want any.
let textboxes1 = map (makeTextBox style) (reprl value)
textboxes2 = case textboxes1 of
[_tb] -> textboxes1
[tb1, tb2] ->
[tb1,
offsetTextBoxCenters (styleAuxOffset style) tb1 tb2]
_ -> wrong textboxes1
nodeBB = case textboxes2 of
[tb] -> tbBoxBB tb
[tb1, tb2] -> bbMerge (tbBoxBB tb1) (tbBoxBB tb2)
_ -> wrong textboxes2
wrong tbs =
error $ "makeGNode: wrong no. of text boxes; " ++
"expected 1 or 2, but got " ++ show (length tbs)
(inlets, outlets) = makeIolets style nodeBB (countIolets value)
in GNode value textboxes2 nodeBB inlets outlets
makeIolets :: Style -> BBox -> (Int, Int) -> ([Iolet], [Iolet])
makeIolets style bbox (nin, nout) =
-- make the (inlets, outlets)
(makeIoletsRow style (bbXCenter bbox) (bbBottom bbox) nin,
makeIoletsRow style (bbXCenter bbox) (bbTop bbox) nout)
makeIoletsRow :: Style -> Double -> Double -> Int -> [Iolet]
makeIoletsRow style cx cy n =
-- make a row of n Iolets, centered at (cx, cy)
let radius = styleIoletRadius style
diam = 2 * radius -- diameter
w = fromIntegral n * diam
x1 = cx - w / 2 + radius -- the center of the first iolet
x i = x1 + fromIntegral (i - 1) * diam
make i = Iolet (Circle (Position (x i) cy) radius)
in map make [1..n]
data Iolet = Iolet Circle
-- | other shapes could be added
deriving (Eq, Read, Show)
ioletCenter :: Iolet -> Position
ioletCenter (Iolet circle) = circleCenter circle
pointInIolet :: Position -> Iolet -> Bool
pointInIolet point (Iolet circle) = pointInCircle point circle
-- Build a Tree of node Sizes parallel to the Repr tree,
-- each node in the Size tree is the size of
-- the corresponding node in the repr tree.
-- treeSizes: given a tree of *node* sizes, for the root nodes of the tree
-- and its subtrees, returns a tree of *tree* sizes
treeSizes :: Style -> Tree (GNode e) -> Tree Size
treeSizes style gTree =
let subtreeSizes = map (treeSizes style) (subForest gTree)
(BBox _ _ rootWidth rootHeight) = gnodeNodeBB (rootLabel gTree)
treeWidth = max rootWidth (paddedWidth style subtreeSizes)
treeHeight = rootHeight + paddedHeight style subtreeSizes
in T.Node (Size treeWidth treeHeight) subtreeSizes
-- (paddedWidth style subtrees) and (paddedHeight style subtrees)
-- compute the total size of all subtrees
-- of of a Size tree, including padding.
paddedWidth :: Style -> [Tree Size] -> Double
paddedWidth _style [] = 0
paddedWidth style subtrees =
sum [w | (T.Node (Size w _h) _) <- subtrees] +
hpad style * fromIntegral (length subtrees - 1)
-- UNUSED:
-- -- | pairSum is unused; should it be?
-- pairSum :: (Double, Double) -> Double
-- pairSum (a, b) = a + b
paddedHeight :: Style -> [Tree Size] -> Double
paddedHeight _style [] = 0
paddedHeight style subtrees =
vpad style + maximum [h | (T.Node (Size _w h) _) <- subtrees]
-- A TreeLayout is a Tree of LayoutNodes.
-- The root node in a TreeLayout identifies the root of the source Tree
-- (nodeSource) and the node's bounding box (nodeNodeBB).
-- It also tells the bounding box of the whole tree (nodeTreeBB).
-- The subtrees of the TreeLayout are the TreeLayouts
-- of the subtrees of the source Tree.
type TreeLayout e = Tree (LayoutNode e)
data LayoutNode e = LayoutNode {nodeGNode :: GNode e,
nodeTreeBB :: BBox}
deriving (Eq)
layoutNodeSource :: LayoutNode e -> e
layoutNodeSource = gnodeValue . nodeGNode
instance (Show e) => Show (LayoutNode e) where
show (LayoutNode gnode treebb) = par "LayoutNode" [show gnode, show treebb]
instance Widen (LayoutNode e) where
widen node@(LayoutNode gNode treeBB) minWidth =
let dw = bbWidth treeBB - minWidth
in if dw <= 0
then node
else LayoutNode (translate (dw / 2) 0 gNode)
(widen treeBB minWidth)
{- try this, it's tough!
instance (Draw n, Widen n) = Widen (Tree n) where
...
-}
-- UNUSED
-- -- | layoutRootSource: unused
-- layoutRootSource :: TreeLayout e -> e
-- layoutRootSource tree = layoutNodeSource (rootLabel tree)
layoutRootBB :: TreeLayout e -> BBox
layoutRootBB = gnodeNodeBB . nodeGNode . rootLabel
layoutTreeBB :: TreeLayout e -> BBox
layoutTreeBB = nodeTreeBB . rootLabel
treeLayout :: (Repr e) => Style -> IoletCounter e -> Tree e -> TreeLayout e
treeLayout style counter tree =
let t1 = treeGNodes style counter tree
t2 = treeSizes style t1
start = Position (hpad style) (vpad style)
t3 = treeLayout2 style start tree t1 t2
in treeLayoutAddMargin t3 (exomargin style)
-- treeGNodes produces a tree of (GNode e),
-- based on a top left corner at (0, 0) which, of course, will need
-- to be translated in the final tlo.
treeGNodes :: Repr e =>
Style -> IoletCounter e -> Tree e -> Tree (GNode e)
treeGNodes style counter tree = fmap (makeGNode style counter) tree
treeLayout2 :: (Repr e) =>
Style -> Position -> Tree e -> Tree (GNode e) -> Tree Size -> TreeLayout e
treeLayout2 style
(Position startX startY) -- top left after padding
(T.Node _root subtrees) -- the tree being laid out
-- ^^ actually we don't need the tree as an argument;
-- it's implied by the GNode tree
(T.Node gnode subGNodes) -- "node sizes"
(T.Node (Size treeWidth treeHeight) subTreeSizes) -- tree Sizes
=
let
nodeHeight = bbHeight (gnodeNodeBB gnode)
-- center the root in its level
-- UNUSED:
-- nodeCenterX = startX + treeWidth / 2
-- nodeCenterY = startY + nodeHeight
-- center the subtrees (may be wider or narrower than the root)
subtreesTotalWidth = paddedWidth style subTreeSizes
subX = startX + (treeWidth - subtreesTotalWidth) / 2 -- first subtree
subY = startY + nodeHeight + vpad style
sublayouts :: (Repr e) =>
Double -> [Tree e] -> [Tree (GNode e)] -> [Tree Size] ->
[TreeLayout e]
sublayouts _ [] [] [] = []
sublayouts x (t:ts) (g:gs) (s:ss) =
treeLayout2 style (Position x subY) t g s :
sublayouts (x + sizeW (rootLabel s) + hpad style) ts gs ss
sublayouts _ _ _ _ = error "treeLayout2: mismatched list lengths"
in T.Node (LayoutNode -- root node
(centerGNode gnode startX startY treeWidth nodeHeight) -- node
(BBox startX startY treeWidth treeHeight)) -- whole tree
(sublayouts subX subtrees subGNodes subTreeSizes)
-- | Center a GNode in a rectangular area
centerGNode :: GNode e -> Double -> Double -> Double -> Double -> GNode e
centerGNode gnode startx starty awidth aheight =
let cx = startx + awidth / 2 -- desired center
cy = starty + aheight / 2
Position cgx cgy = bbCenter (gnodeNodeBB gnode)
in translate (cx - cgx) (cy - cgy) gnode
treeLayoutPaddedSize :: Style -> TreeLayout e -> Size
treeLayoutPaddedSize style tlo =
let Size w h = treeLayoutSize tlo
in Size (w + 2.0 * hpad style) (h + 2.0 * vpad style)
treeLayoutSize :: TreeLayout e -> Size
treeLayoutSize tlo =
let BBox _x _y w h = layoutTreeBB tlo in Size w h
layoutTreeMoveCenterTo ::
Double -> Double -> TreeLayout e -> TreeLayout e
layoutTreeMoveCenterTo newX newY layoutTree =
let Position oldX oldY = bbCenter (nodeTreeBB (rootLabel layoutTree))
in translate (newX - oldX) (newY - oldY) layoutTree
treeLayoutAddMargin :: TreeLayout e -> Double -> TreeLayout e
treeLayoutAddMargin tree margin =
let LayoutNode {nodeGNode = rootGNode, nodeTreeBB = treeBB} =
rootLabel tree
subtrees = subForest tree
BBox x y w h = treeBB
treeBB' = BBox (x - margin) (y - margin)
(w + 2 * margin) (h + 2 * margin)
root' = translate margin margin (LayoutNode rootGNode treeBB')
subtrees' = translate margin margin subtrees
in T.Node root' subtrees'
treeLayoutWidth :: TreeLayout e -> Double
treeLayoutWidth = bbWidth . layoutTreeBB
treeLayoutWiden :: TreeLayout e -> Double -> TreeLayout e
treeLayoutWiden tlo minWidth =
let w = treeLayoutWidth tlo
in
if w >= minWidth
then tlo
else let dw = minWidth - w
T.Node root subs = translate (dw / 2) 0 tlo
LayoutNode rootGNode treeBB = root
root' = LayoutNode rootGNode
(translate (-dw / 2)
0
(widen treeBB minWidth))
in T.Node root' subs
setColor :: VColor -> Render ()
setColor (ColorRGB red green blue) =
setSourceRGB red green blue
setColor (ColorRGBA red green blue alpha) =
setSourceRGBA red green blue alpha
-- Drawing things
-- ****** THINK: is this class well conceived?
-- A lot of things could be done with draw and translate
-- if it were not tied to Style and DrawMode
class Draw a where
draw :: Style -> DrawMode -> a -> Render ()
translate :: Double -> Double -> a -> a
-- VV DrawMode is now very awkward, since
-- a node needs to know not only if it is selected,
-- but which port(s) are selected
data DrawMode = DrawNormal | DrawActive
| DrawSelectedNode
| DrawSelectedInlet Int
| DrawSelectedOutlet Int
deriving (Eq)
instance (Draw e) => Draw [e] where
draw style mode = mapM_ (draw style mode)
translate dx dy = map (translate dx dy)
{- Tree must be an instance of Draw,
in order for translate to work in treeLayoutWiden,
but again, this is a bad combination (draw + translate).
-}
instance (Draw e) => Draw (Tree e) where
draw style mode tree = do
draw style mode (rootLabel tree)
draw style mode (subForest tree)
translate dx dy tree =
T.Node (translate dx dy (rootLabel tree))
(translate dx dy (subForest tree))
instance Draw (LayoutNode e) where
draw style mode (LayoutNode gnode _treeBB) = draw style mode gnode
translate dx dy (LayoutNode gnode treeBB) =
LayoutNode (translate dx dy gnode)
(translate dx dy treeBB)
instance Draw (GNode e) where
draw style mode (GNode _value textboxes nodeBB inlets outlets) =
do
-- draw node box
let (nodeTextCol, nodeEdgeCol, nodeFillCol) =
case mode of
DrawActive ->
(styleActiveTextColor,
styleActiveEdgeColor,
styleActiveFillColor)
DrawSelectedNode ->
(styleSelectedTextColor,
styleSelectedEdgeColor,
styleSelectedFillColor)
_ -> (styleNormalTextColor,
styleNormalEdgeColor,
styleNormalFillColor)
-- (overall box for the node)
when (styleShowNodeBoxes style) $
drawBox (Just (nodeFillCol style))
(Just (nodeEdgeCol style)) nodeBB
-- assert textboxes has one or two elements
-- draw the first text box
drawTextBox (Just (styleFont style))
(Just (nodeFillCol style)) -- background
Nothing -- frame color
(nodeTextCol style) -- text color
(head textboxes)
-- draw the second textbox, if any, using "aux" style
case (tail textboxes) of
[tbAux] ->
drawTextBox (Just (styleAuxFont style))
Nothing
Nothing
(styleAuxColor style)
tbAux
_ -> return ()
-- Draw the iolets
when (styleShowNodePorts style) $ do
drawInlets style mode inlets
drawOutlets style mode outlets
translate dx dy (GNode value textboxes nodeBB inlets outlets) =
GNode value
(map (translate dx dy) textboxes)
(translate dx dy nodeBB)
(translate dx dy inlets)
(translate dx dy outlets)
instance Draw TextBox where
draw style mode =
drawTextBox (Just (styleFont style))
(Just (modeFillCol mode style))
Nothing
(modeTextCol mode style)
translate dx dy (TextBox text textBB boxBB) =
TextBox text (translate dx dy textBB) (translate dx dy boxBB)
drawTextBox :: Maybe VFont -> Maybe VColor -> Maybe VColor -> VColor ->
TextBox -> Render ()
drawTextBox mfont mbgcolor mframecolor textcolor
(TextBox text textBB boxBB) = do
let BBox textX textY _textW _textH = textBB
drawBox mbgcolor mframecolor boxBB
setColor textcolor
case mfont of
(Just font) -> setFont font
_ -> return ()
moveTo textX textY
showText text
instance Draw BBox where
draw style mode =
drawBox (Just (modeFillCol mode style))
(Just (modeEdgeCol mode style))
translate dx dy (BBox x y w h) = BBox (x + dx) (y + dy) w h
drawBox :: Maybe VColor -> Maybe VColor -> BBox -> Render ()
drawBox mBgColor mFgColor (BBox x y w h) =
-- draw the BBox, in the specified colors, irrespective of style
let setup color =
do
rectangle x y w h
setColor color
in case (mBgColor, mFgColor) of
(Just bgColor, Just fgColor) ->
do
setup bgColor
fillPreserve
setColor fgColor
stroke
(Just bgColor, Nothing) ->
do
setup bgColor
fill
(Nothing, Just fgColor) ->
do
setup fgColor
stroke
_ -> return ()
instance Draw Position where
draw _style _mode _pos = return () -- bare points are invisible ??? ******
translate dx dy (Position x y) = Position (x + dx) (y + dy)
instance Draw Iolet where
draw style mode (Iolet circle) = draw style mode circle
translate dx dy (Iolet circle) = Iolet (translate dx dy circle)
drawIolet :: Iolet -> VColor -> VColor -> Render ()
drawIolet (Iolet circle) = drawCircle circle
drawInlets :: Style -> DrawMode -> [Iolet] -> Render ()
drawInlets style mode inlets =
let selected i = mode == DrawSelectedInlet i
in drawIolets selected style inlets
drawOutlets :: Style -> DrawMode -> [Iolet] -> Render ()
drawOutlets style mode outlets =
let selected o = mode == DrawSelectedOutlet o
in drawIolets selected style outlets
drawIolets :: (Int -> Bool) -> Style -> [Iolet] -> Render ()
drawIolets selected style iolets =
-- (selected n) should be true iff n is the selected iolet
let loop _ [] = return ()
loop n (p:ps) =
uncurry (drawIolet p)
(if selected n
then (styleSelectedFillColor style,
styleSelectedEdgeColor style)
else (styleNormalFillColor style,
styleNormalEdgeColor style)) >>
loop (n + 1) ps
in loop 0 iolets
instance Draw Circle where
draw style mode circle =
drawCircle circle (modeFillCol mode style) (modeEdgeCol mode style)
translate dx dy (Circle center radius) =
Circle (translate dx dy center) radius
drawCircle :: Circle -> VColor -> VColor -> Render ()
drawCircle (Circle (Position x y) r) bgColor fgColor = do
newPath -- otherwise we get a line to the arc
arc x y r 0 (2 * pi)
setColor bgColor
fillPreserve
setColor fgColor
stroke
-- Helper functions to find the background and foreground colors for a mode
modeFillCol :: DrawMode -> (Style -> VColor)
modeFillCol DrawNormal = styleNormalFillColor
modeFillCol DrawActive = styleActiveFillColor
modeFillCol _ = styleSelectedFillColor
modeTextCol :: DrawMode -> (Style -> VColor)
modeTextCol DrawNormal = styleNormalTextColor
modeTextCol DrawActive = styleActiveTextColor
modeTextCol _ = styleSelectedTextColor
modeEdgeCol :: DrawMode -> (Style -> VColor)
modeEdgeCol DrawNormal = styleNormalEdgeColor
modeEdgeCol DrawActive = styleActiveEdgeColor
modeEdgeCol _ = styleSelectedEdgeColor