| Index: chrome/browser/resources/touch_ntp/grabber.js
|
| diff --git a/chrome/browser/resources/touch_ntp/grabber.js b/chrome/browser/resources/touch_ntp/grabber.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6844413b15acc5ff384afa32cae9a36e7e3ffb01
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/touch_ntp/grabber.js
|
| @@ -0,0 +1,375 @@
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview Grabber implementation.
|
| + * Allows you to pick up objects (with a long-press) and drag them around the
|
| + * screen.
|
| + *
|
| + * Note: This should perhaps really use standard drag-and-drop events, but there
|
| + * is no standard for them on touch devices. We could define a model for
|
| + * activating touch-based dragging of elements (programatically and/or with
|
| + * CSS attributes) and use it here (even have a JS library to generate such
|
| + * events when the browser doesn't support them).
|
| + */
|
| +'use strict';
|
| +
|
| +/**
|
| + * Create a Grabber object to enable grabbing and dragging a given element.
|
| + * @constructor
|
| + * @param {!Element} element The element that can be grabbed and moved.
|
| + */
|
| +function Grabber(element) {
|
| + /**
|
| + * The element the grabber is attached to.
|
| + * @type {!Element}
|
| + * @private
|
| + */
|
| + this.element_ = element;
|
| +
|
| + /**
|
| + * The TouchHandler responsible for firing lower-level touch events when the
|
| + * element is manipulated.
|
| + * @type {!TouchHandler}
|
| + * @private
|
| + */
|
| + this.touchHandler_ = new TouchHandler(this.element);
|
| +
|
| + /**
|
| + * Tracks all event listeners we have created.
|
| + * @type {EventTracker}
|
| + * @private
|
| + */
|
| + this.events_ = new EventTracker();
|
| +
|
| + // Enable the generation of events when the element is touched (but no need to
|
| + // use the early capture phase of event processing).
|
| + this.touchHandler_.enable(/* opt_capture */ false);
|
| +
|
| + // Prevent any built-in drag-and-drop support from activating for the element.
|
| + // Note that we don't want details of how we're implementing dragging here to
|
| + // leak out of this file (eg. we may switch to using webkit drag-and-drop).
|
| + this.events_.add(this.element, 'dragstart', function(e) {
|
| + e.preventDefault();
|
| + }, true);
|
| +
|
| + // Add our TouchHandler event listeners
|
| + this.events_.add(this.element, TouchHandler.EventType.TOUCH_START,
|
| + this.onTouchStart_.bind(this), false);
|
| + this.events_.add(this.element, TouchHandler.EventType.LONG_PRESS,
|
| + this.onLongPress_.bind(this), false);
|
| + this.events_.add(this.element, TouchHandler.EventType.DRAG_START,
|
| + this.onDragStart_.bind(this), false);
|
| + this.events_.add(this.element, TouchHandler.EventType.DRAG_MOVE,
|
| + this.onDragMove_.bind(this), false);
|
| + this.events_.add(this.element, TouchHandler.EventType.DRAG_END,
|
| + this.onDragEnd_.bind(this), false);
|
| + this.events_.add(this.element, TouchHandler.EventType.TOUCH_END,
|
| + this.onTouchEnd_.bind(this), false);
|
| +}
|
| +
|
| +/**
|
| + * Events fired by the grabber.
|
| + * Events are fired at the element affected (not the element being dragged).
|
| + * @enum {string}
|
| + */
|
| +Grabber.EventType = {
|
| + // Fired at the grabber element when it is first grabbed
|
| + GRAB: 'grabber:grab',
|
| + // Fired at the grabber element when dragging begins (after GRAB)
|
| + DRAG_START: 'grabber:dragstart',
|
| + // Fired at an element when something is dragged over top of it.
|
| + DRAG_ENTER: 'grabber:dragenter',
|
| + // Fired at an element when something is no longer over top of it.
|
| + // Not fired at all in the case of a DROP
|
| + DRAG_LEAVE: 'grabber:drag',
|
| + // Fired at an element when something is dropped on top of it.
|
| + DROP: 'grabber:drop',
|
| + // Fired at the grabber element when dragging ends (successfully or not) -
|
| + // after any DROP or DRAG_LEAVE
|
| + DRAG_END: 'grabber:dragend',
|
| + // Fired at the grabber element when it is released (even if no drag
|
| + // occured) - after any DRAG_END event.
|
| + RELEASE: 'grabber:release'
|
| +};
|
| +
|
| +/**
|
| + * The type of Event sent by Grabber
|
| + * @constructor
|
| + * @param {string} type The type of event (one of Grabber.EventType).
|
| + * @param {Element!} grabbedElement The element being dragged.
|
| + */
|
| +Grabber.Event = function(type, grabbedElement) {
|
| + var event = document.createEvent('Event');
|
| + event.initEvent(type, true, true);
|
| + event.__proto__ = Grabber.Event.prototype;
|
| +
|
| + /**
|
| + * The element which is being dragged. For some events this will be the same
|
| + * as 'target', but for events like DROP that are fired at another element it
|
| + * will be different.
|
| + * @type {!Element}
|
| + */
|
| + event.grabbedElement = grabbedElement;
|
| +
|
| + return event;
|
| +};
|
| +
|
| +Grabber.Event.prototype = {
|
| + __proto__: Event.prototype
|
| +};
|
| +
|
| +
|
| +/**
|
| + * The CSS class to apply when an element is touched but not yet
|
| + * grabbed.
|
| + * @type {string}
|
| + */
|
| +Grabber.PRESSED_CLASS = 'grabber-pressed';
|
| +
|
| +/**
|
| + * The class to apply when an element has been held (including when it is
|
| + * being dragged.
|
| + * @type {string}
|
| + */
|
| +Grabber.GRAB_CLASS = 'grabber-grabbed';
|
| +
|
| +/**
|
| + * The class to apply when a grabbed element is being dragged.
|
| + * @type {string}
|
| + */
|
| +Grabber.DRAGGING_CLASS = 'grabber-dragging';
|
| +
|
| +Grabber.prototype = {
|
| + /**
|
| + * @return {!Element} The element that can be grabbed.
|
| + */
|
| + get element() {
|
| + return this.element_;
|
| + },
|
| +
|
| + /**
|
| + * Clean up all event handlers (eg. if the underlying element will be removed)
|
| + */
|
| + dispose: function() {
|
| + this.touchHandler_.disable();
|
| + this.events_.removeAll();
|
| +
|
| + // Clean-up any active touch/drag
|
| + if (this.dragging_)
|
| + this.stopDragging_();
|
| + this.onTouchEnd_();
|
| + },
|
| +
|
| + /**
|
| + * Invoked whenever this element is first touched
|
| + * @param {!TouchHandler.Event} e The TouchHandler event.
|
| + * @private
|
| + */
|
| + onTouchStart_: function(e) {
|
| + this.element.classList.add(Grabber.PRESSED_CLASS);
|
| +
|
| + // Always permit the touch to perhaps trigger a drag
|
| + e.enableDrag = true;
|
| + },
|
| +
|
| + /**
|
| + * Invoked whenever the element stops being touched.
|
| + * Can be called explicitly to cleanup any active touch.
|
| + * @param {!TouchHandler.Event=} opt_e The TouchHandler event.
|
| + * @private
|
| + */
|
| + onTouchEnd_: function(opt_e) {
|
| + if (this.grabbed_) {
|
| + // Mark this element as no longer being grabbed
|
| + this.element.classList.remove(Grabber.GRAB_CLASS);
|
| + this.element.style.pointerEvents = '';
|
| + this.grabbed_ = false;
|
| +
|
| + this.sendEvent_(Grabber.EventType.RELEASE, this.element);
|
| + } else {
|
| + this.element.classList.remove(Grabber.PRESSED_CLASS);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handler for TouchHandler's LONG_PRESS event
|
| + * Invoked when the element is held (without being dragged)
|
| + * @param {!TouchHandler.Event} e The TouchHandler event.
|
| + * @private
|
| + */
|
| + onLongPress_: function(e) {
|
| + assert(!this.grabbed_, 'Got longPress while still being held');
|
| +
|
| + this.element.classList.remove(Grabber.PRESSED_CLASS);
|
| + this.element.classList.add(Grabber.GRAB_CLASS);
|
| +
|
| + // Disable mouse events from the element - we care only about what's
|
| + // under the element after it's grabbed (since we're getting move events
|
| + // from the body - not the element itself). Note that we can't wait until
|
| + // onDragStart to do this because it won't have taken effect by the first
|
| + // onDragMove.
|
| + this.element.style.pointerEvents = 'none';
|
| +
|
| + this.grabbed_ = true;
|
| +
|
| + this.sendEvent_(Grabber.EventType.GRAB, this.element);
|
| + },
|
| +
|
| + /**
|
| + * Invoked when the element is dragged.
|
| + * @param {!TouchHandler.Event} e The TouchHandler event.
|
| + * @private
|
| + */
|
| + onDragStart_: function(e) {
|
| + assert(!this.lastEnter_, 'only expect one drag to occur at a time');
|
| + assert(!this.dragging_);
|
| +
|
| + // We only want to drag the element if its been grabbed
|
| + if (this.grabbed_) {
|
| + // Mark the item as being dragged
|
| + // Ensures our translate transform won't be animated and cancels any
|
| + // outstanding animations.
|
| + this.element.classList.add(Grabber.DRAGGING_CLASS);
|
| +
|
| + // Determine the webkitTransform currently applied to the element.
|
| + // Note that it's important that we do this AFTER cancelling animation,
|
| + // otherwise we could see an intermediate value.
|
| + // We'll assume this value will be constant for the duration of the drag
|
| + // so that we can combine it with our translate3d transform.
|
| + this.baseTransform_ = this.element.ownerDocument.defaultView.
|
| + getComputedStyle(this.element).webkitTransform;
|
| +
|
| + this.sendEvent_(Grabber.EventType.DRAG_START, this.element);
|
| + e.enableDrag = true;
|
| + this.dragging_ = true;
|
| +
|
| + } else {
|
| + // Hasn't been grabbed - don't drag, just unpress
|
| + this.element.classList.remove(Grabber.PRESSED_CLASS);
|
| + e.enableDrag = false;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Invoked when a grabbed element is being dragged
|
| + * @param {!TouchHandler.Event} e The TouchHandler event.
|
| + * @private
|
| + */
|
| + onDragMove_: function(e) {
|
| + assert(this.grabbed_ && this.dragging_);
|
| +
|
| + this.translateTo_(e.dragDeltaX, e.dragDeltaY);
|
| +
|
| + var target = e.touchedElement;
|
| + if (target && target != this.lastEnter_) {
|
| + // Send the events
|
| + this.sendDragLeave_(e);
|
| + this.sendEvent_(Grabber.EventType.DRAG_ENTER, target);
|
| + }
|
| + this.lastEnter_ = target;
|
| + },
|
| +
|
| + /**
|
| + * Send DRAG_LEAVE to the element last sent a DRAG_ENTER if any.
|
| + * @param {!TouchHandler.Event} e The event triggering this DRAG_LEAVE.
|
| + * @private
|
| + */
|
| + sendDragLeave_: function(e) {
|
| + if (this.lastEnter_) {
|
| + this.sendEvent_(Grabber.EventType.DRAG_LEAVE, this.lastEnter_);
|
| + this.lastEnter_ = undefined;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Moves the element to the specified position.
|
| + * @param {number} x Horizontal position to move to.
|
| + * @param {number} y Vertical position to move to.
|
| + * @private
|
| + */
|
| + translateTo_: function(x, y) {
|
| + // Order is important here - we want to translate before doing the zoom
|
| + this.element.style.WebkitTransform = 'translate3d(' + x + 'px, ' +
|
| + y + 'px, 0) ' + this.baseTransform_;
|
| + },
|
| +
|
| + /**
|
| + * Invoked when the element is no longer being dragged.
|
| + * @param {TouchHandler.Event} e The TouchHandler event.
|
| + * @private
|
| + */
|
| + onDragEnd_: function(e) {
|
| + // We should get this before the onTouchEnd. Don't change
|
| + // this.grabbed_ - it's onTouchEnd's responsibility to clear it.
|
| + assert(this.grabbed_ && this.dragging_);
|
| + var event;
|
| +
|
| + // Send the drop event to the element underneath the one we're dragging.
|
| + var target = e.touchedElement;
|
| + if (target)
|
| + this.sendEvent_(Grabber.EventType.DROP, target);
|
| +
|
| + // Cleanup and send DRAG_END
|
| + // Note that like HTML5 DND, we don't send DRAG_LEAVE on drop
|
| + this.stopDragging_();
|
| + },
|
| +
|
| + /**
|
| + * Clean-up the active drag and send DRAG_LEAVE
|
| + * @private
|
| + */
|
| + stopDragging_: function() {
|
| + assert(this.dragging_);
|
| + this.lastEnter_ = undefined;
|
| +
|
| + // Mark the element as no longer being dragged
|
| + this.element.classList.remove(Grabber.DRAGGING_CLASS);
|
| + this.element.style.webkitTransform = '';
|
| +
|
| + this.dragging_ = false;
|
| + this.sendEvent_(Grabber.EventType.DRAG_END, this.element);
|
| + },
|
| +
|
| + /**
|
| + * Send a Grabber event to a specific element
|
| + * @param {string} eventType The type of event to send.
|
| + * @param {!Element} target The element to send the event to.
|
| + * @private
|
| + */
|
| + sendEvent_: function(eventType, target) {
|
| + var event = new Grabber.Event(eventType, this.element);
|
| + target.dispatchEvent(event);
|
| + },
|
| +
|
| + /**
|
| + * Whether or not the element is currently grabbed.
|
| + * @type {boolean}
|
| + * @private
|
| + */
|
| + grabbed_: false,
|
| +
|
| + /**
|
| + * Whether or not the element is currently being dragged.
|
| + * @type {boolean}
|
| + * @private
|
| + */
|
| + dragging_: false,
|
| +
|
| + /**
|
| + * The webkitTransform applied to the element when it first started being
|
| + * dragged.
|
| + * @type {string|undefined}
|
| + * @private
|
| + */
|
| + baseTransform_: undefined,
|
| +
|
| + /**
|
| + * The element for which a DRAG_ENTER event was last fired
|
| + * @type {Element|undefined}
|
| + * @private
|
| + */
|
| + lastEnter_: undefined
|
| +};
|
| +
|
|
|