packages feed

scroll-1.20150313: Curses.hs

module Curses where

import UI.NCurses hiding (Window)
import Control.Monad.State.Strict
import qualified Data.Vector as V
import Data.Vector ((!))
import Control.Applicative

import Types
import View

inCurses :: (Palette -> Curses a) -> IO a
inCurses a = runCurses $ do
	void $ setCursorMode CursorInvisible
	setEcho False
	palette <- assignColors
	a palette

data Palette = Palette
	{ swallowedColor :: ColorID
	, invokedColor :: ColorID
	}

assignColors :: Curses Palette
assignColors = Palette
	<$> newColorID ColorYellow ColorBlack 1
	<*> newColorID ColorGreen ColorBlack 2

paint :: Palette -> (Palette -> ColorID) -> Update a -> Update a
paint palette selectcolor a = do
	setColor (selectcolor palette)
	r <- a
	setColor defaultColorID
	return r

-- Checks window bounds.
putGlyph :: ViewOffset -> MaxPos -> Pos -> Glyph -> Update ()
putGlyph (xoff, yoff) (xmax, ymax) (x,y) g
	| x' < xmax && x' > 0 && y' < ymax && y' > 0 = do
		moveCursor (fromIntegral y') (fromIntegral x')
		drawLineH (Just g) 1
	| otherwise = return ()
  where
	x' = x + xoff
	y' = y + yoff

headGlyph :: Glyph
headGlyph = bodyGlyph '@'

bodyGlyph :: Char -> Glyph
bodyGlyph c = Glyph c [AttributeStandout]

swallowedGlyph :: Char -> Glyph
swallowedGlyph c = Glyph c [AttributeStandout]

stomachColor :: Segment -> (Palette -> ColorID)
stomachColor s
	| segmentInvoked s = invokedColor
	| otherwise = swallowedColor

drawPlayer :: ViewOffset -> MaxPos -> Palette -> Player -> Update ()
drawPlayer offset maxpos palette p = do
	-- draw the body from the last segment to first, since
	-- segments sometimes sit on top of other segments.
	forM_ (reverse (playerBody p)) $
		drawSegment offset maxpos palette

	-- draw head last so the cursor is over it
	putGlyph offset maxpos (playerHead p) headGlyph

drawSegment :: ViewOffset -> MaxPos -> Palette -> Segment -> Update ()
drawSegment offset maxpos palette s
	| segmentSide s == CurrentSide =
		case segmentSwallowed s of
			Nothing -> putGlyph offset maxpos (segmentPos s) $ bodyGlyph $
				bodyChar $ segmentDirection s
			Just c -> paint palette (stomachColor s) $
				putGlyph offset maxpos (segmentPos s) $ swallowedGlyph c
	| otherwise = return ()

drawWindow :: Integer -> Int -> Window -> Update ()
drawWindow ymax xmax (Window (x,y) l) =
	when (x < xmax) $ do
		let xI = fromIntegral x
		let yI = fromIntegral y
		forM_ [0..length l - 1] $ \n -> do
			let yp = yI+fromIntegral n
			when (yp < ymax) $ do
				moveCursor yp xI
				drawString $ trim (l !! n)
  where
	trim = take (xmax - x - 1)

displayView :: View -> Palette -> Maybe Integer -> ViewOffset -> Curses (Maybe Event, ViewOffset)
displayView view palette timeoutms oldoffset = loop 
  where
	yv = viewVisible view
	loop = do
		w <- defaultWindow
		(ymaxI, xmaxI) <- screenSize
		let ymax = fromIntegral ymaxI
		let xmax = fromIntegral xmaxI

		let maxpos = (xmax, ymax)
		let newoffset@(xdelta, ydelta) = adjustOffset view oldoffset maxpos

		let (ytrimmer, yoff) = viewPort ydelta ymax (V.length yv)
		let yvtrimmed = ytrimmer yv

		let xsample = V.head yv
		let (xtrimmer, xoff) = viewPort xdelta xmax (V.length xsample)
		let xoffI = fromIntegral xoff

		updateWindow w $ do
			let clearline = drawLineH (Just (Glyph ' ' [])) xmaxI
			forM_ [0..ymax-2] $ \y -> do
				let yI = fromIntegral y
				let y' = y - yoff
				moveCursor yI 0
				void clearline
				when (y' < V.length yvtrimmed && y' >= 0) $ do
					let cs = V.toList $ xtrimmer $
						yvtrimmed ! y'
					unless (null cs) $ do
						moveCursor yI xoffI
						drawString cs

			drawPlayer newoffset maxpos palette (viewPlayer view)
			mapM_ (drawWindow ymaxI xmax) (viewWindows view)
		render

		mev <- getEvent w timeoutms
		case mev of
			Just (EventMouse _ _) -> loop
			Just (EventUnknown _) -> loop
			Just EventResized -> loop
			Just ev -> return (Just ev, newoffset)
			Nothing -> return (Nothing, newoffset)

arrowDirection :: Event -> Maybe Direction
arrowDirection (EventSpecialKey KeyLeftArrow) = Just DLeft
arrowDirection (EventSpecialKey KeyDownArrow) = Just DDown
arrowDirection (EventSpecialKey KeyUpArrow) = Just DUp
arrowDirection (EventSpecialKey KeyRightArrow) = Just DRight
arrowDirection (EventSpecialKey KeyEnter) = Just DDive
arrowDirection _ = Nothing