packages feed

gluturtle-0.0.33: src/Graphics/UI/GLUT/Turtle/Field.hs

module Graphics.UI.GLUT.Turtle.Field(

	-- * types and classes
	Field,
	Coordinates(..),

	-- * basic functions
	initialize,
	openField,
	openConsole,
	setConsole,
	closeField,
	topleft,
	center,
	coordinates,
	fieldSize,
	setFieldSize,

	-- * draw
	forkField,
	flushField,
	fieldColor,

	-- ** to Layer
	drawLine,
	fillRectangle,
	fillPolygon,
	writeString,
	drawImage,
	undoField,

	-- ** to Character
	drawCharacter,
	drawCharacterAndLine,
	clearCharacter,

	outputString,

	-- * event driven
	oninputtext,
	onclick,
	onrelease,
	ondrag,
	onmotion,
	onkeypress,
	ontimer,

	prompt
) where

import Control.Monad

import Graphics.UI.GLUT.Turtle.Triangles

import qualified Graphics.UI.GLUT.Turtle.GLUTools as G
import Graphics.UI.GLUT.Turtle.GLUTools(
	($=), initialize, createWindow, printLines, keyboardCallback, loop,
	displayAction)
import Text.XML.YJSVG(Position(..), Color(..))

import Control.Concurrent(ThreadId, forkIO, Chan, newChan, writeChan, readChan)
import Data.IORef(IORef, newIORef, readIORef, writeIORef)
import Data.IORef.Tools(atomicModifyIORef_)
import Data.Maybe

--------------------------------------------------------------------------------

prompt :: Field -> String -> IO ()
prompt f p = do
	mc <- getConsole f
	case mc of
		Just c -> do
			writeIORef (cPrompt c) p
			atomicModifyIORef_ (cCommand c)
				(\ls -> init ls ++ [p ++ last ls])
		_ -> return ()

data Coordinates = CoordTopLeft | CoordCenter

data Console = Console{
	cPrompt :: IORef String,
	cCommand :: IORef [String],
	cHistory :: IORef [String],
	cChanged :: IORef Int,
	cChanChanged :: IORef Int,
	cChan :: Chan String
 }

data Field = Field{
	fConsole :: IORef (Maybe Console),

	fFieldWindow :: G.Window,
	fSize :: IORef (Int, Int),
	fCoordinates :: IORef Coordinates,
	fBgcolor :: IORef [Color],

	fAction :: IORef (IO ()),
	fActions :: IORef [Maybe (IO ())],

	fChanged :: IORef Int,

	fInputtext :: IORef (String -> IO Bool),
	fOnclick :: IORef (Int -> Double -> Double -> IO Bool)
 }

--------------------------------------------------------------------------------

openConsole :: String -> Int -> Int -> IO Console
openConsole name w h = do
	cwindow <- createWindow name w h
	cprompt <- newIORef ""
	ccommand <- newIORef [""]
	chistory <- newIORef []
	cchanged <- newIORef 1
	cchanchanged <- newIORef 0
	cchan <- newChan
	let	c = Console{
			cPrompt = cprompt,
			cCommand = ccommand,
			cHistory = chistory,
			cChanged = cchanged,
			cChanChanged = cchanchanged,
			cChan = cchan }
	keyboardCallback $ processKeyboard c
	displayAction cchanged $ do
		cmd <- readIORef ccommand
		hst <- readIORef chistory
		printLines cwindow 1.0 $ reverse cmd ++ hst
	return c

processKeyboard ::
	Console -> Char -> G.KeyState -> G.Modifiers -> G.Position -> IO ()
processKeyboard console '\r' G.Down _ _ = do
	atomicModifyIORef_ (cChanged console) (+ 1)
	p <- readIORef $ cPrompt console
	str <- readIORef (cCommand console)
	atomicModifyIORef_ (cHistory console) (reverse str ++)
	writeIORef (cCommand console) [p]
	atomicModifyIORef_ (cChanChanged console) (+ 1)
	writeChan (cChan console) $ drop (length p) $ concat str
