import System.Environment import System.Exit import System.IO import Data.Array.IArray import Data.ByteString.Char8 (pack) import Graphics.Vty -- pad left fills the left side with pad chars, pad right fills the right side -- with pad chars data Pad = Pad { padLeft :: String -> String , padRight :: String -> String } type Frame = [String] -- an immutable array of frames, one-indexed type Presentation = Array Int Frame newPad :: Char -> Int -> Pad newPad c w = Pad { padLeft = padl , padRight = padr } where pad' t = replicate (w - length t) c shorten = take w padl t = pad' t ++ shorten t padr t = shorten t ++ pad' t -- |Split an input list split :: Eq a => [a] -> [[a]] -> [[[a]]] split _ [] = [[]] split tok (x:xs) | tok == x = [] : split tok xs | otherwise = let (x':xs') = split tok xs in (x:x') : xs' -- |Convert a string to an image toImage :: String -> Image toImage = renderBS attr . pack -- |Render a frame. The first pair of Ints is the tuple (screen width, screen -- height), the second pair of Ints is the two numbers to display in the lower -- right corner, likely the tuple (slide num, total slides). renderFrame :: Frame -> (Int, Int) -> (Int, Int) -> Image renderFrame (title:body) (w, h) (num, tot) = vertcat $ map toImage text where p = newPad ' ' w t = padRight p (' ' : title) sep = replicate w '-' body' = take (min (h - 3) (length body)) body -- maybe shorten body bodyLines = [padRight p (' ':b) | b <- body'] vsep = [padRight p "" | _ <- [1 .. h - length body - 3]] foot = padLeft p (show num ++ " / " ++ show tot) text = foldl (++) [] [[t, sep], bodyLines, vsep, [foot]] -- |Take a Vty and a list of frames, and do the presentation; this function -- terminates when q or Ctrl-C are entered. loop :: Vty -> Presentation -> IO () loop vty pres = loop' minFrame where (minFrame, maxFrame) = bounds pres loop' n = do let frame = pres ! n dims <- getSize vty blankVty vty update vty $ pic { pCursor = NoCursor, pImage = renderFrame frame dims (n, maxFrame) } eventLoop where eventLoop = do ev <- getEvent vty case ev of EvKey KLeft _ -> loop' (max minFrame (n-1)) EvKey KRight _ -> loop' (min maxFrame (n+1)) EvKey KHome _ -> loop' minFrame EvKey KEnd _ -> loop' maxFrame EvKey (KASCII 'q') _ -> return () EvKey (KASCII 'c') [MCtrl] -> return () EvKey (KASCII 'r') _ -> blankVty vty >> loop' n _ -> eventLoop -- |Sometimes the vty module gets confused and leaves artifacts from previous -- slides on the screen. Calling blankvty a few times seems to fix this. blankVty :: Vty -> IO () blankVty vty = do (w, h) <- getSize vty let lines = [replicate w ' ' | _ <- [1..h]] update vty $ pic { pCursor = NoCursor , pImage = vertcat $ map toImage lines} refresh vty -- |Parse an input file, representing a presentation, into a presentation makeFrames :: String -> Presentation makeFrames xs = listArray (1, length frames) frames where frames = concatMap copyFrames $ basicSplit xs basicSplit = split "--" . lines copyFrames = cf [] . split "." cf _ [] = [] cf h (x:xs) = let p = h ++ x in p : cf (p ++ [[]]) xs main = do args <- getArgs presentation <- parse args h <- openFile presentation ReadMode frames <- fmap makeFrames $ hGetContents h vty <- mkVty loop vty frames shutdown vty where parse ["-h"] = usage parse [x] = return x parse _ = usage usage = putStrLn "Usage: hspresent [-h] " >> exit exit = exitWith ExitSuccess