Canvas widget#
The canvas widget allows creating custom widgets with user-defined drawings and touch reactions.
It has two special properties, touch
and draw
, that work like the script
property : they are used to defined scripts that will be executed when specific events occurs.
This widget requires a good understanding of the javascript Canvas API and of the javascript programming language in general.
Canvas properties#
valueLength
#
This property must be set to the number of values held by the widget : 1 for a simple slider, 2 for an XY pad, etc. Attempts to set the widget's value with a different number of values will be ignored.
set("this", 1) // works if valueLength == 1
set("this", [1, 2]) // works if valueLength == 2
autoClear
#
This is a convenience option that clears the canvas context and calls beginPath
before each drawing. When this property is set to false
, the canvas must be cleared manually.
continuous
#
If this property is enabled, the widget will be redrawn at a fixed rate even if not interacted with (see onDraw
).
onTouch
#
This script is executed when the widget is touched, when it is released, and during the movement of the pointer while the widget is touched.
This script has access to the same variables and functions as the script
property (except the event-specific ones), plus the following:
value
: widget valuewidth
: canvas width in pixelsheight
: canvas height in pixelsevent
: object containing the following:type
:"start"
,"move"
or"stop"
target
: id of the widget under the pointer,"this"
if it's the canvas widget itself,null
if no widget is under the pointertargetName
: name attribute of the html element under the pointertargetTagName
: tag name of the html element under the pointeroffsetX
,offsetY
: touch coordinates in pixels, relative to the html element under the pointermovementX
,movementY
: movement of the pointer in pixels since the last eventpointerId
: unique identifier used to differentiate fingers in multitouch situationaltKey
,ctrlKey
,shiftKey
: keyboard modifier statesforce
: amount of pressure applied to the touch surface between0
and1
(see Touch.force). Equals0
if the API is not supported or if no pressure information is found.radiusX
,radiusY
,rotationAngle
: see Touch.rotationAngle- (iOS only)
altitudeAngle
,azimuthAngle
: see Touch.altitudeAngle - (iOS only)
touchType
: "direct" or "stylus"
Extra html elements
Elements added using the html
property can be touched as well, event.targetName
can be used to determine which element is touched.
onDraw
#
This script is executed when the widget should be redrawn, which is when it's touched and when it receives a value.
This script has access to the same variables and functions as the script
property (except the event-specific ones), plus the following:
value
: widget valuewidth
: canvas width in pixelsheight
: canvas height in pixelsctx
: canvas rendering context of the widgetcssVars
: object containing the computed value of some of the widget's style properties such ascolorWidget
,alphaFill
,padding
, etc
onResize
#
This script is executed when the widget has resized, before it is redrawn.
This script has access to the same variables and functions as the script
property (except the event-specific ones), plus the following:
value
: widget valuewidth
: canvas width in pixelsheight
: canvas height in pixelscssVars
: object containing the computed value of some of the widget's style properties such ascolorWidget
,alphaFill
,padding
, etc
Example: XY pad#
Let's create a simple xy pad, with a value made of two numbers between 0 and 1. We set valueLength
to 2
to make sure the widget only accepts incoming messages with two values (x and y).
First, we use the onTouch
property to store the touch coordinates in the locals
object. We also call set()
to store these in the widget's value (this way, the widget can send messages and sync with other widgets).
// onTouch
// store normalized coordinates
if (event.type == "start") {
locals.x = event.offsetX / width
locals.y = event.offsetY / height
} else {
// when the pointer is moving, increment coordinates
// because offsetX and offsetY may not be relevant
// if the pointer hovers a different widgets
locals.x += event.movementX / width
locals.y += event.movementY / height
}
// update widget value and send
set("this", [locals.x, locals.y])
Then, we use the onDraw
property to draw a circle at the touch coordinates.
// onDraw
// draw circle at touch coordinates
ctx.arc(value[0] * width, value[1] * height, 6, 0, Math.PI * 2)
// use colorFill property as stroke color
ctx.strokeStyle = cssVars.colorFill
// draw stroke
ctx.stroke()
Finally, we use the onValue
property to apply limits to the values.
// onValue
// apply limits
var x = Math.max(0, Math.min(1, value[0])),
y = Math.max(0, Math.min(1, value[1]))
// re-update widget value without retriggering script or sending message
set("this", [x, y], {sync: false, send: false})
// the widget will automatically send its value if
// this script was triggered by a user interaction
// no need to call send() here but we could, for example
// if we want to split the value in to multiple messages
Example: multi slider#
Let's build a row of 20-sliders with a single widget. We first set valueLength
to... 20 !
// onTouch
// store normalized coordinates
if (event.type == "start") {
locals.x = event.offsetX / width
locals.y = event.offsetY / height
} else {
// when the pointer is moving, increment coordinates
// because offsetX and offsetY may not be relevant
// if the pointer hovers a different widgets
locals.x += event.movementX / width
locals.y += event.movementY / height
}
// which slider are we touching ?
var n = parseInt(locals.x * value.length)
n = Math.max(0, Math.min(n, value.length-1))
// update value at slider's index
// 1 - locals.y because y axis is from top to bottom in js canvas
value[n] = 1 - locals.y
// update widget value and send
set("this", value)
// onDraw
ctx.fillStyle = cssVars.colorFill
ctx.globalAlpha = cssVars.alphaFillOn
var sliderWidth = width / value.length - 1
for (var i in value){
ctx.beginPath()
ctx.rect(i * width / value.length, height, sliderWidth, - value[i] * height)
ctx.fill()
}
// onValue
// apply limits
for (var i in value) {
value[i] = Math.max(0, Math.min(1, value[i]))
}
// re-update widget value without retriggering script or sending message
set("this", value, {sync: false, send: false})