processKeyboard c '\b' G.Down _ _ = do
	atomicModifyIORef_ (cChanged c) (+ 1)
	p <- readIORef $ cPrompt c
	atomicModifyIORef_ (cCommand c) $ \s -> case s of
		[""] -> [""]
		[ss] | length ss <= length p -> s
		_ -> case (init s, last s) of
			(i, "") -> init i ++ [init $ last i]
			(i, l) -> i ++ [init l]
processKeyboard c chr G.Down _ _ = do
	atomicModifyIORef_ (cChanged c) (+ 1)
	atomicModifyIORef_ (cCommand c) (`addToTail` chr)
processKeyboard _ _ _ _ _ = return ()

addToTail :: [String] -> Char -> [String]
addToTail [] _ = error "bad"
addToTail strs c
	| length (last strs) < 50 = init strs ++ [last strs ++ [c]]
	| otherwise = strs ++ [[c]]

openField :: String -> Int -> Int -> IO Field
openField name w h = do
	fsize <- newIORef (w, h)
	fcoord <- newIORef CoordCenter
	fbgcolor <- newIORef [RGB 255 255 255]

	faction <- newIORef $ return ()
	factions <- newIORef []

	fchanged <- newIORef 0
	finputtext <- newIORef $ const $ return True
	fclick <- newIORef (\_ _ _ -> return True)

	ffield <- createWindow name w h

	let	act = do
			G.currentWindow $= Just ffield
			actwt
		actwt = do
			G.Size w' h' <- G.get G.windowSize
			writeIORef fsize $ (fromIntegral w', fromIntegral h')
			G.clearColor $= G.Color4 0 0 0 0
			G.clear [G.ColorBuffer]
			makeFieldColor . head =<< readIORef fbgcolor
			sequence_ . reverse . catMaybes =<< readIORef factions
			join $ readIORef faction
			G.swapBuffers
	displayAction fchanged act
	G.reshapeCallback $= Just (\size -> G.viewport $= (G.Position 0 0, size))

	fconsole <- newIORef Nothing
	let f = Field{
		fConsole = fconsole,

		fFieldWindow = ffield,
		fSize = fsize,
		fCoordinates = fcoord,
		fBgcolor = fbgcolor,

		fAction = faction,
		fActions = factions,

		fChanged = fchanged,

		fInputtext = finputtext,
		fOnclick = fclick
	 }
	G.keyboardMouseCallback $= Just (processKeyboardMouse f)

	return f

setConsole :: Field -> Console -> IO ()
setConsole f console = do
	loop (cChanChanged console) $ do
		cmd <- readChan $ cChan console
		continue <- readIORef (fInputtext f) >>= ($ cmd)
		unless continue G.leaveMainLoop
	writeIORef (fConsole f) $ Just console

processKeyboardMouse :: Field -> G.Key -> G.KeyState -> G.Modifiers -> G.Position -> IO ()
processKeyboardMouse f (G.Char c) ks m p = do
	mc <- getConsole f
	case mc of
		Just con -> do
			processKeyboard con c ks m p
			atomicModifyIORef_ (fChanged f) (+ 1)
		Nothing -> return ()
processKeyboardMouse f (G.MouseButton mb) G.Down _m (G.Position x_ y_) = do
	coord <- readIORef (fCoordinates f)
	continue <- case coord of
		CoordCenter -> do
			(w, h) <- fieldSize f
			let	x = fromIntegral x_ - (w / 2)
				y = (h / 2) - fromIntegral y_
			readIORef (fOnclick f) >>= (\fun -> fun (buttonToInt mb) x y)
		CoordTopLeft -> do
			let	(x, y) = (fromIntegral x_, fromIntegral y_)
			readIORef (fOnclick f) >>= (\fun -> fun (buttonToInt mb) x y)
	unless continue G.leaveMainLoop
processKeyboardMouse _f (G.MouseButton _mb) G.Up _m _p = do
	return ()
processKeyboardMouse _f (G.SpecialKey _sk) _ks _m _p = do
	return ()

buttonToInt :: G.MouseButton -> Int
buttonToInt G.LeftButton = 1
buttonToInt G.MiddleButton = 2
buttonToInt G.RightButton = 3
buttonToInt G.WheelUp = 4
buttonToInt G.WheelDown = 5
buttonToInt (G.AdditionalButton n) = n

