What is the right way to render Cairo updates in Gtk2hs?
Asked Answered
R

1

10

I'm writing a Haskell program with click-and-drag functionality, so with each mouse movement event an update gets painted to the window. At the moment I'm using

renderWithDrawable myCanvas update

However this is flickering a lot. My understanding is that I need to create a separate drawable (a "surface"?), render to that, and then blit it onto the screen window in a single operation. However I'm confused as to the right way to do this.

I've found drawWindowBeginPaintRegion, which talks about eliminating flicker. However it is removed in Gtk3 according to the Haddock docs. So I'm not sure if I should use this, as it seems to be deprecated.

I've also found renderWithSimilarSurface in Cairo, which seems to do something similar.

I'm also not sure how these functions relate to renderWithDrawable: do I have to use them inside that function, or what?

What is the right way to do this?

Edit

This seems to be a known thing in Cairo. I'm trying to figure out how to handle this in Haskell.

Rego answered 12/11, 2015 at 7:42 Comment(3)
Can you post a minimal self-contained example?Star
@Cactus, not easily. A demo of the flicker problem would need some kind of animation or mouse event handler to do repeated redrawing, so its not going to be small. Also the degree of flicker seems to vary with the platform. Instead I'll update the post with pointers to discussions of the issue in other languages. Drawing to a PixMap and then blitting this to the screen seems to be a standard thing in other languages, so I was hoping there was a canonical way to do it in Haskell.Rego
You might want to look at the clip function. It will allow you to only redraw what you need (in your case, the initial rectangle (old position) of your dragged object and its new rectangle (new position)).Howerton
F
4

The right way to do this is to make sure all your drawing comes from within expose events, and operates on the draw window provided by the event. You can mark a region as "dirty" and trigger a synthetic expose event using drawWindowInvalidateRect, drawWindowInvalidateRegion, or widgetQueueDraw.

A quick worked example of setting up the drawing pipeline follows. It is excerpted from a custom Viewport type, which does Google-maps style panning with smooth motions on drag-and-release operations, that I built for a side-project some time ago. To support that, it has to redraw on mouse motion events, so it addresses a similar use case to your described problem. I've elided irrelevant stuff with ... to highlight the important bits. I've uploaded the complete project to github just now, so you can browse the repo to see the full details of Viewport. (It's been years though, so there's probably a fair bit of bitrot -- don't expect the project to just build and run with modern GHCs/packages.)

viewportNew :: Viewport -> IO DrawingArea
viewportNew v = do
    da <- drawingAreaNew
    -- ...
    on da exposeEvent $ exposeViewport posRef (draw v)
    -- ...

exposeViewport :: IORef Position -> RegionRenderer -> EventM EExpose Bool
exposeViewport posRef draw = do
    dw      <- eventWindow
    region  <- eventRegion >>= liftIO . regionGetRectangles
    -- ...
    liftIO . renderWithDrawable dw $ do
        -- Cairo () action goes here
        -- can reference region to decide which things to draw
        draw region
    return True -- see documentation of exposeEvent for what this means

This template should take advantage of gtk's built-in double-buffering and work with both the gtk and gtk3 packages.

Frog answered 14/11, 2015 at 21:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.