Index: ui/file_manager/gallery/js/slide_mode.js |
diff --git a/ui/file_manager/gallery/js/slide_mode.js b/ui/file_manager/gallery/js/slide_mode.js |
index fdc77f222e6956d1cfaf8f4ff30f92525918213e..10fe725687ee8fb40204e00b002721383c11ab11 100644 |
--- a/ui/file_manager/gallery/js/slide_mode.js |
+++ b/ui/file_manager/gallery/js/slide_mode.js |
@@ -91,6 +91,11 @@ SlideMode.prototype.getName = function() { return 'slide'; }; |
SlideMode.prototype.getTitle = function() { return 'GALLERY_SLIDE'; }; |
/** |
+ * @return {Viewport} Viewport. |
+ */ |
+SlideMode.prototype.getViewport = function() { return this.viewport_; }; |
+ |
+/** |
* Initialize the listeners. |
* @private |
*/ |
@@ -232,8 +237,7 @@ SlideMode.prototype.initDom_ = function() { |
this.displayStringFunction_, |
this.onToolsVisibilityChanged_.bind(this)); |
- this.editor_.getBuffer().addOverlay( |
- new SwipeOverlay(this.advanceManually.bind(this))); |
+ this.touchHandlers_ = new TouchHandlers(this.imageContainer_, this); |
}; |
/** |
@@ -311,6 +315,7 @@ SlideMode.prototype.enter = function( |
this.selectionModel_.addEventListener('change', this.onSelectionBound_); |
this.dataModel_.addEventListener('splice', this.onSpliceBound_); |
this.dataModel_.addEventListener('content', this.onContentBound_); |
+ this.touchHandlers_.enable = true; |
// Wait 1000ms after the animation is done, then prefetch the next image. |
this.requestPrefetch(1, delay + 1000); |
@@ -357,6 +362,9 @@ SlideMode.prototype.leave = function(zoomToRect, callback) { |
// Disable the slide-mode only buttons when leaving. |
this.editButton_.setAttribute('disabled', ''); |
this.printButton_.setAttribute('disabled', ''); |
+ |
+ // Disable touch operation. |
+ this.touchHandlers_.enable = false; |
}; |
@@ -860,8 +868,9 @@ SlideMode.prototype.onKeyDown = function(event) { |
case 'U+001B': // Escape |
if (this.isEditing()) { |
this.toggleEditor(event); |
- } else if (this.viewport_.getZoomIndex() !== 0) { |
+ } else if (this.viewport_.isZooming()) { |
this.viewport_.resetView(); |
+ this.touchHandlers_.stopOperation(); |
this.imageView_.applyViewportChange(); |
} else { |
return false; // Not handled. |
@@ -878,14 +887,14 @@ SlideMode.prototype.onKeyDown = function(event) { |
case 'Down': |
case 'Left': |
case 'Right': |
- if (!this.isEditing() && this.viewport_.getZoomIndex() !== 0) { |
+ if (!this.isEditing() && this.viewport_.isZooming()) { |
var delta = SlideMode.KEY_OFFSET_MAP[keyID]; |
this.viewport_.setOffset( |
~~(this.viewport_.getOffsetX() + |
delta[0] * this.viewport_.getZoom()), |
~~(this.viewport_.getOffsetY() + |
- delta[1] * this.viewport_.getZoom()), |
- true); |
+ delta[1] * this.viewport_.getZoom())); |
+ this.touchHandlers_.stopOperation(); |
this.imageView_.applyViewportChange(); |
} else { |
this.advanceWithKeyboard(keyID); |
@@ -898,21 +907,24 @@ SlideMode.prototype.onKeyDown = function(event) { |
case 'Ctrl-U+00BB': // Ctrl+'=' zoom in. |
if (!this.isEditing()) { |
- this.viewport_.setZoomIndex(this.viewport_.getZoomIndex() + 1); |
+ this.viewport_.zoomIn(); |
+ this.touchHandlers_.stopOperation(); |
this.imageView_.applyViewportChange(); |
} |
break; |
case 'Ctrl-U+00BD': // Ctrl+'-' zoom out. |
if (!this.isEditing()) { |
- this.viewport_.setZoomIndex(this.viewport_.getZoomIndex() - 1); |
+ this.viewport_.zoomOut(); |
+ this.touchHandlers_.stopOperation(); |
this.imageView_.applyViewportChange(); |
} |
break; |
case 'Ctrl-U+0030': // Ctrl+'0' zoom reset. |
if (!this.isEditing()) { |
- this.viewport_.resetView(); |
+ this.viewport_.setZoom(1.0); |
+ this.touchHandlers_.stopOperation(); |
this.imageView_.applyViewportChange(); |
} |
break; |
@@ -928,6 +940,7 @@ SlideMode.prototype.onKeyDown = function(event) { |
SlideMode.prototype.onResize_ = function() { |
this.viewport_.setScreenSize( |
this.container_.clientWidth, this.container_.clientHeight); |
+ this.touchHandlers_.stopOperation(); |
this.editor_.getBuffer().draw(); |
}; |
@@ -1077,7 +1090,7 @@ SlideMode.prototype.isSlideshowOn_ = function() { |
}; |
/** |
- * Start the slideshow. |
+ * Starts the slideshow. |
* @param {number=} opt_interval First interval in ms. |
* @param {Event=} opt_event Event. |
*/ |
@@ -1086,6 +1099,9 @@ SlideMode.prototype.startSlideshow = function(opt_interval, opt_event) { |
this.viewport_.resetView(); |
this.imageView_.applyViewportChange(); |
+ // Disable touch operation. |
+ this.touchHandlers_.enable = false; |
+ |
// Set the attribute early to prevent the toolbar from flashing when |
// the slideshow is being started from the mosaic view. |
this.container_.setAttribute('slideshow', 'playing'); |
@@ -1116,7 +1132,7 @@ SlideMode.prototype.startSlideshow = function(opt_interval, opt_event) { |
}; |
/** |
- * Stop the slideshow. |
+ * Stops the slideshow. |
* @param {Event=} opt_event Event. |
* @private |
*/ |
@@ -1141,6 +1157,9 @@ SlideMode.prototype.stopSlideshow_ = function(opt_event) { |
this.leaveAfterSlideshow_ = false; |
setTimeout(this.toggleMode_.bind(this), toggleModeDelay); |
} |
+ |
+ // Re-enable touch operation. |
+ this.touchHandlers_.enable = true; |
}; |
/** |
@@ -1152,7 +1171,7 @@ SlideMode.prototype.isSlideshowPlaying_ = function() { |
}; |
/** |
- * Pause/resume the slideshow. |
+ * Pauses/resumes the slideshow. |
* @private |
*/ |
SlideMode.prototype.toggleSlideshowPause_ = function() { |
@@ -1182,7 +1201,7 @@ SlideMode.prototype.scheduleNextSlide_ = function(opt_interval) { |
}; |
/** |
- * Resume the slideshow. |
+ * Resumes the slideshow. |
* @param {number=} opt_interval Slideshow interval in ms. |
* @private |
*/ |
@@ -1192,7 +1211,7 @@ SlideMode.prototype.resumeSlideshow_ = function(opt_interval) { |
}; |
/** |
- * Pause the slideshow. |
+ * Pauses the slideshow. |
* @private |
*/ |
SlideMode.prototype.pauseSlideshow_ = function() { |
@@ -1211,7 +1230,7 @@ SlideMode.prototype.isEditing = function() { |
}; |
/** |
- * Stop editing. |
+ * Stops editing. |
* @private |
*/ |
SlideMode.prototype.stopEditing_ = function() { |
@@ -1244,9 +1263,11 @@ SlideMode.prototype.toggleEditor = function(opt_event) { |
this.editor_.getPrompt().showAt( |
'top', 'GALLERY_READONLY_WARNING', 0, this.context_.readonlyDirName); |
} |
+ this.touchHandlers_.enable = false; |
} else { |
this.editor_.getPrompt().hide(); |
this.editor_.leaveModeGently(); |
+ this.touchHandlers_.enable = true; |
} |
}; |
@@ -1260,7 +1281,7 @@ SlideMode.prototype.print_ = function() { |
}; |
/** |
- * Display the error banner. |
+ * Displays the error banner. |
* @param {string} message Message. |
* @private |
*/ |
@@ -1272,7 +1293,7 @@ SlideMode.prototype.showErrorBanner_ = function(message) { |
}; |
/** |
- * Show/hide the busy spinner. |
+ * Shows/hides the busy spinner. |
* |
* @param {boolean} on True if show, false if hide. |
* @private |
@@ -1294,45 +1315,202 @@ SlideMode.prototype.showSpinner_ = function(on) { |
}; |
/** |
- * Overlay that handles swipe gestures. Changes to the next or previous file. |
- * @param {function(number)} callback A callback accepting the swipe direction |
- * (1 means left, -1 right). |
- * @constructor |
- * @implements {ImageBuffer.Overlay} |
+ * Apply the change of viewport. |
*/ |
-function SwipeOverlay(callback) { |
- this.callback_ = callback; |
+SlideMode.prototype.applyViewportChange = function() { |
+ this.imageView_.applyViewportChange(); |
+}; |
+ |
+/** |
mtomasz
2014/07/23 08:15:08
@param, @constructor missing
hirono
2014/07/23 09:27:23
Done.
|
+ * Touch handlers of the slide mode. |
+ */ |
+function TouchHandlers(targetElement, slideMode) { |
mtomasz
2014/07/23 08:15:08
I'm wondering if the plural naming is fine. How ab
hirono
2014/07/23 09:27:23
Done.
|
+ /** |
+ * Event source. |
+ * @type {DOMElement} |
+ */ |
+ this.targetElement_ = targetElement; |
+ |
+ /** |
+ * Target of touch operations. |
+ * @type {SlideMode} |
+ * @private |
+ */ |
+ this.slideMode_ = slideMode; |
+ |
+ /** |
+ * Flag to enable/disable touch operation. |
+ */ |
+ this.enable_ = true; |
mtomasz
2014/07/23 08:15:08
nit: enabled?
hirono
2014/07/23 09:27:23
Done.
|
+ |
+ /** |
+ * Whether it is in a touch operation that is started from targetElement or |
+ * not. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.inTouch_ = false; |
mtomasz
2014/07/23 08:15:08
nit: How about simply touchStarted?
hirono
2014/07/23 09:27:23
Done.
|
+ |
+ /** |
+ * The swipe action that should happen only once in an operation is already |
+ * done or not. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.done_ = false; |
+ |
+ /** |
+ * Event on beginning of the current gesture. |
+ * The variable is updated when the number of touch finger changed. |
+ * @type {TouchEvent} |
+ * @private |
+ */ |
+ this.gestureStartEvent_ = null; |
+ |
+ /** |
+ * Last touch event. |
+ * @type {TouchEvent} |
+ * @private |
+ */ |
+ this.lastEvent_ = null; |
+ |
+ /** |
+ * Zoom value just after last touch event. |
+ * @type {number} |
+ * @private |
+ */ |
+ this.lastZoom_ = 1.0; |
+ |
+ var onTouchEventBound = this.onTouchEvent_.bind(this); |
+ targetElement.addEventListener('touchstart', onTouchEventBound); |
+ targetElement.ownerDocument.addEventListener('touchmove', onTouchEventBound); |
+ targetElement.ownerDocument.addEventListener('touchend', onTouchEventBound); |
} |
/** |
- * Inherit ImageBuffer.Overlay. |
+ * If the user touched the image and moved the finger more than SWIPE_THRESHOLD |
+ * horizontally it's considered as a swipe gesture (change the current image). |
+ * @type {number} |
+ * @const |
*/ |
-SwipeOverlay.prototype.__proto__ = ImageBuffer.Overlay.prototype; |
+TouchHandlers.SWIPE_THRESHOLD = 100; |
/** |
- * @param {number} x X pointer position. |
- * @param {number} y Y pointer position. |
- * @param {boolean} touch True if dragging caused by touch. |
- * @return {function} The closure to call on drag. |
+ * Obtains distance between fingers. |
+ * @param {TouchEvent} event Touch event. It should include more than two |
+ * touches. |
+ * @return {boolean} Distance between touch[0] and touch[1]. |
*/ |
-SwipeOverlay.prototype.getDragHandler = function(x, y, touch) { |
- if (!touch) |
- return null; |
- var origin = x; |
- var done = false; |
- return function(x, y) { |
- if (!done && origin - x > SwipeOverlay.SWIPE_THRESHOLD) { |
- this.callback_(1); |
- done = true; |
- } else if (!done && x - origin > SwipeOverlay.SWIPE_THRESHOLD) { |
- this.callback_(-1); |
- done = true; |
- } |
- }.bind(this); |
+TouchHandlers.getDistance = function(event) { |
+ var touch1 = event.touches[0]; |
+ var touch2 = event.touches[1]; |
+ var dx = touch1.clientX - touch2.clientX; |
+ var dy = touch1.clientY - touch2.clientY; |
+ return Math.sqrt(dx * dx + dy * dy); |
+}; |
+ |
+TouchHandlers.prototype = { |
+ /** |
+ * @param {boolean} flag New value. |
+ */ |
+ set enable(flag) { |
+ this.enable_ = flag; |
+ if (!this.enable_) |
+ this.stopOperation(); |
+ } |
}; |
/** |
- * If the user touched the image and moved the finger more than SWIPE_THRESHOLD |
- * horizontally it's considered as a swipe gesture (change the current image). |
+ * Stops the current touch operation. |
+ */ |
+TouchHandlers.prototype.stopOperation = function() { |
+ this.inTouch_ = false; |
+ this.done_ = false; |
+ this.gestureStartEvent_ = null; |
+ this.lastEvent_ = null; |
+ this.lastZoom_ = 1.0; |
+}; |
+ |
+/** |
+ * @param {event} event Touch event. |
*/ |
-SwipeOverlay.SWIPE_THRESHOLD = 100; |
+TouchHandlers.prototype.onTouchEvent_ = function(event) { |
+ // Check if the current touch operation started from the target element or |
+ // not. |
+ if (!this.inTouch_) { |
+ // Check if a touch operation starts with the event. |
+ if (this.enable_ && |
+ event.type === 'touchstart' && |
mtomasz
2014/07/23 08:15:08
nit: Why not splitting this to a separate method?
hirono
2014/07/23 09:27:23
Done.
|
+ event.touches.length === 1) { |
+ this.inTouch_ = true; |
+ } else { |
+ return; |
+ } |
+ } else { |
+ // Check if the current touch operaiton ends with the event. |
mtomasz
2014/07/23 08:15:08
typo: operation
hirono
2014/07/23 09:27:23
Done.
|
+ if (event.touches.length === 0) { |
+ this.stopOperation(); |
+ return; |
+ } |
+ } |
+ |
+ // Check if a new gesture started or not. |
+ if (!this.lastEvent_ || |
+ this.lastEvent_.touches.length !== event.touches.length) { |
+ if (event.touches.length === 2 || |
+ event.touches.length === 1) { |
+ this.gestureStartEvent_ = event; |
+ this.lastEvent_ = event; |
+ this.lastZoom_ = this.slideMode_.getViewport().getZoom(); |
+ } else { |
+ this.gestureStartEvent_ = null; |
+ this.lastEvent_ = null; |
+ this.lastZoom_ = 1.0; |
+ } |
+ return; |
+ } |
+ |
+ // Handle the gesture movement. |
+ var viewport = this.slideMode_.getViewport(); |
+ switch (event.touches.length) { |
+ case 1: |
+ if (viewport.isZooming()) { |
+ // Scrolling an image by swipe. |
+ var dx = event.touches[0].screenX - this.lastEvent_.touches[0].screenX; |
+ var dy = event.touches[0].screenY - this.lastEvent_.touches[0].screenY; |
+ viewport.setOffset( |
+ viewport.getOffsetX() + dx, viewport.getOffsetY() + dy); |
+ this.slideMode_.applyViewportChange(); |
+ } else { |
+ // Traversing images by swipe. |
+ if (this.done_) |
+ break; |
+ var dx = |
+ event.touches[0].clientX - |
+ this.gestureStartEvent_.touches[0].clientX; |
+ if (dx > TouchHandlers.SWIPE_THRESHOLD) { |
+ this.slideMode_.advanceManually(-1); |
+ this.done_ = true; |
+ } else if (dx < -TouchHandlers.SWIPE_THRESHOLD) { |
+ this.slideMode_.advanceManually(1); |
+ this.done_ = true; |
+ } |
+ } |
+ break; |
+ |
+ case 2: |
+ // Pinch zoom. |
+ var distance1 = TouchHandlers.getDistance(this.lastEvent_); |
+ var distance2 = TouchHandlers.getDistance(event); |
+ if (distance1 == 0.0) |
mtomasz
2014/07/23 08:15:08
nit: Simply === 0?
hirono
2014/07/23 09:27:23
Done.
|
+ break; |
+ var zoom = distance2 / distance1 * this.lastZoom_; |
+ viewport.setZoom(zoom); |
+ this.slideMode_.applyViewportChange(); |
+ break; |
+ } |
+ |
+ // Update the last event. |
+ this.lastEvent_ = event; |
+ this.lastZoom_ = viewport.getZoom(); |
+}; |