packages feed

ginsu-0.8.0.2: Screen.hs

{-# LANGUAGE OverlappingInstances #-}
module Screen(
    Packing(..),
    Attribute(..),
    Widget(..),
    Canvas(..),
    doRender,
    Key(..),
    RenderContext,
    newRenderContext,
    setRenderContext,
    tryDrawRenderContext,
    resizeRenderContext,
    setRenderWidget,
    keyRenderContext,
    keyCatcherWidget,
    newSVarWidget,
    dynamicWidget,
    widgetAttr,
    widgetCenter,
    widgetEmpty,
    widgetHorizontalBox,
    widgetScroll,
    widgetSimpleFrame,
    widgetText,
    widgetVerticalBox,
    stackedWidgets

    ) where

import Control.Concurrent
import Exception as E
import Data.Bits
import Control.Monad

import CacheIO
import Curses
import ErrorLog
import Format
import GenUtil
import Doc.Chars
import Doc.DocLike()


data Canvas = Canvas {window :: !Window, origin :: !(Int,Int), widgetOrigin :: !(Int,Int), bounds :: !(Int,Int)  }

data Widget = Widget {
    render :: Canvas -> IO (),
    getBounds :: IO (Int,Int),
    processKey :: Key -> IO Bool
    }


data Packing = NoExpand | Expand | ExpandFill
    deriving(Show,Eq,Enum)
data Attribute = AttrBold | AttrBlink | AttrDim | AttrReverse | AttrUnderline | AttrColor String String
    deriving(Show,Eq)

instance Show Canvas where
    show c = fmtSs "<canvas:origin=%s,bounds=%s>" [show (origin c), show (bounds c)]

widgetEmpty = emptyWidget
--widgetEmptySized x y = emptySizedWidget (floor (x + 0.5)) (floor (y + 0.5))
widgetCenter w = centerWidget w
widgetSimpleFrame w = boxWidget w
widgetVerticalBox True ws = homVerticalBox ws
widgetVerticalBox False ws = verticalBox ws []
widgetHorizontalBox True ws = homHorizontalBox ws
widgetHorizontalBox False ws = horizontalBox ws []
widgetText s = staticText s
widgetAttr al w = attrWidget (foldl (flip ($)) attr0 (map ma al)) w where
    ma AttrBlink a = a `setBlink` True
    ma AttrBold a = a  `setBold` True
    ma AttrDim a = a  `setDim` True
    ma AttrReverse a = a  `setReverse` True
    ma AttrUnderline a = a `setUnderline` True
    ma _ a = a
widgetScroll = newScrollWidget


isExpand NoExpand = False
isExpand _ = True

isFill ExpandFill = True
isFill _ = False

{-
data Attribute = Attribute [String] String String
parseAttr :: String -> Attribute
parseAttr s = Attribute as fg bg where
    rs = filter (not . f . head) $ groupBy (\x y -> f x && f y) (map toLower s)
    as = filter (`elem` attributes) rs
    col x = if isJust (color x) then return x else Nothing
    fg = fromJust $ msum (map (cGet "fg") rs)  `mplus` msum (map col rs) `mplus` return "default"
    bg = fromJust $ msum (map (cGet "bg") rs) `mplus` return "default"
    f ',' = True
    f c | isSpace c = True
    f _ = False
    cGet p r | (p ++ ":") `isPrefixOf` r = col (drop (length p + 1) r)
    cGet _ _ = Nothing
    attributes = ["normal", "bold", "blink", "dim", "reverse", "underline" ]
-}

data RenderContext = RenderContext {
    needsResize :: MVar Bool,
    drawingStuff :: MVar (IO (), Key -> IO Bool, IO ())
    }

newRenderContext = do
    y <- newMVar False
    z <- newMVar (return (), \_ -> return False, return ())
    return RenderContext {needsResize = y, drawingStuff = z }


resizeRenderContext rc action = swapMVar (needsResize rc) True >>= \x -> unless x action >> return ()

tryDrawRenderContext rc = do
    y <- swapMVar (needsResize rc) False
    withMVar (drawingStuff rc) $ \(z,_,_) -> when (True) $ do
	erase
	when y (attempt endWin >> refresh)
	z
	refresh

setRenderContext rc dr = do
    modifyMVar_ (drawingStuff rc)  $ \(_,_,final) -> do
    final
    --touchRenderContext rc
    return (dr,\_ -> return False, return ())

setRenderWidget rc w = do
    modifyMVar_ (drawingStuff rc)  $ \(_,_,final) -> do
    final
    --touchRenderContext rc
    return (wdr, processKey w, return (){-, nf-}) where
	wdr = do
	    c <- newCanvas
	    eannM ("doRender " ++ show c) $ render w c

keyRenderContext :: RenderContext -> Key -> IO Bool
keyRenderContext _ KeyResize = return True
keyRenderContext rc k = do
    (_,pks,_) <- readMVar (drawingStuff rc)
    pks k

doRender w = do
    erase
    c <- newCanvas
    eannM ("doRender " ++ show c) $ render w c
    refresh

newCanvas = do
    (ys,xs) <- scrSize
    return $  (Canvas {window = stdScr, origin = (0,0), widgetOrigin = (0,0), bounds = (xs,ys)})




-------------------
-- Drawing Routines
-------------------

canvasTranslate :: Canvas -> (Int,Int) -> Canvas
-- canvasTranslate canvas@(Canvas {origin = (x,y), bounds = (xb,yb)}) (xt,yt) = assert (xt > 0 && yt > 0) $ canvas {origin = (x + xt, y + yt), bounds = (max (xb - xt) 0, max (yb - yt) 0)}
canvasTranslate canvas@(Canvas {origin = (x,y), bounds = (xb,yb), widgetOrigin = (wx,wy)}) (xt,yt) = canvas {origin = (x + xt, y + yt), widgetOrigin = (wx, wy), bounds = (max (xb - xt) 0, max (yb - yt) 0)}


canvasWTranslate :: Canvas -> (Int,Int) -> Canvas
canvasWTranslate canvas@(Canvas {widgetOrigin = (x,y)}) (xt,yt) = canvas {widgetOrigin = (x + xt, y + yt)}

withAttr :: Canvas -> Attr -> IO a -> IO a
withAttr canvas attr action = E.bracket (wAttrGet w) (wAttrSet w) f where
    f (a,p) = wAttrSet w (a .|. attr,p) >> action
    w = window canvas


drawString :: Canvas -> String -> IO ()
drawString canvas s = eannM (fmtSs "drawString %s %s" [(show canvas), s]) $ ds ml yb (origin canvas) where
	ds (s:ls) yb (x,y) | yb > 0 = E.try (mvWAddStr (window canvas) y x (take xb s)) >> ds ls (yb - 1) (x,y + 1)
	ds _ _ _ = return ()
	(xb,yb) = bounds canvas
	ls = lines s
	(wx,wy) = widgetOrigin canvas
	ml = map (mv ' ' wx) (mv "" wy ls)
	mv _ 0 ss = ss
	mv _ n ss | n > 0 = drop n ss
	mv ec n ss = replicate (0 - n) ec ++ ss


boundCanvas :: Maybe Int -> Maybe Int -> Canvas -> Canvas
boundCanvas mx my canvas = (canvas {bounds = (nx,ny)}) where
    (cx,cy) = bounds canvas
    nx = maybe cx (min cx) mx
    ny = maybe cy (min cy) my


-- rendering routines

renderCentered :: Widget -> Canvas -> IO ()
renderCentered w canvas = do
	(xs,ys) <- getBounds w
	let (xs',ys') =  bounds canvas
	    f cs ws | ws >= cs = 0
	    f cs ws = (cs - ws) `div` 2
	    nc = (canvasTranslate canvas (f xs' xs,f ys' ys))
	renderChild w nc {bounds=(min xs $ fst $ bounds nc ,min ys $ snd $ bounds nc)}


renderChild :: Widget -> Canvas -> IO ()
renderChild _ (Canvas {bounds = (xb,yb)}) | xb == 0 || yb == 0 = return ()
renderChild w c = render w c

----------
-- Widgets
----------



emptyWidget = Widget {
    render = const (return ()),
    getBounds = return (0,0),
    processKey = \_ -> return False
    }


childrenWidget ws = emptyWidget {processKey = pk} where
    pk k = tk ws k
    tk [] _ = return False
    tk (w:ws) k = do
	b <- processKey w k
	if b then return True else tk ws k
-- layout widgets

-- | centers child widget, causes child to not use any extra space.
centerWidget :: Widget -> Widget
centerWidget w = w {render = rst} where
    rst canvas = do
	(xs,ys) <- getBounds w
	let (xs',ys') =  bounds canvas
	renderChild w (canvasTranslate canvas (f xs' xs,f ys' ys)) {bounds=(min xs $ fst $ bounds canvas ,min ys $ snd $ bounds canvas)} where
	    f cs ws | ws >= cs = 0
	    f cs ws = (cs - ws) `div` 2


-- General Widgets


newScrollWidget :: Widget -> IO Widget
newScrollWidget w = do
    yr <- newMVar 0
    let rst canvas = do
	    y <- readMVar yr
	    renderChild w (canvasWTranslate canvas (0,y))
	pk (KeyChar 'j') = modifyMVar_ yr (\x -> return $ x + 1) >> keyDone
	pk KeyDown = modifyMVar_ yr (\x -> return $ x + 1) >> keyDone
	pk (KeyChar 'k') = modifyMVar_ yr (\x -> return $ x - 1) >> keyDone
	pk KeyUp = modifyMVar_ yr (\x -> return $ x - 1) >> keyDone
	pk KeyHome = swapMVar yr 0 >> keyDone
	pk (KeyChar 'g') = swapMVar yr 0 >> keyDone
	pk KeyNPage = modifyMVar_ yr (\x -> return $ x + 25) >> keyDone
	pk KeyPPage = modifyMVar_ yr (\x -> return $ x - 25) >> keyDone
	pk k = processKey w k
	keyDone = modifyMVar_ yr (\x -> return  (max 0 x)) >> return True
    return w {processKey = pk, render = rst}


-- | draw box around child widget
boxWidget :: Widget -> Widget
boxWidget w = w {render = rst, getBounds = gb} where
    gb = getBounds w >>= \(x,y) -> return (x + 2, y + 2)
    rst canvas@(Canvas {bounds = (xb,yb)}) = do
	-- let tb = '+' : (replicate (xb - 2) '-' ++ "+")
	let tb = ulCorner ++ (replicate (xb - 2) hLine ++ urCorner)
	    bb = llCorner ++ (replicate (xb - 2) hLine ++ lrCorner)
	    bc c@(Canvas {bounds = (xb,yb)}) = c {bounds = (max (xb - 1) 0, max (yb - 1) 0)}
	renderChild w (canvasTranslate (bc canvas) (1,1))	
	drawString canvas tb
	drawString (canvasTranslate canvas (0,yb - 1)) bb
	sequence_ [drawString (canvasTranslate canvas (xt,yt)) (vLine :: String) | yt <- [1..yb - 2], xt <- [0,xb - 1]]


{-
widgetMinBBox :: Maybe Int -> Maybe Int -> Widget -> Widget
widgetMaxBBox :: Maybe Int -> Maybe Int -> Widget -> Widget
widgetExactBBox :: Maybe Int -> Maybe Int -> Widget -> Widget
-}

staticText s = emptyWidget {render = rst, getBounds = return bounds } where
    bounds = (maximum (0:map length ls),length ls)
    ls = lines s
    rst canvas = drawString canvas s


	


verticalBox :: [(Packing,Widget)] -> [(Packing,Widget)] -> Widget
verticalBox starts ends = (childrenWidget aw) {render = rd, getBounds = gb} where
    awpb = starts ++ ends
    awp = if any isExpand $ fsts awpb then awpb else starts ++ [(Expand,emptyWidget)] ++ ends
    aw = snds awp
    gb = do
	(xs,ys) <- fmap unzip $ mapM getBounds aw
	return (maximum xs, sum ys)
    rd canvas = do
	let (_,yb) = bounds canvas
        --let ywo = snd $ widgetOrigin $ canvas
	(_,ys) <- gb
	let ne = length (filter isExpand $ fsts awp)
	    el = if yb > ys then splitSpace (yb - ys) ne else replicate ne 0
	    f ((e:es),canvas) (ps,w) | isExpand ps = do
		(_,wby) <- getBounds w
		if isFill ps then
		    renderChild w (boundCanvas Nothing (Just (wby + e)) canvas)
			else renderCentered w (boundCanvas Nothing (Just (wby + e)) canvas)
		return (es,canvasTranslate canvas (0,wby + e))
	    f (es,canvas) (_,w) = do
		(_,wby) <- getBounds w
		--renderCentered w (boundCanvas Nothing (Just wby) canvas)
		renderChild w (boundCanvas Nothing (Just wby) canvas)
		return (es,canvasTranslate canvas (0,wby))
	foldlM_ f (el,canvas) awp


horizontalBox :: [(Packing,Widget)] -> [(Packing,Widget)] -> Widget
horizontalBox starts ends = (childrenWidget aw) {render = rd, getBounds = gb } where
    awpb = starts ++ ends
    awp = if any isExpand $ fsts awpb then awpb else starts ++ [(Expand,emptyWidget)] ++ ends
    aw = snds awp
    gb = do
	(xs,ys) <- fmap unzip $ mapM getBounds aw
	return (sum xs, maximum ys)
    rd canvas = do
	let (xb,_) = bounds canvas
	(xs,_) <- gb
	let ne = length (filter isExpand $ fsts awp)
	    el = if xb > xs then splitSpace (xb - xs) ne else replicate ne 0
	    f ((e:es),canvas) (ps,w) | isExpand ps = do
		(wbx,_) <- getBounds w
		if isFill ps then
		    renderChild w (boundCanvas (Just (wbx + e)) Nothing canvas)
			else renderCentered w (boundCanvas (Just (wbx + e)) Nothing canvas)
		return (es,canvasTranslate canvas (wbx + e,0))
	    f (es,canvas) (_,w) = do
		(wbx,_) <- getBounds w
		--renderCentered w (boundCanvas (Just wbx) Nothing canvas)
		renderChild w (boundCanvas (Just wbx) Nothing canvas)
		return (es,canvasTranslate canvas (wbx,0))
	foldlM_ f (el,canvas) awp

homVerticalBox :: [(Packing,Widget)] -> Widget
homVerticalBox awp = (childrenWidget aw) {render = rd, getBounds = gb } where
    aw = snds awp
    gb = do
	(xs,ys) <- fmap unzip $ mapM getBounds aw
	return (maximum xs, (maximum ys) * length aw)
    rd canvas = do
	let (_,yb) = bounds canvas
	    el = splitSpace yb (length aw)
	    f ([],_) _ = error "no space to split"
	    f ((e:es),canvas) (ps,w)  = do
		if isFill ps then
		    renderChild w (boundCanvas Nothing (Just e) canvas)
			else renderCentered w (boundCanvas Nothing (Just e) canvas)
		return (es,canvasTranslate canvas (0,e))
	foldlM_ f (el,canvas) awp


homHorizontalBox :: [(Packing,Widget)] -> Widget
homHorizontalBox awp = (childrenWidget aw) {render = rd, getBounds = gb } where
    aw = snds awp
    gb = do
	(xs,ys) <- fmap unzip $ mapM getBounds aw
	return (maximum xs * length aw, maximum ys)
    rd canvas = do
	let (xb,_) = bounds canvas
	    el = splitSpace xb (length aw)
	    f ([],_) _ = error "no space to split"
	    f ((e:es),canvas) (ps,w)  = do
		if isFill ps then
		    renderChild w (boundCanvas (Just e) Nothing canvas)
			else renderCentered w (boundCanvas (Just e) Nothing canvas)
		return (es,canvasTranslate canvas (e,0))
	foldlM_ f (el,canvas) awp

splitSpace m n = f (m `mod` n) $ replicate n (m `div` n) where
    f 0 xs = xs
    f n (x:xs) = (x + 1) : f (n - 1) xs
    f n' [] = errorf "splitSpace %i %i (f %i []): this can't happen." [fi m, fi n, fi n']


	
newSVarWidget :: (Readable c ) => c a -> (a -> Widget) -> Widget
newSVarWidget sv f = emptyWidget {render = rw, getBounds = gb} where
    rw canvas = do
	v <- readVal sv
	render (f v) canvas
    gb = do
	v <- readVal sv
	getBounds (f v)

dynamicWidget :: IO Widget -> Widget
dynamicWidget w = emptyWidget {render = rw, getBounds = gb, processKey = pk {- , processEvent = pe -} } where
    rw canvas = do
	v <- w
	render v canvas
    gb = w >>= getBounds
    pk k = w >>= \x -> processKey x k
--    pe a b = w >>= \x -> processEvent x a b



stackedWidgets :: [Widget] -> Widget
stackedWidgets [] = widgetEmpty
stackedWidgets ws@(w:_) = widgetEmpty { render = rnd, processKey = \k -> processKey w k, getBounds = gb  } where
    rnd canvas = mapM_ (flip render canvas) (reverse ws)
    gb = do
	bs <- mapM getBounds ws
	return $ (liftT2 (maximum, maximum)) (unzip bs)

{-
newAlternate :: Widget -> IO (Widget, Alternate)
newAlternate w = do
    sig <- newSignal
    final <- connect (changedSignal w) (\() -> signal sig ())
    r <- newMVar (w,final)
    let w = emptyWidget {render = rw, getBounds = gb, processKey = pk }
	rw canvas = do
	    (w,_) <- readMVar r
	    renderChild w canvas
	gb = do
	    (w,_) <- readMVar r
	    getBounds w
	pk k = do
	    (w,_) <- readMVar r
	    processKey w k
    return (w,Alternate r sig)


switchAlternate :: Alternate -> Widget -> IO ()
switchAlternate (Alternate mv sig) w = (modifyMVar_ mv $ \(_,final) ->
	(final >> connect (changedSignal w) (\() -> signal sig ()) >>= \nf -> return (w,nf)))
	    >> signal sig ()
-}

keyCatcherWidget :: (Curses.Key -> IO Bool) -> Widget -> Widget
keyCatcherWidget kr w = w {processKey = kr'} where
    kr' k = do
	b <- processKey w k
	if b then return True else (kr k)

attrWidget :: Attr -> Widget -> Widget
attrWidget a w = w {render = nr} where
    nr canvas = Screen.withAttr canvas a (renderChild w  canvas)

{-
-- data Event = KeyEvent Key | MouseEvent MouseEvent

--data Justification = Justified | LeftJustified | RightJustified


data InlineBox = InlineBoxText String | InlineBoxAttr [Attr] [InlineBox] | InlineBoxChoice InlineBox InlineBox |
    InlineBoxLB | InlineBoxRight [InlineBox] -- | InlineBoxAction (Event -> IO Bool) [InlineBox]




inlineBoxWidget :: [InlineBox] -> Widget
inlineBoxWidget xs = emptyWidget {render = rnd, getBounds = gb } where
    rnd canvas = undefined
    gb = return (0,0)

-}