/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var Guacamole = Guacamole || {};
/**
* Provides cross-browser multi-touch events for a given element. The events of
* the given element are automatically populated with handlers that translate
* touch events into a non-browser-specific event provided by the
* Guacamole.Touch instance.
*
* @constructor
* @augments Guacamole.Event.Target
* @param {!Element} element
* The Element to use to provide touch events.
*/
Guacamole.Touch = function Touch(element) {
Guacamole.Event.Target.call(this);
/**
* Reference to this Guacamole.Touch.
*
* @private
* @type {!Guacamole.Touch}
*/
var guacTouch = this;
/**
* The default X/Y radius of each touch if the device or browser does not
* expose the size of the contact area.
*
* @private
* @constant
* @type {!number}
*/
var DEFAULT_CONTACT_RADIUS = Math.floor(16 * window.devicePixelRatio);
/**
* The set of all active touches, stored by their unique identifiers.
*
* @type {!Object.<Number, Guacamole.Touch.State>}
*/
this.touches = {};
/**
* The number of active touches currently stored within
* {@link Guacamole.Touch#touches touches}.
*/
this.activeTouches = 0;
/**
* Fired whenever a new touch contact is initiated on the element
* associated with this Guacamole.Touch.
*
* @event Guacamole.Touch#touchstart
* @param {!Guacamole.Touch.Event} event
* A {@link Guacamole.Touch.Event} object representing the "touchstart"
* event.
*/
/**
* Fired whenever an established touch contact moves within the element
* associated with this Guacamole.Touch.
*
* @event Guacamole.Touch#touchmove
* @param {!Guacamole.Touch.Event} event
* A {@link Guacamole.Touch.Event} object representing the "touchmove"
* event.
*/
/**
* Fired whenever an established touch contact is lifted from the element
* associated with this Guacamole.Touch.
*
* @event Guacamole.Touch#touchend
* @param {!Guacamole.Touch.Event} event
* A {@link Guacamole.Touch.Event} object representing the "touchend"
* event.
*/
element.addEventListener('touchstart', function touchstart(e) {
// Fire "ontouchstart" events for all new touches
for (var i = 0; i < e.changedTouches.length; i++) {
var changedTouch = e.changedTouches[i];
var identifier = changedTouch.identifier;
// Ignore duplicated touches
if (guacTouch.touches[identifier])
continue;
var touch = guacTouch.touches[identifier] = new Guacamole.Touch.State({
id : identifier,
radiusX : changedTouch.radiusX || DEFAULT_CONTACT_RADIUS,
radiusY : changedTouch.radiusY || DEFAULT_CONTACT_RADIUS,
angle : changedTouch.angle || 0.0,
force : changedTouch.force || 1.0 /* Within JavaScript changedTouch events, a force of 0.0 indicates the device does not support reporting changedTouch force */
});
guacTouch.activeTouches++;
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
guacTouch.dispatch(new Guacamole.Touch.Event('touchmove', e, touch));
}
}, false);
element.addEventListener('touchmove', function touchstart(e) {
// Fire "ontouchmove" events for all updated touches
for (var i = 0; i < e.changedTouches.length; i++) {
var changedTouch = e.changedTouches[i];
var identifier = changedTouch.identifier;
// Ignore any unrecognized touches
var touch = guacTouch.touches[identifier];
if (!touch)
continue;
// Update force only if supported by browser (otherwise, assume
// force is unchanged)
if (changedTouch.force)
touch.force = changedTouch.force;
// Update touch area, if supported by browser and device
touch.angle = changedTouch.angle || 0.0;
touch.radiusX = changedTouch.radiusX || DEFAULT_CONTACT_RADIUS;
touch.radiusY = changedTouch.radiusY || DEFAULT_CONTACT_RADIUS;
// Update with any change in position
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
guacTouch.dispatch(new Guacamole.Touch.Event('touchmove', e, touch));
}
}, false);
element.addEventListener('touchend', function touchstart(e) {
// Fire "ontouchend" events for all updated touches
for (var i = 0; i < e.changedTouches.length; i++) {
var changedTouch = e.changedTouches[i];
var identifier = changedTouch.identifier;
// Ignore any unrecognized touches
var touch = guacTouch.touches[identifier];
if (!touch)
continue;
// Stop tracking this particular touch
delete guacTouch.touches[identifier];
guacTouch.activeTouches--;
// Touch has ended
touch.force = 0.0;
// Update with final position
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
guacTouch.dispatch(new Guacamole.Touch.Event('touchend', e, touch));
}
}, false);
};
/**
* The current state of a touch contact.
*
* @constructor
* @augments Guacamole.Position
* @param {Guacamole.Touch.State|object} [template={}]
* The object whose properties should be copied within the new
* Guacamole.Touch.State.
*/
Guacamole.Touch.State = function State(template) {
template = template || {};
Guacamole.Position.call(this, template);
/**
* An arbitrary integer ID which uniquely identifies this contact relative
* to other active contacts.
*
* @type {!number}
* @default 0
*/
this.id = template.id || 0;
/**
* The Y radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @type {!number}
* @default 0
*/
this.radiusX = template.radiusX || 0;
/**
* The X radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @type {!number}
* @default 0
*/
this.radiusY = template.radiusY || 0;
/**
* The rough angle of clockwise rotation of the general area of the touch
* contact, in degrees.
*
* @type {!number}
* @default 0.0
*/
this.angle = template.angle || 0.0;
/**
* The relative force exerted by the touch contact, where 0 is no force
* (the touch has been lifted) and 1 is maximum force (the maximum amount
* of force representable by the device).
*
* @type {!number}
* @default 1.0
*/
this.force = template.force || 1.0;
};
/**
* An event which represents a change in state of a single touch contact,
* including the creation or removal of that contact. If multiple contacts are
* involved in a touch interaction, each contact will be associated with its
* own event.
*
* @constructor
* @augments Guacamole.Event.DOMEvent
* @param {!string} type
* The name of the touch event type. Possible values are "touchstart",
* "touchmove", and "touchend".
*
* @param {!TouchEvent} event
* The DOM touch event that produced this Guacamole.Touch.Event.
*
* @param {!Guacamole.Touch.State} state
* The state of the touch contact associated with this event.
*/
Guacamole.Touch.Event = function TouchEvent(type, event, state) {
Guacamole.Event.DOMEvent.call(this, type, [ event ]);
/**
* The state of the touch contact associated with this event.
*
* @type {!Guacamole.Touch.State}
*/
this.state = state;
};