diagrams / diagrams-gtk

Optional add-on to diagrams-cairo allowing diagrams to be rendered directly to GTK windows.
Other
4 stars 5 forks source link

diagram upside down when rescaling when window resized #7

Open s5k6 opened 8 years ago

s5k6 commented 8 years ago
Sorry, I guess this example is pretty minimal, but it works..

If you replace the line marked FIRST with the line marked SECOND, then
the diagram flips upside down, and I have no idea why.

Any help would be helpful...
Stefan

> import Control.Monad.Trans ( liftIO )
> import Graphics.UI.Gtk
> import Diagrams.Backend.Cairo
> import Diagrams.Backend.Gtk
> import Diagrams.Prelude

> import           Diagrams.Prelude                hiding (height, width)
> import           Diagrams.Backend.Cairo.Internal
> import           Graphics.UI.Gtk
> import qualified Graphics.Rendering.Cairo        as CG

I was wondering why `defaultRender` requires a `DrawingArea` as
argument [1], but only gets a window from it to draw on [2].  In
contrast, `renderToGtk` is happy with a something of type
`DrawableClass dc => dc` [1].

Why is that?

Why would I want it differently?  I want to have a window that
rescales the diagram contents when it gets resized.  I'm a GUI-noob,
so I took GTK integration from [3]:

> main :: IO ()
> main
>   = do initGUI
>        canvas <- drawingAreaNew
>        canvas `on` sizeRequest $ return (Requisition 200 200)
>        canvas `on` exposeEvent $ renderFigure myFigure   -- below
>        window <- windowNew
>        Graphics.UI.Gtk.set window [ containerChild := canvas]
>        onDestroy window mainQuit
>        widgetShowAll window
>        mainGUI

> renderFigure :: Diagram B -> EventM EExpose Bool
> renderFigure dia = do
>    win <- eventWindow

>    liftIO . renderToGtk win . toGtkCoords $ scale 15 dia     -- FIRST

Above line does not rescale the diagram when the window is resized, so
I wanted to have something like the following:

  >    liftIO . defaultRender' win . toGtkCoords $ dia   -- SECOND

>    return True

Having a look at the sources [2], I decided that I could build such a
`defaultRender'` on my own, basically simplifying `defaultRender` from
[2]:

> defaultRender' :: (Monoid' m, DrawableClass dc)
>                => dc -> QDiagram Cairo V2 Double m -> IO ()
> defaultRender' drawable diagram
>   = renderDoubleBuffered drawable opts diagram
>   where
>     opts w h
>       = CairoOptions
>         { _cairoFileName     = ""
>         , _cairoSizeSpec     = dims (V2 (fromIntegral w) (fromIntegral h))
>         , _cairoOutputType   = RenderOnly
>         , _cairoBypassAdjust = False
>         }

Now if you exchange the two lines marked FIRST and SECOND above, then
this diagram actually scales nicely.  But why on earth is it upside
down?  In case you do not see this: On my machine it is mirrored on
the horizontal line!

To make the example work, I also copied the following 4 functions
from [2]...

> renderDoubleBuffered ::
>   (Monoid' m, DrawableClass dc) =>
>   dc -- ^ drawable to render onto
>   -> (Int -> Int -> Options Cairo V2 Double) -- ^ options, depending on drawable width and height
>   -> QDiagram Cairo V2 Double m -- ^ Diagram
>   -> IO ()
> renderDoubleBuffered drawable renderOpts diagram = do
>   (w,h) <- drawableGetSize drawable
>   let opts = renderOpts w h
>       renderAction = delete w h >> snd (renderDia Cairo opts diagram)
>   renderWithDrawable drawable (doubleBuffer renderAction)
> 
> delete :: Int -> Int -> CG.Render ()
> delete w h = do
>   CG.setSourceRGB 1 1 1
>   CG.rectangle 0 0 (fromIntegral w) (fromIntegral h)
>   CG.fill

> doubleBuffer :: CG.Render () -> CG.Render ()
> doubleBuffer renderAction = do
>   CG.pushGroup
>   renderAction
>   CG.popGroupToSource
>   CG.paint

...and took an example from diagram's tutorial [4].

> node :: Int -> Diagram B
> node n = text (show n) # fontSizeL 1 # fc white <> circle 1 # fc green

> myFigure :: Diagram B
> myFigure = atPoints (trailVertices $ regPoly 6 5) $ map node [1..]

____________________
[1] https://s3.amazonaws.com/haddock.stackage.org/lts-6.1/diagrams-gtk-1.3.0.1/Diagrams-Backend-Gtk.html
[2] https://s3.amazonaws.com/haddock.stackage.org/lts-6.1/diagrams-gtk-1.3.0.1/src/Diagrams-Backend-Gtk.html#defaultRender
[3] http://stackoverflow.com/questions/11885373/how-do-i-use-the-diagrams-library-with-gtk-drawables
[4] http://projects.haskell.org/diagrams/doc/quickstart.html
byorgey commented 8 years ago

Cairo/gtk and diagrams have different ideas about the y-axis: in diagrams, the positive y-axis points up (just like in math); in cairo/gtk, it points down (so (0,0) is at the top left of the screen). When a diagram is "adjusted" it is rescaled and centered but also flipped upside down to account for this.

I think the problem is that your code is flipping the diagram twice: toGtkCoords calls adjustDia with the last component of the options record (_cairoBypassAdjust) set to False: this option controls whether the adjustment step is skipped. So False means it is not skipped, and toGtkCoords does stuff like center the diagram and flip it upside down. But then your defaultRender' function also has that option set to False and so the adjustment happens again: the diagram is already properly scaled and centered so nothing happens there, but it does get flipped again. Notice how in the original code, liftIO . renderToGtk win . toGtkCoords, if you look at the implementation of renderToGtk it has _cairoBypassAdjust set to True, so the adjustment step is bypassed, avoiding a second flip.

s5k6 commented 8 years ago

Thanks for that illuminating answer.

So the original defaultRender would do it wrong? I cannot test because I do not have a DrawingArea at hand, and don't know where to get one from.

I've tried _cairoBypassAdjust = True, but then the diagram does not resize automatically when the window is resized. So what's the correct way to do this? Do I have to reflect the diagram myself, as follows?

...
> defaultRender' drawable diagram
>   = renderDoubleBuffered drawable opts $ scaleY (-1) diagram
>   where
...
>         , _cairoBypassAdjust = False

And about the root of my problem: I have a DrawWindow from eventWindow and need to run defaultRender on it. How do I get the required DrawingArea?

byorgey commented 8 years ago

Ah, I see now, toGtkCoords does not resize the diagram, it only centers and flips it. But if you have _cairoBypassAdjust = False in defaultRender' then it will also center and flip the diagram, so I think the solution is to just omit the call to toGtkCoords --- you don't need it at all.

Unfortunately I do not understand the GTK backend well enough to answer your questions about DrawingArea.