質問

I'm not so experienced in Haskell and I've just started using Gtk2Hs so this might be a silly question.

I have the following Line type defined:

type Coord = (Int,Int)
type Line = (Coord,Coord)

And I have a function which draws a list of Lines on a DrawingArea. The problem is that this function draws all the Lines at the same time but I would like to draw them one at a time with a little delay between two Lines.

render :: [Line] -> IO ()
render lines =
  do initGUI
     win <- windowNew
     windowSetTitle win "Animation"
     win `onDestroy` mainQuit

     can <- drawingAreaNew
     can `onSizeRequest` return (Requisition 400 400)
     can `onExpose` drawCanvas lines can

     but <- buttonNewWithLabel "Quit"
     but `onClicked` mainQuit
     hbox <- hBoxNew False 0
     boxPackStart hbox but PackRepel 150

     vbox <- vBoxNew False 5
     containerAdd vbox can
     containerAdd vbox hbox
     containerAdd win vbox

     widgetShowAll win
     mainGUI

This function gets called when the DrawingArea is exposed:

drawCanvas :: [Line] -> DrawingArea -> event -> IO Bool
drawCanvas lines can _evt =
  do dw <- widgetGetDrawWindow can
     drawWindowClear dw
     gc <- gcNew dw
     mapM_ (\(a,b) -> drawLine dw gc a b) lines
     return True

I have considered using a StateT to keep track of which Lines have yet to be drawn but I don't know how to implement the animation. Even if a call widgetShowAll every time after altering the state, the window doesn't get shown until mainGUI is called.

Is it possible to make a new thread that continuously updates the state while drawCanvas somehow takes care of the drawing? If so, could somebody please show me an example of such behavior? Or is there a better approach perhaps?

役に立ちましたか?

解決

Gtk2Hs allows to set up "timers" that call functions at regular intervals. So I would do the following:

  • Because most of Gtk2Hs happens in the IO monad, use an IORef or an MVar to store the state of the animation and change it anywhere.
  • Add a call to timeoutAdd before mainGUI, like this: timeoutAdd (update can lines) 100 to set up a timer that will run every 100 milliseconds and call a new function "update" with the DrawingArea and the IORef/MVar with the animation state as parameters.
  • In the function "update" change the animation state and call widgetQueueDraw can so that the drawing area is re-exposed. That will call "drawCanvas" automatically because it is connected to the expose event. "update" has to return an IO Bool. Returning False will stop the timer.
  • To update the animation state, I would use a tuple. The first element would store the line to be drawn, and the second element would store the list of the other lines.

I find it meaningful to update the animation's state and the drawing in separate functions.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top