{-# LANGUAGE FlexibleInstances, ViewPatterns #-} module SimpleH.GL ( module SimpleH.Reactive,module SimpleH,module SimpleH.GL.Base, -- * Creating windows & Handling events EventHandler,Position,Title, spawnWindow, Button(..),GLFW.KeyButtonState(..), Size(..), -- * Examining and modifying the window GettableStateVar,SettableStateVar, -- * Drawing the scene Scene, Widget(..),Transform(..), Shape(..),ShapeProp(..), Vertex(..),VertexProp(..), drawScene, -- ** Basic types Coord, -- * Utilities -- ** Basic colors white,black,grey,gray, red,green,blue,yellow,magenta,cyan, -- ** Vertices vert,cvert ) where import SimpleH import SimpleH.Reactive import qualified Graphics.UI.GLFW as GLFW import qualified Graphics.Rendering.OpenGL as GL import Graphics.Rendering.OpenGL hiding ( Color,Position,Vertex,Polygon,Texture, preservingMatrix,withMatrix) import Control.Concurrent import SimpleH.GL.Texture import SimpleH.GL.Base type EventHandler = Event Seconds Position -> Event Seconds (Button,GLFW.KeyButtonState) -> IO (Event Seconds (IO ())) type Title = String -- |Create an OpenGL window and sinks all events into the given handler. spawnWindow :: Title -> EventHandler -> IO () spawnWindow title sink = do -- This code was partially stolen from http://www.haskell.org/haskellwiki/GLFW sizes <- newChan GLFW.initialize -- open window GLFW.openWindow (Size 400 400) [GLFW.DisplayAlphaBits 8] GLFW.Window GLFW.windowTitle $= title -- enable 2D texturing texture Texture2D $= Enabled generateMipmap Texture2D $= Enabled shadeModel $= Smooth -- enable antialiasing lineSmooth $= Enabled blend $= Enabled blendFunc $= (SrcAlpha, OneMinusSrcAlpha) lineWidth $= 1.5 -- set the color to clear background clearColor $= Color4 0 0 0 0 -- set 2D orthogonal view inside windowSizeCallback because -- any change to the Window size should result in different -- OpenGL Viewport. GLFW.windowSizeCallback $= \ size@(Size w h) -> do writeChan sizes size viewport $= (GL.Position 0 0, size) matrixMode $= Projection loadIdentity let w' = realToFrac w ; h' = realToFrac h w'' = max 1 (w'/h') ; h'' = max 1 (h'/w') ortho2D (-w'') w'' (-h'') h'' -- Now we define our own little stuff -- It doesn't seem very logical to poll events when swapping buffers. GLFW.disableSpecial GLFW.AutoPollEvent let callbackE c f = do ch <- newChan c $= f (writeChan ch) event (readChan ch) _keyboard <- callbackE GLFW.keyCallback curry _mousePos <- callbackE GLFW.mousePosCallback (promap (\(GL.Position x y) -> V2 x y)) _mouseButton <- callbackE GLFW.mouseButtonCallback curry _size <- event (readChan sizes) let _button = (_1%~fromK<$>_keyboard) + (_1%~MouseButton<$>_mouseButton) fromK (GLFW.CharKey c) = CharKey c fromK (GLFW.SpecialKey k) = SpecialKey k relative (Size w h) (V2 x y) = V2 ((x'-xc)/m) ((yc-y')/m) where x' = realToFrac x ; y' = realToFrac y w' = realToFrac w ; h' = realToFrac h xc = w'/2 ; yc = h'/2 ; m = min w' h'/2 ev <- sink (relative<$>Reactive (Size 1 1) _size<|*>_mousePos) _button -- GLFW doesn't handle multithreading nicely, so we have to -- manually interleave polling events and rendering within -- the main thread. And since waiting for events may block -- while the rendering might occur, we have to poll regularly. -- I chose a 10ms polling period because it was both reactive -- enough for keyboard and mouse events and not too heavy on -- the CPU. let period = ms 10 for_ ((ev+(const<$>Reactive unit ev<|*>_size))^.._mapping _future._event) $ \(t,x) -> fix $ \poll -> do c <- GLFW.pollEvents >> currentTime if t <= pure (c+period) then when (t > pure (c-period)) x else waitTill (c+period) >> poll GLFW.closeWindow GLFW.terminate ms = (/1000) clearScreen = GL.clear [GL.ColorBuffer,GL.DepthBuffer] data Button = CharKey Char | SpecialKey GLFW.SpecialKey | MouseButton GLFW.MouseButton deriving (Eq,Show) type Scene t = [Widget t] data Widget t = Shape [ShapeProp] (Shape t) | SubScene [Transform t] (Scene t) data Shape t = Polygon [Vertex t] data Vertex t = Vertex [VertexProp t] !t !t !t data VertexProp t = Color (V4 t) | TexCoord (V2 t) data ShapeProp = Texture Texture data Transform t = Translate !t !t !t | Rotate !t (V3 t) | Zoom !t !t !t type Coord = GLfloat instance Semigroup Coord instance Monoid Coord instance Ring Coord type Position = V2 Coord instance Graphics (Widget Coord) where draw (Shape ps s) = traverse_ draw ps >> draw s draw (SubScene trs s) = preservingMatrix $ (traverse_ draw trs >> drawScene s) where draw (Translate dx dy dz) = translate (Vector3 dx dy dz) draw (Rotate a (V3 ax ay az)) = rotate a (Vector3 ax ay az) draw (Zoom zx zy zz) = scale zx zy zz instance Graphics (Shape Coord) where draw (Polygon p) = renderPrimitive GL.Polygon $ traverse_ draw p instance Graphics (Vertex Coord) where draw (Vertex ps x y z) = traverse_ draw ps >> vertex (Vertex3 x y z) instance Graphics (VertexProp Coord) where draw (Color (V4 r g b a)) = color (Color4 r g b a) draw (TexCoord (V2 x y)) = texCoord (TexCoord2 x y) instance Graphics ShapeProp where draw (Texture t) = draw t withMatrix f = GL.get (matrix Nothing) >>= f preservingMatrix ma = withMatrix $ \old -> ma <* (matrix Nothing $= (old :: GL.GLmatrix Coord)) drawScene :: Scene Coord -> IO () drawScene = between clearScreen GLFW.swapBuffers . traverse_ draw instance Functor Vector3 where map f (Vector3 x y z) = Vector3 (f x) (f y) (f z) instance Unit Vector3 where pure = join (join Vector3) instance Applicative Vector3 where Vector3 fx fy fz <*> Vector3 x y z = Vector3 (fx x) (fy y) (fz z) instance Functor Vertex3 where map f (Vertex3 x y z) = Vertex3 (f x) (f y) (f z) instance Unit Vertex3 where pure = join (join Vertex3) instance Applicative Vertex3 where Vertex3 fx fy fz <*> Vertex3 x y z = Vertex3 (fx x) (fy y) (fz z) vert = Vertex [] cvert c = Vertex [c] rgb r g b = V4 r g b 1 white = rgb 1 1 1 black = rgb 0 0 0 grey g = rgb g g g gray = grey red = rgb 1 0 0 green = rgb 0 1 0 blue = rgb 0 0 1 yellow = green+red magenta = red+blue cyan = green+blue