10.3  The Canvas

by Martin Grimme (gDesklets SVG logo is a courtesy of Johannes Rebhan)

10.3.1  Introduction

The <canvas> element provides a canvas for drawing scalable vector graphics onto. These drawings can even be animated.

This tutorial shows you how you can make full use of the <canvas> element and SVG scripting.

10.3.2  SVG

SVG stands for Scalable Vector Graphics. It is a standardized graphics format based on XML. gDesklets can read SVG and animate images by modifying properties in the XML tree of the SVG.

Good tools for creating SVG drawings on Unix systems are Sodipodi and its amazing fork Inkscape. Of course, since SVG is clean XML, you can also create drawings with a simple text editor.

10.3.3  Loading Drawings

The easiest to way to render drawings is loading them from file. The <canvas> can read SVG drawings, so you can make an image with your favorite SVG drawing tool and load it into your applet.

The SVG file can be loaded into the canvas with the uri property. Use the width and height properties to scale the image.

<?xml version="1.0" encoding="UTF-8"?>

<display>

  <canvas id="mycanvas" uri="gdesklets.svg" width="200" height="200"/>

</display>
  

10.3.4  Manipulating the DOM

A SVG image is composed of elements. These elements in turn could even be decomposed into more elements, perhaps. Here you can see a halfway decomposed gDesklets logo:

Every piece or composition of pieces can have an ID by which it can be identified and addressed. By manipulating the properties of such an element, scripts can animate the image.

The composition tree of SVG elements is represented by a Document Object Model, a DOM. The DOM is a tree of nodes where each node represents a node in the SVG tree of elements. Element properties can be directly manipulated on the DOM.

The <canvas> display element provides a mini-DOM for this purpose through its dom property.

You can use your SVG editor for finding the ID of the elements which you want to manipulate. If you have the ID, you can use the get() method to retrieve the corresponding node and directly modify its properties.

After having finished manipulating the DOM, you can have the image redraw itself by calling the update() method on the DOM.

<script>

  dom = Dsp.mycanvas.dom
  node = dom.get("rect588")
  node["style"] = "fill:yellow"
  dom.update()

</script>
  

Of course, we can use this in action handlers, too (remember to call the update() method):

<?xml version="1.0" encoding="UTF-8"?>

<display>

  <canvas uri="gdesklets.svg" width="200" height="200"
    on-enter="self.dom.get('rect588')['style'] = 'fill:yellow'; self.dom.update()"
    on-leave="self.dom.get('rect588')['style'] = 'fill:blue'; self.dom.update()"/>

</display>
  

10.3.5  Generating Drawings on the Fly

Instead of loading SVG files, you can also feed the canvas directly with a string of SVG data. The graphics property accepts SVG code.

SVG usually requires the width and height properties specified in the <svg> root tag. gDesklets, however, automatically sets these to 100 x 100 if you omit them.

<?xml version="1.0" encoding="UTF-8"?>

<display window-flags="above">

  <canvas id="mycanvas" width="200" height="200"/>

  <script><![CDATA[

    svg = """
      <svg>

        <rect x="0" y="0" width="100" height="100"
              style="fill:white; stroke:black; fill-opacity:50%"/>

        <circle cx="50" cy="50" r="20" style="stroke:black; fill:yellow"/>

      </svg>
    """

    Dsp.mycanvas.graphics = svg
  ]]></script>

</display>
  

10.3.6  Animate It

Of course, the DOM is also available for images set by the graphics property. For the end of this tutorial, we are going to let the ball on the image bounce.

First of all, the ball needs an ID, so that we can easily access it through the DOM:

<circle id="ball" cx="50" cy="50" r="20" style="stroke:black; fill:yellow"/>
  

The animation can be done in a timer. We simply change the x and y properties regularly to make it move. Special treatment is needed for edges since the ball has to bounce back there. This can be achieved by just inverting the current movement direction.

<?xml version="1.0" encoding="UTF-8"?>

<display window-flags="above">

  <canvas id="mycanvas" width="200" height="200"/>

  <script><![CDATA[

    svg = """
      <svg>

        <rect x="0" y="0" width="100" height="100"
              style="fill:white; stroke:black; fill-opacity:50%"/>

        <circle id="ball" cx="50" cy="50" r="20" style="stroke:black; fill:yellow"/>

      </svg>
    """

    Dsp.mycanvas.graphics = svg

    dx = 3
    dy = 2

    def bounce():
        global dx, dy

        ball = Dsp.mycanvas.dom.get("ball")
        x = int(ball["cx"])
        y = int(ball["cy"])

        # bounce back at the edges
        if (x <= 20 or x >= 80): dx = -dx
        if (y <= 20 or y >= 80): dy = -dy

        # move the ball
        x += dx
        y += dy
        ball["cx"] = str(x)
        ball["cy"] = str(y)

        # redraw image
        Dsp.mycanvas.dom.update()
        
        # keep the animation running
        return True

   
    # animate every 100 milliseconds    
    add_timer(100, bounce)

  ]]></script>

</display>
  

Please note that all properties of SVG elements are strings and have to be strings. That's why we need the conversions in the timer.