diagrams-hsqml-0.2.0.0: HsQML (Qt5) backend for Diagrams

Safe HaskellNone
LanguageHaskell2010

Diagrams.Backend.HsQML.Tutorial

Contents

Synopsis

DiagramCanvas QML script

To use this backend you first need a custom Canvas object to handle signals coming from Haskell. Example script to place in your QML search path (can be next to your main QML document):

 -- DiagramCanvas.qml
import QtQuick 2.4

Canvas {
  id: canvas;
  property variant diagram;
  renderStrategy: Canvas.Threaded;
  renderTarget: Canvas.FramebufferObject;

  onDiagramChanged: {
    var context = canvas.getContext('2d');
    if(canvas.diagram && context) {
    console.log("reconnect");
    canvas.diagram.save.connect(function() {canvas.context.save()})
    canvas.diagram.restore.connect(function () {canvas.context.restore()});
    canvas.diagram.text.connect(function (text,x,y) {canvas.context.strokeText(text,x,y)});
    canvas.diagram.beginPath.connect(function () {canvas.context.beginPath()});
    canvas.diagram.closePath.connect(function () {canvas.context.closePath()});
    canvas.diagram.stroke.connect(function () {canvas.context.stroke()});
    canvas.diagram.fill.connect(function() {canvas.context.fill()});
    canvas.diagram.moveTo.connect(function (x,y) {canvas.context.moveTo(x,y)});
    canvas.diagram.lineTo.connect(function (x,y) {canvas.context.lineTo(x,y)});
    canvas.diagram.bezierCurveTo.connect(function(cp1x,cp1y,cp2x,cp2y,x,y) 
                                           {canvas.context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)});
    canvas.diagram.connectLinearGradient.connect(connectLG);
    canvas.diagram.connectRadialGradient.connect(connectRG);

    canvas.diagram.setStrokeColour.connect(function(r,g,b,a) 
                                            {canvas.context.strokeStyle = Qt.rgba(r,g,b,a).toString();});
    canvas.diagram.setFillColour.connect(function(r,g,b,a) 
                                            {canvas.context.fillStyle = Qt.rgba(r,g,b,a)});
    canvas.diagram.setFont.connect(function(f) {canvas.context.font = f});
    canvas.diagram.setLineWidth.connect(function(w) {canvas.context.lineWidth = w});
    canvas.diagram.oddEvenFill.connect(function() {canvas.context.fillRule = Qt.OddEvenFill});
    canvas.diagram.windingFill.connect(function() {canvas.context.fillRule = Qt.WindingFill});
    } else {
     console.log("warning! no diagram or context object to connect");
    }
  }

  onPaint: {
    if(canvas.diagram) {
      canvas.diagram.reload();
    }
  }

  function connectLG(gradient, x0, y0, x1, y1) {
    var grad = canvas.context.createLinearGradient(x0, y0, x1, y1);
    gradient.addStop.connect( function(r,g,b,a, off) {grad.addColorStop(off, Qt.rgba(r,g,b,a))});
    canvas.diagram.setLineGradient.connect(function() {canvas.context.strokeStyle = grad;});
    canvas.diagram.setFillGradient.connect(function() {canvas.context.fillStyle = grad;});

  }

  function connectRG(gradient, x0, y0, r0, x1, y1, r1) {
    var grad = canvas.context.createRadialGradient(x0, y0, r0, x1, y1, r1);
    gradient.addStop.connect(function(r,g,b,a, off) {grad.addColorStop(off, Qt.rgba(r,g,b,a))});
    canvas.diagram.setLineGradient.connect(function() {canvas.context.strokeStyle = grad;});
    canvas.diagram.setFillGradient.connect(function() {canvas.context.fillStyle = grad;});
  }
}

Context object

You can make an ObjRef to a DiagramObj available to the QML script by placing it in a property of your context object:

 -- Main.hs
 module Main where
 import Control.Applicative
 import Control.Concurrent
 import Control.Concurrent.MVar
 import Data.Typeable
 import Diagrams.Prelude
 import Diagrams.Backend.HsQML
 import Graphics.QML

data Repaint deriving Typeable

repaint :: Proxy Repaint
repaint = Proxy

instance SignalKeyClass Repaint where
  type SignalParams Repaint = IO ()

data MainObj = MainObj 
    { diagramObj :: MVar (ObjRef (DiagramObj ()))
    }
  deriving Typeable

instance DefaultClass MainObj where
  classMembers = 
    [ defPropertySigRO' "mainDiagram" diagramChanged $ \this -> 
       case fromObjRef this of
          MainObj mvar -> readMVar mvar
    , defPropertyConst' "self" return
    , defSignal "repaint" repaint
    ]

Main window

Then, place a DiagramCanvas somewhere in your application, and connect it to the controller object:

 -- main.qml
import QtQuick 2.0
import QtQuick.Controls 1.3
import QtQuick.Window 2.2

ApplicationWindow {
  title: "test";
  width: 800;
  height: 800;
  visible: true;

  toolBar: ToolBar {
    ToolButton {
      onClicked: canvas.repaint();
    }
  }

  DiagramCanvas {
    id: canvas;
    anchors.fill: parent;
  
    function repaint() {
       canvas.diagram = self.mainDiagram;
       canvas.paint(canvas.canvasWindow);
    }

    Connections {
      target: self;
      onRepaint: canvas.repaint();
    }
  }

}

Rendering diagrams

The renderHsQML function creates an ObjRef to the controller object.

 -- Main.hs
diagram :: Diagram B R2
diagram = circle 1

main :: IO ()
main = do
  diag <- renderHsQML (mkSizeSpec (Just 800) (Just 800)) diagram
  mainObj <- MainObj <$> newMVar diag
  ctx <- newObjectDC mainObj
  runEngineLoop defaultEngineConfig
     { initialDocument = fileDocument "qml/main.qml"
     , contextObject = Just $ anyObjRef ctx
     }