-- |This is a program which uses OpenGL and the PupEvents framework to -- demonstrate an example usage pattern. It simply takes a click in -- the window, sends it to the server (which negates the coordinates), -- and then paints the original and new Click coordinates with a point. -- This uses the -threaded option in GHC. module Main where import Graphics.Rendering.OpenGL.GL (($=), ($=!), makeGettableStateVar) import qualified Graphics.Rendering.OpenGL.GL as GL import qualified Graphics.UI.GLUT as GLUT import qualified PupEventsPQueue as PQ import qualified EventsClient as Events import Control.Concurrent.STM import Control.Concurrent import Control.Monad import System.Environment -- |Initial OpenGL setup function. The interesting thing here is that -- we fork a thread using 'checkEvents' to check for events. main :: IO () main = do args <- getArgs let ip = args !! 0 let priorities = read (args !! 1) :: Int (progname, _) <- GLUT.getArgsAndInitialize (outqueue, inqueue) <- Events.doClient (Just ip) priorities GLUT.createWindow "Hello World" GLUT.initialDisplayMode $= [GLUT.DoubleBuffered, GLUT.RGBAMode] GLUT.keyboardMouseCallback $=! Just (keyboardMouse inqueue outqueue) GLUT.displayCallback $=! render forkOS $ checkEvents inqueue GL.clearColor $= GL.Color4 0 0 0 1 GL.blend $= GL.Enabled GL.blendFunc $= (GL.SrcAlpha, GL.OneMinusSrcAlpha) GL.lineSmooth $= GL.Enabled GL.pointSmooth $= GL.Enabled GL.polygonSmooth $= GL.Enabled GL.clear [GL.ColorBuffer] GL.normalize $= GL.Enabled GLUT.mainLoop -- |This is very simple, since all of the drawing is done by Event -- handlers. We set this function as the display callback and call it -- after each event has been handled. render :: IO () render = GLUT.swapBuffers -- |This checks the inqueue returned by the Client module for Events, -- blocking if it can't find one. We fork this off because if the -- functionality here resided in the display callback it would block -- the entire application from doing anything while it was waiting for -- an event, including calling the display callback. Using this method -- we handle Events as the come in and call the display callback to -- swap the buffers when it's done. checkEvents :: PQ.PQueue Events.Event -- ^ The queue to check on -> IO b checkEvents pqueue = forever $ do event <- atomically $ do e <- PQ.getThing pqueue case e of Nothing -> retry Just event -> return event putStrLn $ "Got event" (Events.lookupHandlerClient event) event GLUT.postRedisplay Nothing -- |This is the keyboardMouse callback that gets used by OpenGL. We -- first normalize the coordinates to a -1,1 range, then we push a -- Click event to the incoming queue (not all events on the queue have -- to come from the Server!) and also on the outgoing queue so the -- server can handle it. keyboardMouse :: PQ.PQueue Events.Event -- ^ The incoming events queue -> PQ.PQueue Events.Event -- ^ The outgoing events queue -> GLUT.Key -> GLUT.KeyState -> t -> GLUT.Position -> IO () keyboardMouse iqueue oqueue (GLUT.MouseButton GLUT.LeftButton) GLUT.Down _ (GLUT.Position x y) = do (p1, p2) <- normalizeXY (fromIntegral x) (fromIntegral y) putStrLn $ "click coords: " ++ show x ++ ", " ++ show y putStrLn $ "normalized: " ++ show p1 ++ ", " ++ show p2 let event = Events.Click p1 p2 putStrLn $ "KeyboardMouse callback" atomically $ PQ.writeThing oqueue (Events.lookupPriorityClient event) event atomically $ PQ.writeThing iqueue (Events.lookupPriorityClient event) event putStrLn $ "wrote things" keyboardMouse _ _ _ _ _ _ = return () -- |This is a function to normalize coordinates that are given with -- respect to window dimensions. OpenGL likes it's primitives to have -- drawing coordinates ranging from -1 to 1 on the x or y axis. normalizeXY :: (Fractional t, Fractional t1) => t -- ^ X coordinate -> t1 -- ^ Y coordinate -> IO (t, t1) -- ^ Normalized coordinates (x, y) normalizeXY x y = do (_, (GLUT.Size width height)) <- GLUT.get GLUT.viewport let w = fromIntegral width let h = fromIntegral height return ((x-w/2.0)/(w/2.0), (y-h/2.0)/((-h)/2.0))