Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(703)

Unified Diff: chrome/browser/resources/touch_ntp/grabber.js

Issue 6661024: Use a specialized new tab page in TOUCH_UI builds (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..5e77ba85f2f25ae05dc8e34bd531ce7c24d214e4
--- /dev/null
+++ b/chrome/browser/resources/touch_ntp/grabber.js
@@ -0,0 +1,360 @@
+// 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).
+ */
+
+/**
+ * @constructor
arv (Not doing code reviews) 2011/03/10 19:25:59 Missing description
Rick Byers 2011/03/11 02:44:33 Done.
+ * @param {!Element} element The element that can be grabbed and moved.
+ */
+function Grabber(element) {
+ /**
+ * @type {!Element}
+ * @private
+ */
+ this.element_ = element;
+
+ /**
+ * @type {!TouchHandler}
+ * @private
+ */
+ this.touchHandler_ = new TouchHandler(this.element_);
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.grabbed_ = false;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.dragging_ = false;
+
+ /**
+ * @type {EventTracker}
+ * @private
+ */
+ this.events_ = new EventTracker();
+
+ this.touchHandler_.enable(/* opt_capture */ false);
arv (Not doing code reviews) 2011/03/10 19:25:59 this.touchHandler_.enabled = false; seems like a
Rick Byers 2011/03/11 02:44:33 enabled=false would suggest to me that the touchHa
arv (Not doing code reviews) 2011/03/11 20:08:46 OK, then it makes sense.
+
+ // 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).
arv (Not doing code reviews) 2011/03/10 19:25:59 @enum {string}
Rick Byers 2011/03/11 02:44:33 Done.
+ */
+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 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';
+
+/**
+ * The webkitTransform applied to the element when it first started being
+ * dragged.
+ * @type {string|undefined}
+ */
+Grabber.prototype.baseTransform_;
+
+/**
+ * The element for which a DRAG_ENTER event was last fired
+ * @type {Element|undefined}
+ */
+Grabber.prototype.lastEnter_;
+
+/**
+ * Clean up all event handlers (eg. if the underlying element will be removed)
+ */
+Grabber.prototype.dispose = function() {
+ this.touchHandler_.disable();
+ this.events_.removeAll();
+
+ // Clean-up any active touch/drag
+ if (this.dragging_) {
arv (Not doing code reviews) 2011/03/10 19:25:59 Chrome style is to not use curly braces for single
Rick Byers 2011/03/11 02:44:33 Done. Does this apply to if/else blocks to? Eg. I
arv (Not doing code reviews) 2011/03/11 20:08:46 Yes. If either the if or else part is more than o
Rick Byers 2011/03/15 21:47:54 Ok, thanks. I believe I've updated for this braci
+ this.stopDragging_();
+ }
+ this.onTouchEnd_();
+};
+
+/**
+ * Invoked whenever this element is first touched
+ * @param {!CustomEvent} e The TouchHandler event.
arv (Not doing code reviews) 2011/03/10 19:25:59 CustomEvent is a useless DOM interface. Just use E
Rick Byers 2011/03/11 02:44:33 The only reason I used CustomEvent is that it adds
+ * @private
+ */
+Grabber.prototype.onTouchStart_ = function(e) {
+ this.element_.classList.add(Grabber.PRESSED_CLASS);
+
+ // Always permit the touch to perhaps trigger a drag
+ e.detail.enableDrag = true;
+};
+
+/**
+ * Invoked whenever the element stops being touched.
+ * Can be called explicitly to cleanup any active touch.
+ * @param {!CustomEvent=} opt_e The TouchHandler event.
+ * @private
+ */
+Grabber.prototype.onTouchEnd_ = function(opt_e) {
+ if (this.grabbed_) {
+ // Mark this element as no longer being grabbed
+ this.element_.classList.remove(Grabber.GRAB_CLASS);
+ this.grabbed_ = false;
+
+ this.dispatchEvent_(Grabber.EventType.RELEASE);
arv (Not doing code reviews) 2011/03/10 19:25:59 cr.dispatchSimpleEvent(this, Grabber.EventType.REL
Rick Byers 2011/03/11 02:44:33 I should have looked more closely at the utilities
arv (Not doing code reviews) 2011/03/11 20:08:46 (I misunderstood this. See more in next comment)
Rick Byers 2011/03/15 21:47:54 See below.
+ }
+ 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 {!CustomEvent} e The TouchHandler event.
+ * @private
+ */
+Grabber.prototype.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);
+ this.grabbed_ = true;
+
+ this.dispatchEvent_(Grabber.EventType.GRAB);
+};
+
+/**
+ * Invoked when the element is dragged.
+ * @param {!CustomEvent} e The TouchHandler event.
+ * @private
+ */
+Grabber.prototype.onDragStart_ = function(e) {
+ var that = this;
+ 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_ = window.getComputedStyle(this.element_, null).
arv (Not doing code reviews) 2011/03/10 19:25:59 is this the right window? this.ownerDocument.defa
arv (Not doing code reviews) 2011/03/10 19:25:59 skip null here?
Rick Byers 2011/03/11 02:44:33 Interesting - thanks. I had to read up on this a
Rick Byers 2011/03/11 02:44:33 Done. I thought JSCompiler was complaining if I o
arv (Not doing code reviews) 2011/03/11 20:08:46 I assume most of the Chrome JS would fail in the p
+ webkitTransform;
+
+ this.dispatchEvent_(Grabber.EventType.DRAG_START);
+ e.detail.enableDrag = true;
+ this.dragging_ = true;
+ } else {
+ // Hasn't been grabbed - don't drag, just unpress
+ this.element_.classList.remove(Grabber.PRESSED_CLASS);
+ e.detail.enableDrag = false;
+ }
+};
+
+/**
+ * Invoked when a grabbed element is being dragged
+ * @param {!CustomEvent} e The TouchHandler event.
+ * @private
+ */
+Grabber.prototype.onDragMove_ = function(e) {
+ assert(this.grabbed_ && this.dragging_);
+
+ this.translateTo_(e.detail.dragDeltaX, e.detail.dragDeltaY);
+
+ var target = this.getCoveredElement_(e.detail);
+ if (target && target != this.lastEnter_) {
+ // Send the events
+ this.sendDragLeave_(e);
+ this.dispatchEventTo_(Grabber.EventType.DRAG_ENTER, target);
+ }
+ this.lastEnter_ = target;
+};
+
+/**
+ * Send DRAG_LEAVE to the element last sent a DRAG_ENTER if any.
+ * @param {!CustomEvent} e The event triggering this DRAG_LEAVE.
+ * @private
+ */
+Grabber.prototype.sendDragLeave_ = function(e) {
+ if (this.lastEnter_) {
+ this.dispatchEventTo_(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
+ */
+Grabber.prototype.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_;
arv (Not doing code reviews) 2011/03/10 19:25:59 +2 indentation
Rick Byers 2011/03/11 02:44:33 Done.
+};
+
+/**
+ * Get the element being covered by a given touch.
+ * @param {TouchHandler.EventDetail} touch The details of the touch event
+ * indicating the position to check.
arv (Not doing code reviews) 2011/03/10 19:25:59 indent 4 spaces
Rick Byers 2011/03/11 02:44:33 Done.
+ * @return {Element} The element under the touch or null.
+ * @private
+ */
+Grabber.prototype.getCoveredElement_ = function(touch) {
+ // Ensure the element being dragged doesn't get in the way
+ // It's unfortunate that there's not a better way to do this.
+ // This will probably cause a re-layout, but shouldn't cause flicker as
+ // repaint shouldn't happen in the middle of this function.
+ // We could also use zIndex, but that's more complicated and harder to
+ // reliably get the element out of the way.
+ var origDisplay = this.element_.style.display || '';
+ this.element_.style.display = 'none';
arv (Not doing code reviews) 2011/03/10 19:25:59 I would prefer pointerEvents = 'none' since that d
Rick Byers 2011/03/11 02:44:33 elementFromPoint does appear to be aware of pointe
+ var target = document.elementFromPoint(touch.clientX, touch.clientY);
+ this.element_.style.display = origDisplay;
+
+ return target;
+};
+
+/**
+ * @private
+ * @param {CustomEvent} e The TouchHandler event.
+ */
+Grabber.prototype.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;
+
+ // Try to determine what element is underneath us
+ var target = this.getCoveredElement_(e.detail);
+ if (target) {
+ this.dispatchEventTo_(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
+ */
+Grabber.prototype.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.dispatchEvent_(Grabber.EventType.DRAG_END);
+};
+
+/**
+ * @return {!Element} The element the grabber is attached to.
+ */
+Grabber.prototype.getElement = function() {
arv (Not doing code reviews) 2011/03/10 19:25:59 Use getters and setters for getters and setters G
Rick Byers 2011/03/11 02:44:33 Thanks, but JSCompiler doesn't yet support this ES
arv (Not doing code reviews) 2011/03/11 20:08:46 It is a such a relief to not have to be hamstrung
Rick Byers 2011/03/15 21:47:54 Ok. I agree we shouldn't be sacrificing good styl
+ return 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
+ */
+Grabber.prototype.dispatchEventTo_ = function(eventType, target) {
+ var event = document.createEvent('Event');
arv (Not doing code reviews) 2011/03/10 19:25:59 cr.dispatchSimpleEvent
Rick Byers 2011/03/11 02:44:33 See above.
arv (Not doing code reviews) 2011/03/11 20:08:46 Ah, I was confused by the overloaded term, sender.
Rick Byers 2011/03/15 21:47:54 Darn - I forgot that I added a 'sender' property m
+ event.initEvent(eventType, true, true);
+ event.sender = this;
+ target.dispatchEvent(event);
+};
+
+/**
+ * Send a Grabber event to the grabbed element
+ * @param {string} eventType The type of event to send.
+ * @private
+ */
+Grabber.prototype.dispatchEvent_ = function(eventType) {
+ this.dispatchEventTo_(eventType, this.element_);
+};
+

Powered by Google App Engine
This is Rietveld 408576698