画布 2D 网页应用/可变换对象
本章介绍“可变换对象”。这里,“可变换对象”表示可以通过两指手势移动(“平移”)、旋转和缩放(在两个方向上均匀)的图像。可变换对象的使用方式与 可拖动对象 非常相似;但是,它们的处理函数需要另一幅图像来处理两指触碰的情况,并且需要另一个布尔值来确定对象是否可以用一根手指或鼠标(而不是仅用两根手指)拖动。
与可拖动对象一样,可以同时变换任意数量的对象(只要触摸设备支持所需的触摸事件数量)。此外,应用程序程序员不必担心处理多个同时发生的触摸事件:process
函数只接收具有单个坐标对的事件,并一次只处理一个对象。
本章的示例(可 在线获取;也可 下载版本)展示了两个对象:这两个对象都可以用一根手指(或鼠标)拖动,并用两根手指变换,但其中一个不能缩放,另一个不能旋转。以下部分将讨论如何使用可变换对象以及如何使用它们。有关其他部分,请参见 可拖动对象 一章。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src="cui2d.js"></script>
<script>
function init() {
// get images
imageNormalAlien.src = "alien_sleepy.png";
imageNormalAlien.onload = cuiRepaint;
imageFocusedAlien.src = "alien_wow.png";
imageFocusedAlien.onload = cuiRepaint;
imageGrabbedOnceAlien.src = "alien_lipbite.png";
imageGrabbedOnceAlien.onload = cuiRepaint;
imageGrabbedTwiceAlien.src = "alien_smiley.png";
imageGrabbedTwiceAlien.onload = cuiRepaint;
// set defaults for all pages
cuiBackgroundFillStyle = "#00A000";
// initialize cui2d and start with myPage
cuiInit(myPage);
}
// create images for the smiley
var imageNormalAlien = new Image();
var imageFocusedAlien = new Image();
var imageGrabbedOnceAlien = new Image();
var imageGrabbedTwiceAlien = new Image();
// create draggable objects
var transformable0 = new cuiTransformable();
var transformable1 = new cuiTransformable();
// create a page
var myPage = new cuiPage(400, 300, myPageProcess);
// a function to repaint the canvas and return false (if null == event)
// or to process user events (if null != event) and return true
// if the event has been processed
function myPageProcess(event) {
// draw and react to transformables
if (transformable0.process(event, 50, 100, 200, 200, null,
imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien,
cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
transformable0.scale = 1.0; // always reset scale (i.e. don't scale)
return true;
}
if (transformable1.process(event, 250, 100, 200, 200, null,
imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien,
cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
transformable1.rotation = 0.0; // always reset rotation (i.e. dont' rotate)
return true;
}
// repaint this page?
if (null == event) {
// background
cuiContext.fillStyle = "#F0F0F0";
cuiContext.fillRect(0, 0, this.width, this.height);
}
return false; // event has not been processed
}
</script>
</head>
<body bgcolor="#000000" onload="init()"
style="-webkit-user-drag:none; -webkit-user-select:none; ">
<span style="color:white;">A canvas element cannot be displayed.</span>
</body>
</html>
与可拖动对象类似,应为每个可变换对象定义一个全局变量。在示例中,看起来像这样
// create draggable objects
var transformable0 = new cuiTransformable();
var transformable1 = new cuiTransformable();
然后在页面的处理函数中处理可变换对象
// draw and react to transformables
if (transformable0.process(event, 50, 100, 200, 200, null,
imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien,
cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
transformable0.scale = 1.0; // always reset scale (i.e. don't scale)
return true;
}
if (transformable1.process(event, 250, 100, 200, 200, null,
imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien,
cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
transformable1.rotation = 0.0; // always reset rotation (i.e. dont' rotate)
return true;
}
处理函数的最后一个参数(这里为 cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers
)指定了可用的交互形式。要支持多种交互形式,需要按示例中所示使用按位或运算将常量组合起来。其他有用的常量是 cuiConstants.isDraggableWithTwoFingers
、cuiConstants.isRotatableWithTwoFingers
和 cuiConstants.isUniformlyScalableWithTwoFingers
。使用这些标志停用旋转和缩放不如示例中的方法灵活;但是,它可以避免一些与旋转和缩放手势相关的平移运动。
process
函数在对象处理事件时返回 true
。在这种情况下,应采取其他操作。这里,通过始终将其设置为 1.0 来停用第一个对象的缩放。类似地,通过始终将旋转角度设置为 0 度来停用第二个对象的旋转。
当然,对对象的变换有许多其他的反应方式;例如,限制 translationX
和 translationY
。
由于不同的变换和手势使用两个触点,因此可变换对象比可拖动对象需要更多的属性
/**
* @class cuiTransformable
* @classdesc Transformable objects can be translated, rotated, and scaled with one- and
* two-finger gestures.
*
* @desc Create a new cuiTransformable.
*/
function cuiTransformable() {
/**
* Clockwise rotation angle in degrees by which the object has been rotated.
* @member {number} cuiTransformable.rotation
*/
this.rotation = 0;
/**
* Scaling factor by which the object has been magnified.
* @member {number} cuiTransformable.scale
*/
this.scale = 1;
/**
* Difference in x coordinate by which the centre of the transformable has been moved relative to its
* initial position (specified by x + 0.5 * width with the arguments of {@link cuiTransformable#process}).
* @member {number} cuiTransformable.translationX
*/
this.translationX = 0;
/**
* Difference in y coordinate by which the centre of the transformable has been moved relative to its
* initial position (specified by y + 0.5 * height with the arguments of {@link cuiTransformable#process}).
* @member {number} cuiTransformable.translationY
*/
this.translationY = 0;
/**
* Flag specifying whether a mouse button or first finger is inside the object's rectangle.
* @member {boolean} cuiTransformable.isPointerInside0
*/
this.isPointerInside0 = false;
/**
* Flag specifying whether a mouse button or first finger is pushing the object or has been
* pushing the object and is still held down (but may have moved outside the object's
* rectangle).
* @member {boolean} cuiTransformable.isPointerDown0
*/
this.isPointerDown0 = false;
/**
* Flag specifying whether a second finger is pushing the object or has been
* pushing the object and is still held down (but may have moved outside the object's
* rectangle).
* @member {boolean} cuiTransformable.isPointerDown1
*/
this.isPointerDown1 = false;
this.hasTriggeredClick = false; // click event has been triggered?
this.hasTriggeredDoubleClick = false; // double click event has been triggered?
this.hasTriggeredHold0 = false; // hold event has been triggered for first pointer?
this.hasTriggeredHold1 = false; // hold event has been triggered for second pointer?
/**
* Flag to specify whether to process events even if they are outside of the rectangle.
* If true, it will consume many more events and therefore should only be used for background objects.
* @member {boolean} cuiTransformable.isProcessingOuterEvents
*/
this.isProcessingOuterEvents = false;
this.timeDown0 = 0; // time in milliseconds after January 1, 1970 when the first pointer went down
this.timeDown1 = 0; // time in milliseconds after January 1, 1970 when the second pointer went down
this.identifier0 = -1; // identifier of the first touch point (-1 for mouse)
this.identifier1 = -1; // identifier of the second touch point (-1 for mouse)
this.translationXDown = 0; // value of translationX when the pointer went down
this.translationYDown = 0; // value of translationX when the pointer went down
this.rotationDown = 0; // value of rotation when the pointer went down
this.scaleDown = 0; // value of scale when the pointer went down
this.eventXDown0 = 0; // x coordinate of the event when the first pointer went down
this.eventYDown0 = 0; // y coordinate of the event when the first pointer went down
this.eventXDown1 = 0; // x coordinate of the event when the second pointer went down
this.eventYDown1 = 0; // y coordinate of the event when the second pointer went down
this.eventX0 = 0; // current x coordinate of the first pointer
this.eventY0 = 0; // current Y coordinate of the first pointer
this.eventX1 = 0; // current x coordinate of the second pointer
this.eventY1 = 0; // current y coordinate of the second pointer
};
/**
* Returns whether the transformable has just been clicked.
* @returns {boolean} True if the draggable has been clicked, false otherwise.
*/
cuiTransformable.prototype.isClicked = function() {
return this.hasTriggeredClick;
}
/**
* Determine whether the button has just been double clicked.
* @returns {boolean} True if the button has been double clicked, false otherwise.
*/
cuiTransformable.prototype.isDoubleClicked = function() {
return this.hasTriggeredDoubleClick;
}
/**
* Determine whether first pointer has just been held down longer than {@link cuiTimeUntilHold}.
* @returns {boolean} True if the first pointer has just been held down long enough, false otherwise.
*/
cuiTransformable.prototype.isHeldDown0 = function() {
return this.hasTriggeredHold0;
}
/**
* Determine whether second pointer has just been held down longer than {@link cuiTimeUntilHold}.
* @returns {boolean} True if the second pointer has just been held down long enough, false otherwise.
*/
cuiTransformable.prototype.isHeldDown1 = function() {
return this.hasTriggeredHold1;
}
/**
* Either process the event (if event != null) and return true if the event has been processed,
* or draw the appropriate image for the object state in the rectangle
* with a text string on top of it (if event == null) and return false.
* This function is usually called by {@link cuiPage.process} of a {@link cuiPage}.
* @param {Object} event - An object describing a user event by its "type", coordinates in
* page coordinates ("eventX" and "eventY"), an "identifier" for touch events, and optionally
* "buttons" to specify which mouse buttons are depressed. If null, the function should
* redraw the object.
* @param {number} x - The x coordinate of the top, left corner of the object's rectangle.
* @param {number} y - The y coordinate of the top, left corner of the object's rectangle.
* @param {number} width - The width of the object's rectangle.
* @param {number} height - The height of the object's rectangle.
* @param {string} text - A text that is written at the center of the rectangle. (May be null).
* @param {Object} imageNormal - An image to be drawn inside the object's rectangle if there
* are no user interactions. (May be null.)
* @param {Object} imageFocused - An image to be drawn inside the object's rectangle if the
* mouse hovers over the object's rectangle or a touch point moves into it. (May be null.)
* @param {Object} imagePressed0 - An image to be drawn inside the object's rectangle if a
* mouse button is pushed or the object is touched once. (May be null.)
* @param {Object} imagePressed1 - An image to be drawn inside the object's rectangle if a
* mouse button is pushed or the object is touched twice. (May be null.)
* @param {number} interactionBits - The forms of interaction, either {@link cuiConstants.none}
* or a bitwise-or of other constants in {@link cuiConstants}, e.g.
* cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers.
* @returns {boolean} True if event != null and the event has been processed (implying that
* no other GUI elements should process it). False otherwise.
*/
cuiTransformable.prototype.process = function (event, x, y, width, height,
text, imageNormal, imageFocused, imagePressed0, imagePressed1, interactionBits) {
if (null == event) {
// choose appropriate image
var image = imageNormal;
if (this.isPointerDown1) {
image = imagePressed1;
}
else if (this.isPointerDown0) {
image = imagePressed0;
}
else if (this.isPointerInside0) {
image = imageFocused;
}
// transform and draw object
cuiContext.save();
cuiContext.translate(this.translationX, this.translationY);
cuiContext.translate(x + 0.5 * width, y + 0.5 * height);
cuiContext.rotate(this.rotation * Math.PI / 180.0);
cuiContext.scale(this.scale, this.scale);
cuiContext.translate(-x - 0.5 * width, -y - 0.5 * height);
if (null != text) {
cuiContext.fillText(text, x, y, width, height);
}
if (null != image) {
cuiContext.drawImage(image, x, y, width, height);
}
cuiContext.restore();
return false;
}
// check point of event
var isIn = false;
var mappedX = event.eventX - this.translationX;
var mappedY = event.eventY - this.translationY;
mappedX = mappedX - x - 0.5 * width;
mappedY = mappedY - y - 0.5 * height;
var angle = -this.rotation * Math.PI / 180.0;
var tempX = Math.cos(angle) * mappedX - Math.sin(angle) * mappedY;
mappedY = Math.sin(angle) * mappedX + Math.cos(angle) * mappedY;
mappedX = tempX / this.scale;
mappedY = mappedY / this.scale;
mappedX = mappedX + x + 0.5 * width;
mappedY = mappedY + y + 0.5 * height;
if ((x <= mappedX && mappedX < x + width && y <= mappedY && mappedY < y + height) ||
this.isProcessingOuterEvents) {
isIn = true;
}
// clear event notifications (they are only set once and need to be cleared afterwards)
this.hasTriggeredClick = false;
this.hasTriggeredDoubleClick = false;
this.hasTriggeredHold0 = false;
this.hasTriggeredHold1 = false;
// process double click events
if ("dblclick" == event.type) {
this.hasTriggeredDoubleClick = isIn;
return isIn;
}
// process our hold events
if ("mousehold" == event.type) {
if (event.timeDown == this.timeDown0 && event.identifier == this.identifier0 &&
this.isPointerDown0) {
this.hasTriggeredHold0 = true;
return true;
}
return false;
}
if ("mousehold" == event.type) {
if (event.timeDown == this.timeDown1 && event.identifier == this.identifier1 &&
this.isPointerDown1) {
this.hasTriggeredHold1 = true;
return true;
}
return false;
}
// process wheel events
if ("wheel" == event.type || "mousewheel" == event.type) {
if (!(cuiConstants.isUniformlyScalableWithTwoFingers & interactionBits)) {
return false;
}
if (isIn) {
// compute new x and y based on the motion of the point under the mouse
// first compute the point that is mapped to the point under the mouse
var fixpointX = event.eventX;
var fixpointY = event.eventY;
var mappedX = fixpointX - this.translationX - x - 0.5 * width;
var mappedY = fixpointY - this.translationY - y - 0.5 * height;
var angle = -this.rotation * Math.PI / 180.0;
fixpointX = (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY) / this.scale
+ x + 0.5 * width;
fixpointY = (Math.sin(angle) * mappedX + Math.cos(angle) * mappedY) / this.scale
+ y + 0.5 * height;
// change scale
var delta;
if ("mousewheel" == event.type) {
delta = -event.wheelDelta / 30.0;
}
else {
delta = event.deltaY;
}
this.scale = this.scale * Math.pow(2.0, -0.025 * delta);
// now see where this fixpoint is mapped to with the current transformation
mappedX = fixpointX - x - 0.5 * width;
mappedY = fixpointY - y - 0.5 * height;
angle = this.rotation * Math.PI / 180.0;
var tempX = this.scale * (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY);
mappedY = this.scale * (Math.sin(angle) * mappedX + Math.cos(angle) * mappedY);
mappedX = tempX + x + 0.5 * width + this.translationX;
mappedY = mappedY + y + 0.5 * height + this.translationY;
// (x,y) should be at the position of the mouse;
// we change the transformation such that it ends up there
this.translationX = this.translationX + event.eventX - mappedX;
this.translationY = this.translationY + event.eventY - mappedY;
cuiRepaint();
}
return isIn;
}
// ignore mouse or touch points that are not the tracked point (apart from mousedown and touchstart)
if ((this.isPointerInside0 || this.isPointerDown0) && !this.isPointerDown1) {
if ("touchend" == event.type || "touchmove" == event.type || "touchcancel" == event.type) {
if (event.identifier != this.identifier0) {
return false; // ignore all other touch points except "touchstart" events
}
}
else if (("mousemove" == event.type || "mouseup" == event.type) && this.identifier0 >= 0) {
return false; // ignore mouse (except mousedown) if we are tracking a touch point
}
}
if (this.isPointerDown0 && this.isPointerDown1) {
if ("touchend" == event.type || "touchmove" == event.type || "touchcancel" == event.type) {
if (event.identifier != this.identifier0 &&
event.identifier != this.identifier1) {
return false; // ignore all other touch points except "touchstart" events
}
}
else if (("mousemove" == event.type || "mouseup" == event.type) &&
(this.identifier0 >= 0 && this.identifier1 >= 0)) {
return false; // ignore mouse (except mousedown) if we are tracking a touch point
}
}
// state changes
if (!this.isPointerInside0 && !this.isPointerDown0 && !this.isPointerDown1) { // passive object state
if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) { // add 0th point
this.isPointerDown0 = true;
this.isPointerInside0 = true;
if ("touchstart" == event.type) {
this.identifier0 = event.identifier;
}
else {
this.identifier0 = -1; // mouse
}
this.timeDown0 = (new Date()).getTime();
setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier0, this.timeDown0);
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = event.eventX;
this.eventYDown0 = event.eventY;
this.eventX0 = event.eventX;
this.eventY0 = event.eventY;
cuiRepaint();
return true;
}
else if (isIn && ("mousemove" == event.type || "mouseup" == event.type ||
"touchmove" == event.type)) {
this.isPointerDown0 = false;
this.isPointerInside0 = true;
if ("touchmove" == event.type) {
this.identifier0 = event.identifier;
}
else {
this.identifier0 = -1; // mouse
}
cuiRepaint();
return true;
}
else {
return false; // none of our business
}
}
else if (this.isPointerInside0 && !this.isPointerDown0 && !this.isPointerDown1) { // focused object state (not pushed yet)
if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) { // add 0th point
this.isPointerDown0 = true;
this.isPointerInside0 = true;
if ("touchstart" == event.type) {
this.identifier0 = event.identifier;
}
else {
this.identifier0 = -1; // mouse
}
this.timeDown0 = (new Date()).getTime();
setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier0, this.timeDown0);
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = event.eventX;
this.eventYDown0 = event.eventY;
this.eventX0 = event.eventX;
this.eventY0 = event.eventY;
cuiRepaint();
return true;
}
else if (isIn && ("touchend" == event.type || "touchcancel" == event.type)) {
this.isPointerDown0 = false;
this.isPointerInside0 = false;
cuiRepaint();
return true;
}
else if (!isIn && ("touchmove" == event.type || "touchend" == event.type ||
"touchcancel" == event.type || "mousemove" == event.type || "mouseup" == event.type)) {
this.isPointerDown0 = false;
this.isPointerInside0 = false;
cuiRepaint();
return false; // none of our business
}
else {
return false; // none of our business
}
}
else if (this.isPointerDown0 && !this.isPointerDown1) { // object grabbed once
if (isIn && this.identifier0 < 0 && "mousedown" == event.type) {
// replace 0th mouse point
this.identifier0 = -1; // mouse down
this.isPointerDown0 = true;
this.timeDown0 = (new Date()).getTime();
setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier0, this.timeDown0);
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = event.eventX;
this.eventYDown0 = event.eventY;
this.eventX0 = event.eventX;
this.eventY0 = event.eventY;
cuiRepaint();
return true;
}
else if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) {
// add 1st touch point
this.isPointerDown1 = true;
if ("touchstart" == event.type) {
this.identifier1 = event.identifier;
}
else {
this.identifier1 = -1; // mouse
}
this.timeDown1 = (new Date()).getTime();
setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier1, this.timeDown1);
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = this.eventX0;
this.eventYDown0 = this.eventY0;
this.eventXDown1 = event.eventX;
this.eventYDown1 = event.eventY;
this.eventX1 = event.eventX;
this.eventY1 = event.eventY;
cuiRepaint();
return true;
}
else if ("mouseup" == event.type || ("mousemove" == event.type && 0 == event.buttons)) {
this.isPointerDown0 = false;
this.isPointerInside0 = isIn;
this.identifier0 = -1; // mouse
if (isIn) {
this.hasTriggeredClick = true;
}
cuiRepaint();
return true;
}
else if ("touchend" == event.type) {
this.isPointerDown0 = false;
this.isPointerInside0 = false;
if (isIn) {
this.hasTriggeredClick = true;
}
cuiRepaint();
return true;
}
else if ("touchcancel" == event.type) {
this.isPointerDown0 = false;
this.isPointerInside0 = false;
cuiRepaint();
return true;
}
else if ("touchmove" == event.type || ("mousemove" == event.type)) {
this.isPointerInside0 = isIn;
this.eventX0 = event.eventX;
this.eventY0 = event.eventY;
if (cuiConstants.isDraggableWithOneFinger & interactionBits) {
this.translationX = this.translationXDown + (this.eventX0 - this.eventXDown0);
this.translationY = this.translationYDown + (this.eventY0 - this.eventYDown0);
}
cuiRepaint();
return true;
}
else if (!isIn && (("mousedown" == event.type && this.identifier0 < 0) ||
("touchstart" == event.type && this.identifier0 == event.identifier))) {
this.isPointerDown0 = false;
this.isPointerInside0 = false;
cuiRepaint();
return false; // none of our business
}
else {
return false; // none of our business
}
}
else if (this.isPointerDown1) { // two pointers down
if (("mouseup" == event.type && this.identifier0 < 0) ||
(("touchend" == event.type || "touchcancel" == event.type) &&
event.identifier == this.identifier0)) { // 0th point goes up
// remove 0th point, replace by 1st
this.isPointerDown1 = false;
this.isPointerDown0 = true;
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = this.eventX1;
this.eventYDown0 = this.eventY1;
this.eventX0 = this.eventX1;
this.eventY0 = this.eventY1;
this.identifier0 = this.identifier1;
if (isIn) {
this.hasTriggeredClick = true;
}
cuiRepaint();
return true;
}
else if (("mouseup" == event.type && this.identifier1 < 0) ||
(("touchend" == event.type || "touchcancel" == event.type) &&
event.identifier == this.identifier1)) { // 1st point goes up
// just remove 1st point
this.isPointerDown1 = false;
this.isPointerDown0 = true;
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = this.eventX0;
this.eventYDown0 = this.eventY0;
if (isIn) {
this.hasTriggeredClick = true;
}
cuiRepaint();
return true;
}
else if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) {
// remove 0th point, replace by 1st, add new as 1stby removing the 0th point,
// the user has a way of getting rid of ghost points
// which are no longer tracked (but which we still assume to be active)
this.translationXDown = this.translationX;
this.translationYDown = this.translationY;
this.rotationDown = this.rotation;
this.scaleDown = this.scale;
this.eventXDown0 = this.eventX1;
this.eventYDown0 = this.eventY1;
this.eventX0 = this.eventX1;
this.eventY0 = this.eventY1;
this.identifier0 = this.identifier1;
this.eventXDown1 = event.eventX;
this.eventYDown1 = event.eventY;
this.eventX1 = event.eventX;
this.eventY1 = event.eventY;
if ("touchstart" == event.type) {
this.identifier1 = event.identifier;
}
else {
this.identifier1 = -1; // mouse
}
cuiRepaint();
return true;
}
else if ("touchmove" == event.type || ("mousemove" == event.type)) {
// update dragging
if (("mousemove" == event.type && this.identifier0 < 0) ||
("touchmove" == event.type && event.identifier == this.identifier0)) {
this.eventX0 = event.eventX;
this.eventY0 = event.eventY;
}
else if (("mousemove" == event.type && this.identifier1 < 0) ||
("touchmove" == event.type && event.identifier == this.identifier1)) {
this.eventX1 = event.eventX;
this.eventY1 = event.eventY;
}
else {
return false; // we should not have gotten this event (see above for the filtering)
}
if (cuiConstants.isRotatableWithTwoFingers & interactionBits) {
// compute new rotation
this.rotation = this.rotationDown +
(Math.atan2(this.eventY1 - this.eventY0,
this.eventX1 - this.eventX0) -
Math.atan2(this.eventYDown1 - this.eventYDown0,
this.eventXDown1 - this.eventXDown0)
) * 180.0 / Math.PI;
while (this.rotation >= 360.0) {
this.rotation -= 360.0;
}
while (this.rotation < 0.0) {
this.rotation += 360.0;
}
}
if (cuiConstants.isScalableWithTwoFingers & interactionBits) {
// compute new scale
var diffPointsLength =
Math.sqrt((this.eventX0 - this.eventX1) *
(this.eventX0 - this.eventX1) +
(this.eventY0 - this.eventY1) *
(this.eventY0 - this.eventY1));
var diffPointsLengthDown =
Math.sqrt((this.eventXDown0 - this.eventXDown1) *
(this.eventXDown0 - this.eventXDown1) +
(this.eventYDown0 - this.eventYDown1) *
(this.eventYDown0 - this.eventYDown1));
this.scale = this.scaleDown * diffPointsLength /
diffPointsLengthDown;
}
if (cuiConstants.isDraggableWithTwoFingers & interactionBits) {
// compute new x and y based on the motion of the center between the two points
// first compute the point that was mapped to the center between the two fingers when grabbed
var fixpointX = 0.5 * (this.eventXDown0 + this.eventXDown1);
var fixpointY = 0.5 * (this.eventYDown0 + this.eventYDown1);
var mappedX = fixpointX - this.translationXDown - x - 0.5 * width;
var mappedY = fixpointY - this.translationYDown - y - 0.5 * height;
var angle = -this.rotationDown * Math.PI / 180.0;
fixpointX = (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY) / this.scaleDown
+ x + 0.5 * width;
fixpointY = (Math.sin(angle) * mappedX + Math.cos(angle) * mappedY) / this.scaleDown
+ y + 0.5 * height;
// now see where this fixpoint is mapped to with the current transformation
mappedX = fixpointX - x - 0.5 * width;
mappedY = fixpointY - y - 0.5 * height;
angle = this.rotation * Math.PI / 180.0;
var tempX = this.scale * (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY);
mappedY = this.scale * (Math.sin(angle) * mappedX + Math.cos(angle) * mappedY);
mappedX = tempX + x + 0.5 * width + this.translationX;
mappedY = mappedY + y + 0.5 * height + this.translationY;
// (x,y) should be at the center between the two fingers;
// we change the transformation such that it ends up there
this.translationX = this.translationX +
0.5 * (this.eventX0 + this.eventX1) - mappedX;
this.translationY = this.translationY +
0.5 * (this.eventY0 + this.eventY1) - mappedY;
}
cuiRepaint();
return true;
}
else {
return false;
}
}
// unreachable code
return false;
}