undoField :: Field -> IO ()
undoField f = do
	a : _ <- readIORef $ fActions f
	when (isNothing a) $ atomicModifyIORef_ (fBgcolor f) tail
	atomicModifyIORef_ (fActions f) tail

closeField :: Field -> IO ()
closeField _ = G.leaveMainLoop

topleft, center :: Field -> IO ()
topleft = flip writeIORef CoordTopLeft . fCoordinates
center = flip writeIORef CoordCenter . fCoordinates

coordinates :: Field -> IO Coordinates
coordinates = readIORef . fCoordinates

fieldSize :: Field -> IO (Double, Double)
fieldSize f = do
	(w, h) <- readIORef $ fSize f
	return (fromIntegral w, fromIntegral h)

--------------------------------------------------------------------------------

forkField :: Field -> IO () -> IO ThreadId
forkField _f = forkIO

flushField :: Field -> Bool -> IO a -> IO a
flushField _f _real act = act

fieldColor :: Field -> Color -> IO ()
fieldColor f clr = do
	atomicModifyIORef_ (fBgcolor f) (clr :)
	atomicModifyIORef_ (fActions f) (Nothing :)

makeFieldColor :: Color -> IO ()
makeFieldColor clr = G.preservingMatrix $ do
	G.color $ colorToColor4 clr
	G.renderPrimitive G.Quads $ mapM_ G.vertex [
		G.Vertex2 (-1) (-1),
		G.Vertex2 (-1) 1,
		G.Vertex2 1 1,
		G.Vertex2 1 (-1) :: G.Vertex2 G.GLfloat ]

--------------------------------------------------------------------------------

setFieldSize :: Field -> Double -> Double -> IO ()
setFieldSize f w_ h_ = do
	let	w = round w_
		h = round h_
	writeIORef (fSize f) (w, h)
	G.currentWindow $= Just (fFieldWindow f)
	G.windowSize $= G.Size (fromIntegral w) (fromIntegral h)

drawLine :: Field -> Double -> Color -> Position -> Position -> IO ()
drawLine f w c p q = do
	atomicModifyIORef_ (fActions f) (Just (makeLineAction f p q c w) :)
	atomicModifyIORef_ (fChanged f) (+ 1)
	G.flush

makeLineAction :: Field -> Position -> Position -> Color -> Double -> IO ()
makeLineAction f p q c w = G.preservingMatrix $ do
	G.lineWidth $= fromRational (toRational w)
	G.color $ colorToColor4 c
	pp <- positionToVertex3 f p
	qq <- positionToVertex3 f q
	G.renderPrimitive G.Lines $ mapM_ G.vertex [pp, qq]

colorToColor4 :: Color -> G.Color4 G.GLfloat
colorToColor4 (RGB r g b) = G.Color4
	(fromIntegral r / 255) (fromIntegral g / 255) (fromIntegral b / 255) 0
colorToColor4 _ = error "colorToColor4: not implemented"

makeCharacterAction :: Field -> [Position] -> Color -> Color -> Double -> IO ()
makeCharacterAction f ps c lc lw = do
	ps' <- mapM (positionToPos f) ps
	vs <- mapM (positionToVertex3 f . posToPosition) $
		triangleToPositions $ toTriangles ps'
	vs' <- mapM (positionToVertex3 f) ps
	G.preservingMatrix $ do
		G.color $ colorToColor4 c
		G.renderPrimitive G.Triangles $ mapM_ G.vertex vs
		G.lineWidth $= fromRational (toRational lw)
		G.color $ colorToColor4 lc
		G.renderPrimitive G.LineLoop $ mapM_ G.vertex vs'

type Pos = (Double, Double)
triangleToPositions :: [(Pos, Pos, Pos)] -> [Pos]
triangleToPositions [] = []
triangleToPositions ((a, b, c) : rest) = a : b : c : triangleToPositions rest

positionToPos :: Field -> Position -> IO Pos
positionToPos _ (Center x y) = return (x, y)
positionToPos f (TopLeft x y) = do
	(w, h) <- fieldSize f
	return (x - w / 2, h / 2 - y)

posToPosition :: Pos -> Position
posToPosition (x, y) = Center x y

