roguestar-gl-0.6.0.0: src/PrintText.hs
{-# LANGUAGE OverloadedStrings #-}
-- | A 'PrintTextObject' renders the text console at the bottom of the screen.
module PrintText
(newPrintTextObject,
printText,
setStatus,
PrintTextObject,
renderText,
PrintTextMode(..),
TextType(..),
getInputBuffer,
pushInputBuffer,
pullInputBuffer,
setInputBuffer,
setPrintTextMode,
clearOutputBuffer,
clearInputBuffer)
where
import Control.Concurrent.STM
import Graphics.Rendering.OpenGL as GL
import PrintTextData
import Control.Monad
import Control.Concurrent.Chan
import qualified Data.ByteString.Char8 as B
import qualified Data.Map as Map
import RSAGL.Color
import RSAGL.Color.RSAGLColors
import qualified KeyStroke as K
import DrawString
import Data.Monoid
data PrintTextData = PrintTextData {
text_output_buffer :: [(TextType,B.ByteString)],
text_input_buffer :: K.KeyString,
text_status_lines :: Map.Map StatusField B.ByteString,
text_output_mode :: PrintTextMode,
text_draw_strategy :: DrawString }
data PrintTextObject = PrintTextObject (TVar PrintTextData) (Chan K.KeyString)
newPrintTextObject :: DrawString -> IO PrintTextObject
newPrintTextObject draw_strategy =
do pt_data <- newTVarIO $ PrintTextData {
text_output_buffer = [],
text_input_buffer = mempty,
text_status_lines = Map.empty,
text_output_mode = Unlimited,
text_draw_strategy = draw_strategy }
pt_chan <- newChan
return $ PrintTextObject pt_data pt_chan
printText :: PrintTextObject -> TextType -> B.ByteString -> STM ()
printText (PrintTextObject pto _) text_type str =
do print_text <- readTVar pto
writeTVar pto $ print_text {
text_output_buffer = text_output_buffer print_text ++
map ((,) text_type) (B.lines str) }
setStatus :: PrintTextObject -> Map.Map StatusField B.ByteString -> STM ()
setStatus p@(PrintTextObject pto _) status_lines =
do print_text <- readTVar pto
let changes = Map.differenceWith
(\new old -> if new==old then Nothing else Just new)
status_lines
(text_status_lines print_text)
forM_ (Map.toAscList changes) $ \(field,string) ->
printText p Update $ onChange field string
writeTVar pto . (\x -> x { text_status_lines = status_lines }) =<< readTVar pto
pushInputBuffer :: PrintTextObject -> K.KeyStroke -> IO ()
pushInputBuffer (PrintTextObject _ chan) stroke = writeChan chan (K.keyStroke stroke)
getInputBuffer :: PrintTextObject -> IO K.KeyString
getInputBuffer (PrintTextObject pto _) =
liftM text_input_buffer $ atomically $ readTVar pto
-- | Pull one keypress into the input buffer, if it exists.
pullInputBuffer :: PrintTextObject -> IO ()
pullInputBuffer (PrintTextObject pto chan) =
do e <- isEmptyChan chan
when (not e) $
do stroke <- readChan chan
atomically $
do print_text <- readTVar pto
writeTVar pto $ print_text {
text_input_buffer = text_input_buffer print_text
`mappend` stroke }
return ()
setInputBuffer :: PrintTextObject -> K.KeyString -> IO ()
setInputBuffer (PrintTextObject pto _) new_input_buffer = atomically $
do print_text <- readTVar pto
writeTVar pto $ print_text { text_input_buffer = new_input_buffer }
clearOutputBuffer :: PrintTextObject -> STM ()
clearOutputBuffer (PrintTextObject pto _) =
do print_text <- readTVar pto
writeTVar pto $ print_text { text_output_buffer = [] }
setPrintTextMode :: PrintTextObject -> PrintTextMode -> STM ()
setPrintTextMode (PrintTextObject pto _) pt_mode =
do print_text <- readTVar pto
writeTVar pto $ print_text { text_output_mode = pt_mode }
clearInputBuffer :: PrintTextObject -> IO ()
clearInputBuffer (PrintTextObject pto _) = atomically $
do print_text <- readTVar pto
writeTVar pto $ print_text { text_input_buffer = mempty }
renderText :: Size -> PrintTextObject -> IO ()
renderText (Size width height) (PrintTextObject pto _) =
do ptd <- atomically $ readTVar pto
let draw_strategy = text_draw_strategy ptd
let max_characters_height =
(fromIntegral height - 2 * fromIntegral (draw_string_padding draw_strategy)) `div`
(fromIntegral (draw_string_font_height draw_strategy))
let max_characters_width =
(fromIntegral width - 2 * fromIntegral (draw_string_padding draw_strategy)) `div`
(fromIntegral (draw_string_font_width draw_strategy))
let lines_to_print =
restrictLines max_characters_height max_characters_width $
(case (text_output_mode ptd) of
PrintTextData.Disabled -> []
Limited -> reverse $ take 5 $ reverse $
text_output_buffer ptd
Unlimited -> (text_output_buffer ptd)) ++
(if K.length (text_input_buffer ptd) > 0 ||
text_output_mode ptd /= PrintTextData.Disabled
then [(Input,"> " `B.append` (B.pack $ K.prettyString $ text_input_buffer ptd))]
else [])
let status_lines = flip map (Map.toAscList $ text_status_lines ptd) $
\(field,string) -> (Update,whileActive field string)
drawLines (Size width height) (reverse lines_to_print) Log draw_strategy
drawLines (Size width height) (reverse status_lines) Status draw_strategy
data PrintTextPosition = Status | Log
drawLines :: Size ->
[(TextType,B.ByteString)] ->
PrintTextPosition ->
DrawString ->
IO ()
drawLines (Size width height) lines_to_print position draw_strategy =
do save_depth_func <- get depthFunc
save_depth_mask <- get depthMask
save_blend <- get blend
depthFunc $= Nothing
depthMask $= GL.Disabled
blend $= Enabled
matrixMode $= Projection
loadIdentity
ortho2D 0 (fromIntegral width) 0 (fromIntegral height)
matrixMode $= Modelview 0
loadIdentity
let actual_width_pixels :: Int
actual_width_pixels = draw_string_font_width draw_strategy *
(maximum $ map (B.length . snd) $ (undefined,""):lines_to_print)
let actual_height_pixels :: Int
actual_height_pixels = draw_string_font_height draw_strategy * (length lines_to_print)
let y_position :: Int
y_position = case position of
Status -> fromIntegral height - actual_height_pixels - (draw_string_padding draw_strategy)
Log -> draw_string_padding draw_strategy
color $ (Color4 0 0 0 0.92 :: Color4 GLfloat)
(rect :: Vertex2 GLfloat -> Vertex2 GLfloat -> IO ())
(Vertex2 (fromIntegral (draw_string_padding draw_strategy))
(fromIntegral y_position))
(Vertex2 (fromIntegral (actual_width_pixels +
(draw_string_padding draw_strategy)))
(fromIntegral (actual_height_pixels +
y_position +
(draw_string_padding draw_strategy)*2)))
currentRasterPosition $= (Vertex4 (fromIntegral $ draw_string_padding draw_strategy)
(fromIntegral $ y_position + draw_string_padding draw_strategy)
0 1)
mapM_ (drawLine draw_strategy) lines_to_print
blend $= save_blend
depthMask $= save_depth_mask
depthFunc $= save_depth_func
drawLine :: DrawString -> (TextType,B.ByteString) -> IO ()
drawLine draw_strategy (textType,str) =
do (Vertex4 x y _ _) <- get currentRasterPosition
color $ colorToOpenGL $ textTypeToColor textType
currentRasterPosition $= (Vertex4 x y 0 1)
drawString draw_strategy $ B.unpack str
currentRasterPosition $= (Vertex4 x (y + fromIntegral (draw_string_font_height draw_strategy)) 0 1)
restrictLines :: Int -> Int -> [(TextType,B.ByteString)] -> [(TextType,B.ByteString)]
restrictLines height width text_lines = reverse $ take height $ reverse $ concatMap splitLongLines text_lines
where splitLongLines (_,l) | B.null l = []
splitLongLines (textType,str) = (textType,B.take width str):(splitLongLines (textType,B.drop width str))
textTypeToColor :: TextType -> RGB
textTypeToColor UnexpectedEvent = orange
textTypeToColor Event = yellow
textTypeToColor Input = white
textTypeToColor Query = grey
textTypeToColor Update = light_brown