When I create two windows and redraw them in two different threads (one per window), it seems like all drawing goes to first created window. It constantly switches between what should be displayed in both windows. And second one remains mostly black.
The code was working well with only one window, and then I updated it - inserted currentWindow $= Just glWindow in the beginning of the functions which set callbacks and call rendering methods.
What do you think is the cause of the problems?
EDIT:
Code skeleton:
module Chart.Window where
import Graphics.UI.GLUT hiding (Window, postRedisplay, <etc>)
import qualified Graphics.UI.GLUT as GLUT
import qualified Graphics.Rendering.OpenGL as GL
data Window = Window
{ glWindow :: GLUT.Window
, viewListRef :: IORef [Line]
}
main = do
forkOS start <params1>
forkOS start <params2>
start <params> = do
win <- new <params>
run win
mainLoop
new :: Strict -> (Int, Int) -> (Int, Int) -> IO Window
new name (posx, posy) (w, h) = do
initGLUT
glWindow <- createWindow name
currentWindow $= Just glWindow
windowSize $= Size (fromIntegral w) (fromIntegral h)
windowPosition $= Position (fromIntegral posx) (fromIntegral posy)
return Window {..}
initGLUT :: IO ()
initGLUT = do
beenInit <- get initState
unless beenInit $ void getArgsAndInitialize
initialDisplayMode $= [WithDepthBuffer, DoubleBuffered, RGBAMode]
initialWindowSize $= Size 100 100
initialWindowPosition $= Position 100 100
actionOnWindowClose $= ContinueExectuion
run :: Window -> IO ()
run win#Window{..} = do
-- this will fork (with forkIO) threads
-- which will call "repaint" when chart needs to be updated
initListeners repaint
initCallbacks win
where
repaint :: [Line] -> IO ()
repaint viewList = do
writeIORef viewListRef viewList
postRedisplay win
postRedisplay Window{..} = GLUT.postRedisplay $ Just glWindow
initCallbacks win#Window{..} = do
currentWindow $= Just glWindow
GLUT.displayCallback $= display win
GLUT.idleCallback $= Just (postRedisplay win)
display Window{..} = do
currentWindow $= Just glWindow
Size w h <- get windowSize
viewList <- readIORef viewListRef
drawChart viewList otherOptions
reshapeCallback :: Window -> GLUT.ReshapeCallback
reshapeCallback win#Window{..} size#(Size w h) = do
currentWindow $= Just glWindow
GL.viewport $= (Position 0 0, size)
GL.matrixMode $= GL.Projection
GL.loadIdentity
GL.ortho2D 0 (fromIntegral w) 0 (fromIntegral h)
GL.matrixMode $= GL.Modelview 0
GL.loadIdentity
... -- send command for listener thread to change internal state and postRedisplay
drawChart viewList otherOptions = do
...
-- chart consists of several horizontal panels. Call this for each panel:
glViewport 0 panelYPosition width winHeight
glScissor 0 panelYPosition (fromIntegral width) (fromIntegral panelHeight)
GL.clear [GL.ColorBuffer]
...
-- and then for each line=(Vertex2 v1 v2) from viewList
GL.renderPrimitive GL.Lines $ do
GL.vertex v1
GL.vertex v2
...
BTW, when I commented the line which sets reshapeCallback (and window is reshaped at the beginning) and launched charting with only one window, I got exactly the same effect as in multi-window launch. I mean, the (only) window was mostly empty as if it was secondly created.
I had a similar problem. I work with a thread that calculates the iterations of a genetic algorithm and in each iteration I call to "GL.postRedisplay (Just window)" but it didn't draw anything.
I solved my problem by calling "GL.postRedisplay (Just window)" from the idle function:
idle window = CC.threadDelay (1000*500) >> GL.postRedisplay (Just window)
Don't forget to setup your idle callback function like this:
GL.idleCallback GL.$= Just (idle window) >>
CC and GL mean:
import qualified Control.Concurrent as CC
import qualified Graphics.UI.GLUT as GL
Related
Simplifying the reality, my OpenGL program has the following structure:
At the beginning, there's a function f : (Double,Double,Double) -> Double.
Then there is a function triangulize :: ((Double,Double,Double) -> Double) -> [Triangle] such that triangulize f calculates a triangular mesh of the surface f(x,y,z)=0.
Then there is the displayCallback, a function display :: IORef Float -> DisplayCallBack which displays the graphics (that is to say it displays the triangular mesh). The first argument IORef Float is here to rotate the graphics, and its value (the angle of the rotation) changes when the user presses a key on the keyboard, thanks to the keyboardCallback defined later. Don't forget that the display function calls triangulize f.
Then the problem is the following one. When the user presses the key to rotate the graphic, the display function is triggered. And then triangulize f is re-evaluated, whereas it doesn't need to be re-evaluated: rotating the graphics does not change the triangular mesh (i.e. the result of triangulize f is the same as before).
So, is there a way to rotate the graphics by pressing a key without triggering triangulize f ? In other words, to "freeze" triangulize f so that it is evaluated only once and is never re-evaluated, which is time-consuming but useless since anyway the result is always the same.
I believe this is a standard way to rotate a graphics in Haskell OpenGL (I viewed that way in some tutos), so I don't think it is necessary to post my code. But of course I can post it if needed.
The reality is more complicated since there are other IORef's to control some parameters of the surface. But I would like to firstly know some solutions for this simplified situation.
EDIT: more details and some code
Simplified code
So, if I follow the simplified description above, my program looks like
fBretzel5 :: (Double,Double,Double) -> Double
fBretzel5 (x,y,z) = ((x*x+y*y/4-1)*(x*x/4+y*y-1))^2 + z*z
triangles :: [Triangle] -- Triangle: triplet of 3 vertices
triangles =
triangulize fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
-- "triangulize f (xbounds, ybounds, zbounds)"
-- calculates a triangular mesh of the surface f(x,y,z)=0
display :: IORef Float -> DisplayCallback
display rot = do
clear [ColorBuffer, DepthBuffer]
rot' <- get rot
loadIdentity
rotate rot $ Vector3 1 0 0
renderPrimitive Triangles $ do
materialDiffuse FrontAndBack $= red
mapM_ drawTriangle triangles
swapBuffers
where
drawTriangle (v1,v2,v3) = do
triangleNormal (v1,v2,v3) -- the normal of the triangle
vertex v1
vertex v2
vertex v3
keyboard :: IORef Float -- rotation angle
-> KeyboardCallback
keyboard rot c _ = do
case c of
'e' -> rot $~! subtract 2
'r' -> rot $~! (+ 2)
'q' -> leaveMainLoop
_ -> return ()
postRedisplay Nothing
This causes the issue described above. Each time the key 'e' or 'r' is pressed, the triangulize function runs while its output remains the same.
True code (almost)
Now, here is a version of my program closest to the reality. In fact, it calculates a triangular mesh for a surface f(x,y,z)=l, where the "isolevel" l can be changed with the keyboard.
voxel :: IO Voxel
voxel = makeVoxel fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
-- the voxel is a 3D-array of points; each entry of the array is
-- the value of the function at this point
-- !! the voxel should never changes throughout the program !!
trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level = do
vxl <- voxel
computeContour3d vxl level
-- "computeContour3d vxl level" calculates a triangular mesh
-- of the surface f(x,y,z)=level
display :: IORef Float -> IORef Float -> DisplayCallback
display rot level = do
clear [ColorBuffer, DepthBuffer]
rot' <- get rot
level' <- get level
triangles <- trianglesBretz level'
loadIdentity
rotate rot $ Vector3 1 0 0
renderPrimitive Triangles $ do
materialDiffuse FrontAndBack $= red
mapM_ drawTriangle triangles
swapBuffers
where
drawTriangle (v1,v2,v3) = do
triangleNormal (v1,v2,v3) -- the normal of the triangle
vertex v1
vertex v2
vertex v3
keyboard :: IORef Float -- rotation angle
-> IORef Double -- isolevel
-> KeyboardCallback
keyboard rot level c _ = do
case c of
'e' -> rot $~! subtract 2
'r' -> rot $~! (+ 2)
'h' -> level $~! (+ 0.1)
'n' -> level $~! subtract 0.1
'q' -> leaveMainLoop
_ -> return ()
postRedisplay Nothing
A part of a solution
In fact, I have found a solution in order to "freeze" the voxel:
voxel :: Voxel
{-# NOINLINE voxel #-}
voxel = unsafePerformIO $ makeVoxel fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level =
computeContour3d voxel level
In this way, I think the voxel is never re-evaluated.
But there is still a problem. When the IORef rot changes, to rotate the graphics, then there's no reason to re-evaluate trianglesBretz: the triangular mesh of f(x,y,z)=level is always the same whatever the rotation.
So, how can I say to the display function: "hey! when rot changes, do not re-evaluate trianglesBretz, since you will find the same result" ?
I don't know how to use NOINLINE for trianglesBretz, as I did for voxel. Something which would "freezes" trianglesBretz level unless level changes.
And here is the 5-holes bretzel:
EDIT: solution based on #Petr Pudlák's answer.
After #Petr Pudlák's very good answer I came to the following code. I give this solution here in order to place the answer more in the context of OpenGL.
data Context = Context
{
contextRotation :: IORef Float
, contextTriangles :: IORef [Triangle]
}
red :: Color4 GLfloat
red = Color4 1 0 0 1
fBretz :: XYZ -> Double
fBretz (x,y,z) = ((x2+y2/4-1)*(x2/4+y2-1))^2 + z*z
where
x2 = x*x
y2 = y*y
voxel :: Voxel
{-# NOINLINE voxel #-}
voxel = unsafePerformIO $ makeVoxel fBretz ((-2.5,2.5),(-2.5,2.5),(-1,1))
trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level = computeContour3d voxel level
display :: Context -> DisplayCallback
display context = do
clear [ColorBuffer, DepthBuffer]
rot <- get (contextRotation context)
triangles <- get (contextTriangles context)
loadIdentity
rotate rot $ Vector3 1 0 0
renderPrimitive Triangles $ do
materialDiffuse FrontAndBack $= red
mapM_ drawTriangle triangles
swapBuffers
where
drawTriangle (v1,v2,v3) = do
triangleNormal (v1,v2,v3) -- the normal of the triangle
vertex v1
vertex v2
vertex v3
keyboard :: IORef Float -- rotation angle
-> IORef Double -- isolevel
-> IORef [Triangle] -- triangular mesh
-> KeyboardCallback
keyboard rot level trianglesRef c _ = do
case c of
'e' -> rot $~! subtract 2
'r' -> rot $~! (+ 2)
'h' -> do
l $~! (+ 0.1)
l' <- get l
triangles <- trianglesBretz l'
writeIORef trianglesRef triangles
'n' -> do
l $~! (- 0.1)
l' <- get l
triangles <- trianglesBretz l'
writeIORef trianglesRef triangles
'q' -> leaveMainLoop
_ -> return ()
postRedisplay Nothing
main :: IO ()
main = do
_ <- getArgsAndInitialize
_ <- createWindow "Bretzel"
windowSize $= Size 500 500
initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer]
clearColor $= white
materialAmbient FrontAndBack $= black
lighting $= Enabled
lightModelTwoSide $= Enabled
light (Light 0) $= Enabled
position (Light 0) $= Vertex4 0 0 (-100) 1
ambient (Light 0) $= black
diffuse (Light 0) $= white
specular (Light 0) $= white
depthFunc $= Just Less
shadeModel $= Smooth
rot <- newIORef 0.0
level <- newIORef 0.1
triangles <- trianglesBretz 0.1
trianglesRef <- newIORef triangles
displayCallback $= display Context {contextRotation = rot,
contextTriangles = trianglesRef}
reshapeCallback $= Just yourReshapeCallback
keyboardCallback $= Just (keyboard rot level trianglesRef)
idleCallback $= Nothing
putStrLn "*** Bretzel ***\n\
\ To quit, press q.\n\
\ Scene rotation:\n\
\ e, r, t, y, u, i\n\
\ Increase/Decrease level: h, n\n\
\"
mainLoop
And now my bretzel can be rotated without performing useless calculations.
I'm not very familiar with OpenGL, so I have some difficulty understanding the code in detail - please correct me if I misunderstood something.
I'd try to abstain from using unsafe functions or relying on INLINE as much as possible. This usually makes code brittle and obscures more natural solutions.
In the simplest case, if you don't need to re-evaluate triangularize, we could just replace it with its output. So we'd have
data Context = Context
{ contextRotation :: IORef Float,
, contextTriangles :: [Triangle]
}
and then
display :: Context -> DisplayCallback
which won't reevaluate triangles at all, they'll be computed only once when Context is created.
Now if there are two parameters, rotation and level, and triangles depend on the level, but not on rotation: The trick here would be to manage dependencies properly. Now we expose the storage for parameters explicitly (IORef Float), and as a consequence, we can't monitor when the value inside changes. But the caller doesn't need to know the representation of how the parameters are stored. It just needs to store them somehow. So instead, let's have
data Context = Context
{ contextRotation :: IORef Float,
, contextTriangles :: IORef [Triangle]
}
and
setLevel :: Context -> Float -> IO ()
That is, we expose a function to store the parameter, but we hide the internals. Now we can implement it as:
setLevel (Context _ trianglesRef) level = do
let newTriangles = ... -- compute the new triangles
writeIORef trianglesRef newTriangles
And as triangles don't depend on the rotation parameter, we can have just:
setRotation :: Context -> Float -> IO ()
setRoration (Context rotationRef _) = writeIORef rotationRef
Now the dependencies are hidden for callers. They can set the level or the rotation, without knowing what depends on them. At the same time, triangles are updated when needed (level changes), and only then. And Haskell's lazy evaluation gives a nice bonus: If the level changes multiple times before the triangles are needed, they are not evaluated. The [Triangle] thunk inside the IORef will be only evaluated when requested by display.
I have done numerous graphics with Haskell OpenGL. They are in my repo here: opengl-examples (the gallery is not exhaustive). However I have a problem: when I use materialShininess nothing happens. It there something to enable in order to have the shininess ?
Here is an example of one of my prog. It it not complete but I hope it's enough to identify the issue.
module CompoundFiveTetrahedra2
where
import CompoundFiveTetrahedra.Data
import Control.Monad (when)
import qualified Data.ByteString as B
import Data.IORef
import Graphics.Rendering.OpenGL.Capture (capturePPM)
import Graphics.Rendering.OpenGL.GL
import Graphics.UI.GLUT
import Text.Printf
import Utils.ConvertPPM
import Utils.OpenGL (negateNormal)
import Utils.Prism
blue,red,green,yellow,purple,white,black :: Color4 GLfloat
blue = Color4 0 0 1 1
red = Color4 1 0 0 1
green = Color4 0 1 0 1
yellow = Color4 1 1 0 1
white = Color4 1 1 1 1
black = Color4 0 0 0 1
purple = Color4 0.5 0 0.5 1
display :: IORef GLfloat -> IORef GLfloat -> IORef GLfloat -> IORef GLdouble
-> IORef GLint -> IORef GLfloat -> DisplayCallback
display rot1 rot2 rot3 zoom capture angle = do
clear [ColorBuffer, DepthBuffer]
r1 <- get rot1
r2 <- get rot2
r3 <- get rot3
z <- get zoom
a <- get angle
i <- get capture
loadIdentity
(_, size) <- get viewport
resize z size
rotate a $ Vector3 1 1 1
rotate r1 $ Vector3 1 0 0
rotate r2 $ Vector3 0 1 0
rotate r3 $ Vector3 0 0 1
mapM_ (drawEdge blue) (edges!!0)
mapM_ (drawEdge red) (edges!!1)
mapM_ (drawEdge green) (edges!!2)
mapM_ (drawEdge yellow) (edges!!3)
mapM_ (drawEdge purple) (edges!!4)
mapM_ (drawVertex blue) vertices1
mapM_ (drawVertex red) vertices2
mapM_ (drawVertex green) vertices3
mapM_ (drawVertex yellow) vertices4
mapM_ (drawVertex purple) vertices5
when (i > 0) $ do
let ppm = printf "tetrahedra%04d.ppm" i
png = printf "tetrahedra%04d.png" i
(>>=) capturePPM (B.writeFile ppm)
convert ppm png True
capture $~! (+1)
swapBuffers
drawVertex :: Color4 GLfloat -> Vertex3 GLfloat -> IO ()
drawVertex col v =
preservingMatrix $ do
translate $ toVector v
materialDiffuse Front $= col
renderObject Solid $ Sphere' 0.03 30 30
where
toVector (Vertex3 x y z) = Vector3 x y z
drawEdge :: Color4 GLfloat -> (Vertex3 GLfloat, Vertex3 GLfloat) -> IO ()
drawEdge col (v1,v2) = do
let cylinder = prism v1 v2 30 0.03
renderPrimitive Quads $ do
materialDiffuse Front $= col
mapM_ drawQuad cylinder
where
drawQuad ((w1,w2,w3,w4),n) = do
normal $ negateNormal n
vertex w1
vertex w2
vertex w3
vertex w4
resize :: Double -> Size -> IO ()
resize zoom s#(Size w h) = do
viewport $= (Position 0 0, s)
matrixMode $= Projection
loadIdentity
perspective 45.0 (w'/h') 1.0 100.0
lookAt (Vertex3 0 0 (-3 + zoom)) (Vertex3 0 0 0) (Vector3 0 1 0)
matrixMode $= Modelview 0
where
w' = realToFrac w
h' = realToFrac h
keyboard :: IORef GLfloat -> IORef GLfloat -> IORef GLfloat -> IORef GLint
-> KeyboardCallback
keyboard rot1 rot2 rot3 capture c _ =
case c of
'r' -> rot1 $~! subtract 1
't' -> rot1 $~! (+1)
'f' -> rot2 $~! subtract 1
'g' -> rot2 $~! (+1)
'v' -> rot3 $~! subtract 1
'b' -> rot3 $~! (+1)
'c' -> capture $~! (+1)
'q' -> leaveMainLoop
_ -> return ()
mouse :: IORef GLdouble -> MouseCallback
mouse zoom button keyState _ =
case (button, keyState) of
(LeftButton, Down) -> zoom $~! (+0.1)
(RightButton, Down) -> zoom $~! subtract 0.1
_ -> return ()
idle :: IORef GLfloat -> IdleCallback
idle angle = do
angle $~! (+ 2)
postRedisplay Nothing
main :: IO ()
main = do
_ <- getArgsAndInitialize
_ <- createWindow "Five tetrahedra"
initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer]
clearColor $= black
materialAmbient Front $= black
materialShininess Front $= 80 -- THIS DOES NOT WORK
lighting $= Enabled
light (Light 0) $= Enabled
position (Light 0) $= Vertex4 0 0 (-100) 1
ambient (Light 0) $= white
diffuse (Light 0) $= white
specular (Light 0) $= white
depthFunc $= Just Lequal
depthMask $= Enabled
shadeModel $= Smooth
rot1 <- newIORef 0.0
rot2 <- newIORef 0.0
rot3 <- newIORef 0.0
zoom <- newIORef 0.0
capture <- newIORef 0
angle <- newIORef 0.0
displayCallback $= display rot1 rot2 rot3 zoom capture angle
reshapeCallback $= Just (resize 0)
keyboardCallback $= Just (keyboard rot1 rot2 rot3 capture)
mouseCallback $= Just (mouse zoom)
idleCallback $= Just (idle angle)
mainLoop
Do I miss something to enable the shininess ?
EDIT
Here is an example with the R package rgl, which is also a wrapper to OpenGL. Look at the white part on the spheres. I cannot achieve that with Haskell.
Update: Try shininess of 1.0 to see the difference more clearly at low resolutions.
The shininess parameter affects the sharpness of specular lighting, so you need to turn this type of lighting on for your materials by giving them a specular color. (By default, the specular color is black, so the effect of the shininess parameter will not be visible.) You'll also want to reduce the shininess value for this scene, because it's too high to be very visible.
Try:
materialSpecular Front $= white
materialShininess Front $= 1.0
and you'll start to see white highlights, particularly along the curved edges of your shape. The flat faces will also reflect some white light, but only when they are nearly perpendicular to a line that's mid-angle between the viewer and the light source -- it's a little complicated.
Note that the specular color of most materials is taken to be some "multiple" of white (i.e., somewhere between black for a perfectly dull material to white for the shiniest materials in the scene). The only materials with tinted specular color would be colored metals, like gold or bronze.
Some additional notes:
You're using old-style OpenGL 2.1 shading, not "modern OpenGL", so you don't have to worry so much about the "shaders" that #user2297560 is talking about. OpenGL 2.1 comes with built-in shaders to do basic shading; with modern OpenGL, you have to build everything from scratch.
As #luqui mentioned, if you're looking for materials that actually reflect other parts of the scenes, this kind of shininess won't help you.
Here is the difference. Your original code on the left, the settings above on the right, on your "compoundfivetetrahedra" example. It'll look better if you increase the size of the window.
Note that it works better on curved surfaces. Here's your cylinder example, using:
materialShininess Front $= 5
materialSpecular Front $= white
You can see the shininess on the closer sphere.
I am trying to draw some simple images in gtk2hs cairo. I know you can save the current state using save command and restore using restore command. Is thee a way to check is a state is currently saved. I do not want my image to scale when resizing the window or is there a better to prevent resizing. I do not want to recompute the image every time the window is resized.
The save and restore actions are not really related to whether the image gets recomputed. However there is a demo included with gtk2hs that shows how to cache the result of executing a Cairo action, see cairo/demo/Clock.hs, especially lines 320-404 of main:
let redrawStaticLayers = do
(width, height) <- widgetGetSize window
drawWin <- widgetGetDrawWindow window
background <- createImageSurface FormatARGB32 width height
foreground <- createImageSurface FormatARGB32 width height
let clear = do
save
setOperator OperatorClear
paint
restore
renderWith background $ do
clear
drawClockBackground True width height
renderWith foreground $ do
clear
drawClockForeground True width height
writeIORef backgroundRef (Just background)
writeIORef foregroundRef (Just foreground)
onRealize window redrawStaticLayers
sizeRef <- newIORef (initialSize, initialSize)
timeoutHandlerRef <- newIORef Nothing
window `on` configureEvent $ do
(w,h) <- eventSize
liftIO $ do
size <- readIORef sizeRef
writeIORef sizeRef (w,h)
when (size /= (w,h)) $ do
background <- readIORef backgroundRef
foreground <- readIORef foregroundRef
maybe (return ()) surfaceFinish background
maybe (return ()) surfaceFinish foreground
writeIORef backgroundRef Nothing
writeIORef foregroundRef Nothing
timeoutHandler <- readIORef timeoutHandlerRef
maybe (return ()) timeoutRemove timeoutHandler
handler <- timeoutAddFull (do
writeIORef timeoutHandlerRef Nothing
redrawStaticLayers
widgetQueueDraw window
return False
) priorityDefaultIdle 300
writeIORef timeoutHandlerRef (Just handler)
return False
window `on` exposeEvent $ do
drawWin <- eventWindow
exposeRegion <- eventRegion
liftIO $ do
(width, height) <- drawableGetSize drawWin
background <- readIORef backgroundRef
foreground <- readIORef foregroundRef
renderWithDrawable drawWin $ do
region exposeRegion
clip
save
setOperator OperatorSource
setSourceRGBA 0 0 0 0
paint
restore
case background of
Nothing -> drawClockBackground False width height
Just background -> do
setSourceSurface background 0 0
paint
drawClockHands (isJust background) width height
case foreground of
Nothing -> drawClockForeground False width height
Just foreground -> do
setSourceSurface foreground 0 0
paint
return True
I am currently trying to write a graphical application in haskell using OpenGL and GLFW-b. I am rather new to OpenGL, and I am having some problems displaying the TextureObjects. I have tried two ways of doing it.
The first way I tried was to store the textures as an TextureObject, and then simply using it as an input argument to the render function (see below). However, this doesn't work, as
the texture objects simply appear as white squares.
I will also note that it probably took several second for the program to start, presumably because it loaded all the graphics.
The second thing I tried was to store the textures as an IO TextureObject. This worked
, but it is very slow. It slows down to a few frames per second. I suspect this is because the textures need to be reloaded every time they are drawn. In order to test this, I renamed the textures while the program are running, and indeed the program crashed, confirming that it needs to reload the textures every iteration.
The texture is loaded with the function
loadTexture' :: FilePath -> IO TextureObject
loadTexture' f = do
tex <- either error id <$> readTexture f
textureFilter Texture2D $= ((Linear', Nothing), Linear')
return tex
and rendered with
renderTexture :: Area -> Area -> TextureObject -> IO()
renderTexture window area tex =
let (x,x') = xRangeToGL window $ getXRange area
(y,y') = yRangeToGL window $ getYRange area
in do textureBinding Texture2D $= Just tex
renderPrimitive Quads $ do
col
txc 1 1 >> ver x' y'
txc 1 0 >> ver x' y
txc 0 0 >> ver x y
txc 0 1 >> ver x y'
where col = color (Color3 1.0 1.0 1.0 :: Color3 GLfloat)
ver x y = vertex (Vertex2 x y :: Vertex2 GLfloat)
txc u v = texCoord (TexCoord2 u v :: TexCoord2 GLfloat)
The OpenGL settings are
clearColor $= Color4 r g b 1.0
depthFunc $= Just Lequal
blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
normalize $= Enabled
texture Texture2D $= Enabled
shadeModel $= Smooth
This is my first post, so please tell me if I have missed something, and thank you for your kind assistance!
Bad practice solution:
I have come up with a solution, I have added it as an edit, rather than a solution, because I'm not certain that it is good practice --- it is not the type of answer I would like to have to this post, but it can still be of use to those who wish to answer.
Calling loadTexture using unsafeCoerce,
unsafePerformIO $ loadTexture "foo/bar.png"
works. However, rewriting the loadTexture' function as
loadTexture' :: FilePath -> IO TextureObject
loadTexture' f = do
let tex = unsafePerformIO $ either error id <$> readTexture f
textureFilter Texture2D $= ((Linear', Nothing), Linear')
return tex
does not work.
I'm trying to make a Timer in Haskell using gtk2hs.
I found an example on this website wiki.haskell Tutorial Threaded GUI
which I could successfully implement in my project. The only problem I'm facing is creating a restart button for the timer.
My goal is that when people pres the "New game" button, that a new game starts and that the timer resets.
If a want to just restart a game I can use this line of code
onClicked button1 (startNewGame table window)
, which works. The problem is I can't find a way to bind a the start timer function to a button.
I tried doing this:
onClicked button1 (do (startTimer box) (startNewGame table window))
Which does not work, also this does not work:
onClicked button1 (startTimer box)
How am I suppose to restart a thread correctly?
When I run this code:
onClicked button1 (startTimer box)
I get this error:
gui.hs:29:25:
Couldn't match type `ThreadId' with `()'
Expected type: IO ()
Actual type: IO ThreadId
In the return type of a call of `startTimer'
In the second argument of `onClicked', namely `(startTimer box)'
In a stmt of a 'do' block: onClicked button1 (startTimer box)
How can I bind the (startTimer box) function to a button?
Source code:
import Graphics.UI.Gtk
import SetTest
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import Control.Monad.Trans(liftIO)
import Debug.Trace
import Control.Concurrent
import Control.Concurrent.MVar
import System.Exit
main :: IO ()
main = do
initGUI
window <- windowNew
set window [windowTitle := "Minesweeper",
windowDefaultWidth := 450, windowDefaultHeight := 200]
box <- vBoxNew False 0
containerAdd window box
button1 <- buttonNewWithLabel "New game"
boxPackStart box button1 PackGrow 0
widgetShowAll window
table <- tableNew 5 5 True
--onClicked button1 (do (startTimer box) (startNewGame table window))
--onClicked button1 (startTimer box)
onClicked button1 (startNewGame table window)
startTimer box
containerAdd window table
startNewGame table window
boxPackStart box table PackNatural 0
widgetShowAll window
onDestroy window mainQuit
mainGUI
startTimer :: BoxClass self => self -> IO ThreadId
startTimer box = do
timeLabel <- labelNew Nothing
boxPackStart box timeLabel PackNatural 0
forkIO $ do
let
printTime t = do{
threadDelay 1000000;
postGUIAsync $ labelSetText timeLabel (show t);
printTime (t+1)}
printTime 0
startNewGame:: (WidgetClass self, TableClass self1) => self1 -> self -> IO ()
startNewGame table window = let board = (SetTest.initialize 5 (5,5) (1,1)) :: MyBoard
in checkStatusGame table board window
:: (WidgetClass self, TableClass self1) =>
self1 -> MyBoard -> self -> IO ()
checkStatusGame table board window
| won board = do
cleanAndGenerateTable board table window True
(dialogMessage "hurray hurray hurray" "Gratz, you won!!!")
| lost board = do
(dialogMessage "Baby rage window" "Soz, you lost...")
cleanAndGenerateTable board table window True
| otherwise = (cleanAndGenerateTable board table window False)
cleanAndGenerateTable :: (WidgetClass self, TableClass self1) =>
MyBoard -> self1 -> self -> Bool -> IO ()
cleanAndGenerateTable board table window finished = do
let fieldList = [(x,y) | x <- [0 .. (height board)] , y <- [0 .. (width board)] ]
children <- containerGetChildren table
mapM_ (\child -> containerRemove table child >> widgetDestroy child) children
if finished
then mapM_(generateTableFinished board table window) fieldList
else mapM_ (generateTable board table window) fieldList
widgetShowAll window
generateTable board table window (x,y)
| Set.member (x,y) (flaggedCells board) = createButton "flag.jpg" (x,y) table board window
| Map.member (x,y) (clickedCells board) = createClickedButton (show (Map.findWithDefault (-1) (x,y) (clickedCells board))) (x,y) table
| otherwise = createButton "masked.png" (x,y) table board window
generateTableFinished board table window (x,y)
| Set.member (x,y) (bombs board) = createButtonNoAction "bomb.jpg" (x,y) table board window
| Map.member (x,y) (clickedCells board) = createClickedButton (show (Map.findWithDefault (-1) (x,y) (clickedCells board))) (x,y) table
| otherwise = createClickedButton (show (Map.findWithDefault (-1) (x,y) (maskedCells board))) (x,y) table
createButtonNoAction pth (x,y) table board window = do
button <- buttonNew
box <- hBoxNew False 0
image <- imageNewFromFile pth
boxPackStart box image PackRepel 0
containerAdd button box
tableAttachDefaults table button x (x+1) y (y+1)
createClickedButton lbl (x,y) table = do
button <- buttonNew
box <- hBoxNew False 0
label <- labelNew (Just lbl)
boxPackStart box label PackRepel 0
containerAdd button box
tableAttachDefaults table button x (x+1) y (y+1)
createButton pth (x,y) table board window = do
button <- buttonNew
box <- hBoxNew False 0
image <- imageNewFromFile pth
boxPackStart box image PackRepel 0
containerAdd button box
tableAttachDefaults table button x (x+1) y (y+1)
on button buttonReleaseEvent $ do
click <- eventButton
liftIO $ case click of { LeftButton -> (checkStatusGame table (SetTest.click (x,y) board) window); RightButton -> (checkStatusGame table (SetTest.flag (x,y) board) window) }
return False
return ()
dialogMessage title msg = do dialog <- messageDialogNew Nothing [] MessageOther ButtonsOk msg
set dialog [windowTitle := title]
widgetShowAll dialog
dialogRun dialog
widgetDestroy dialog
If you want to communicate with your timer thread, you will need to hand it a communication channel. An MVar seems appropriate here.
startTimer :: BoxClass self => self -> MVar Integer -> IO ThreadId
startTimer box timer = do
timeLabel <- labelNew Nothing
boxPackStart box timeLabel PackNatural 0
forkIO . forever $ do
threadDelay 1000000
t <- takeMVar timer
putMVar timer (t+1)
postGUIAsync $ labelSetText timeLabel (show t)
At the top of main, you can now create a fresh MVar with timer <- newMVar 0, and pass this to startTimer. In your button callback, you can takeMVar timer >> putMVar timer 0 to reset the timer.