positionToVertex3 :: Field -> Position -> IO (G.Vertex2 G.GLfloat)
positionToVertex3 f (Center x y) = do
	(w, h) <- readIORef $ fSize f
	return $ G.Vertex2
		(fromRational $ 2 * toRational x / fromIntegral w)
		(fromRational $ 2 * toRational y / fromIntegral h)
positionToVertex3 f (TopLeft x y) = do
	(w, h) <- readIORef $ fSize f
	let	x' = 2 * toRational x / fromIntegral w - 1
		y' = 1 - 2 * toRational y / fromIntegral h
	return $ G.Vertex2 (fromRational x') (fromRational y')

writeString :: Field -> String -> Double -> Color -> Position ->
	String -> IO ()
writeString f _fname size clr (Center x_ y_) str =
	atomicModifyIORef_ (fActions f) (Just action :)
	where
	action = G.preservingMatrix $ do
		(w, h) <- readIORef $ fSize f
		let	size' = size / 15
			ratio = 3.5 * fromIntegral h
			x_ratio = 2 * ratio / fromIntegral w
			y_ratio = 2 * ratio / fromIntegral h
			x = x_ratio * fromRational (toRational $ x_ / size')
			y = y_ratio * fromRational (toRational $ y_ / size')
			s = 1 / ratio * fromRational (toRational size')
		G.color $ colorToColor4 clr
		G.scale (s :: G.GLfloat) (s :: G.GLfloat) (s :: G.GLfloat)
		G.clearColor $= G.Color4 0 0 0 0
		G.translate (G.Vector3 x y 0 :: G.Vector3 G.GLfloat)
		G.renderString G.Roman str
writeString _ _ _ _ _ _ = error "writeString: not implemented"

drawImage :: Field -> FilePath -> Position -> Double -> Double -> IO ()
drawImage _f _fp _pos _w _h = return ()

fillRectangle :: Field -> Position -> Double -> Double -> Color -> IO ()
fillRectangle _f _p _w _h _clr = return ()

fillPolygon :: Field -> [Position] -> Color -> Color -> Double -> IO ()
fillPolygon f ps clr lc lw = do
	atomicModifyIORef_ (fActions f) (Just (makeCharacterAction f ps clr lc lw) :)
	atomicModifyIORef_ (fChanged f) (+ 1)

--------------------------------------------------------------------------------

drawCharacter :: Field -> Color -> Color -> [Position] -> Double -> IO ()
drawCharacter f fclr clr sh lw = do
	makeCharacterAction f sh fclr clr lw
	writeIORef (fAction f) $
		makeCharacterAction f sh fclr clr lw
	atomicModifyIORef_ (fChanged f) (+ 1)

drawCharacterAndLine ::	Field -> Color -> Color -> [Position] ->
	Double -> Position -> Position -> IO ()
drawCharacterAndLine f fclr clr sh lw p q = do
	writeIORef (fAction f) $ do
		makeLineAction f p q clr lw
		makeCharacterAction f sh fclr clr lw
	atomicModifyIORef_ (fChanged f) (+ 1)

clearCharacter :: Field -> IO ()
clearCharacter f = writeIORef (fAction f) $ return ()

--------------------------------------------------------------------------------

getConsole :: Field -> IO (Maybe Console)
getConsole = readIORef . fConsole

outputString :: Field -> String -> IO ()
outputString f str = do
	mc <- getConsole f
	case mc of
		Just c -> atomicModifyIORef_ (cHistory c) (str :)
		_ -> return ()

oninputtext :: Field -> (String -> IO Bool) -> IO ()
oninputtext = writeIORef . fInputtext

onclick, onrelease :: Field -> (Int -> Double -> Double -> IO Bool) -> IO ()
onclick f act = writeIORef (fOnclick f) act
onrelease _ _ = return ()

ondrag :: Field -> (Int -> Double -> Double -> IO ()) -> IO ()
ondrag _ _ = return ()

onmotion :: Field -> (Double -> Double -> IO ()) -> IO ()
onmotion _ _ = return ()

onkeypress :: Field -> (Char -> IO Bool) -> IO ()
onkeypress _ _ = return ()

ontimer :: Field -> Int -> IO Bool -> IO ()
ontimer _ _ _ = return ()