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 Prelude 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