Question

I'm writing a program using OpenGl and Haskell that should draw a rectangle when and where the mouse is clicked. However, the program closes as soon as I click and before the rectangle is drawn.

import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
import Graphics.UI.GLUT.Callbacks.Window

main = do
  (progname, _) <- getArgsAndInitialize
  createWindow progname
  keyboardMouseCallback $= Just myKeyboardMouseCallback
  displayCallback $= display
  mainLoop

myKeyboardMouseCallback key keyState modifiers (Position x y) =
  case (key, keyState) of
    (MouseButton LeftButton, Down) -> do
      clear[ColorBuffer]
      let x = x :: GLfloat
      let y = y :: GLfloat
      renderPrimitive Quads $ do
        color $ (Color3 (1.0::GLfloat) 0 0)
        vertex $ (Vertex3 (x::GLfloat) y 0)
        vertex $ (Vertex3 (x::GLfloat) (y+0.2) 0)
        vertex $ (Vertex3 ((x+0.2)::GLfloat) (y+0.2) 0)
        vertex $ (Vertex3 ((x+0.2)::GLfloat) y 0)
      flush
    _ -> return ()

display = do
  clear [ColorBuffer]
  renderPrimitive Lines $ do
  flush

Is there something causing the program to terminate early in one of the methods, or is this just the the computers way of telling me I can't do this?

Was it helpful?

Solution

You can't do what you are trying to do. In an OpenGL program, you are only allowed to issue draw commands in the OpenGL context. This context is always bound to a specific thread, and is only active in the body of the displayCallback in GLUT, because the other callbacks might potentially run from different threads.

However, you might say: On many/most platforms, a separate thread isn't used for input in GLUT, which means that you theoretically could issue drawing commands there. There are, however, many other things that play a role in where and when you can issue drawing commands; for example when the environment requires you to use double-buffered output where the buffers have to be flushed in very specific ways (e.g. when using EGL or GLX for X11).

In short: you should not issue drawing commands outside of the displayCallback. The whole reason for its existence is so that you can let GLUT handle the platform-specific stuff related to native frame buffer management, and it expects you to keep your code in the right places for it to work.

What you want to do instead is to create a mutable variable (Hey, you're using OpenGL; mutable state shouldn't be a worry to you) that indicates whether to draw the rectangle and where. Something like (Using Data.IORef):

main = do
  -- ...

  -- Create a mutable variable that stores a Bool and a pair of floats
  mouseStateRef <- newIORef (False, (0, 0))

  -- Pass a reference to the mutable variable to the callbacks
  keyboardMouseCallback $= Just (myKeyboardMouseCallback mouseStateRef)
  displayCallback $= (display mouseStateRef)

myKeyboardMouseCallback mouseStateRef key keyState modifiers (Position x y) =
  case key of
    MouseButton LeftButton -> do
      -- Store the current mouse pressed state and coords in the reference
      writeIORef mouseStateRef (keyState == Pressed, (x, y))
    _ -> return ()

display mouseStateRef = do
  clear [ColorBuffer]

  -- Read the state stored in the mutable reference
  (pressed, (x, y)) <- readIORef mouseStateRef

  -- Draw the quad if the mouse button is pressed
  when pressed . renderPrimitive Quads $ do
    color $ (Color3 (1.0::GLfloat) 0 0)
    vertex $ (Vertex3 (x::GLfloat) y 0)
    vertex $ (Vertex3 (x::GLfloat) (y+0.2) 0)
    vertex $ (Vertex3 ((x+0.2)::GLfloat) (y+0.2) 0)
    vertex $ (Vertex3 ((x+0.2)::GLfloat) y 0)
  flush
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top