1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * Slide mode displays a single image and has a set of controls to navigate
7 * between the images and to edit an image.
9 * @param {!HTMLElement} container Main container element.
10 * @param {!HTMLElement} content Content container element.
11 * @param {!HTMLElement} toolbar Toolbar element.
12 * @param {!ImageEditor.Prompt} prompt Prompt.
13 * @param {!ErrorBanner} errorBanner Error banner.
14 * @param {!cr.ui.ArrayDataModel} dataModel Data model.
15 * @param {!cr.ui.ListSelectionModel} selectionModel Selection model.
16 * @param {!MetadataModel} metadataModel
17 * @param {!ThumbnailModel} thumbnailModel
18 * @param {!Object} context Context.
19 * @param {!VolumeManager} volumeManager Volume manager.
20 * @param {function(function())} toggleMode Function to toggle the Gallery mode.
21 * @param {function(string):string} displayStringFunction String formatting
26 * @suppress {checkStructDictInheritance}
27 * @extends {cr.EventTarget}
29 function SlideMode(container
, content
, toolbar
, prompt
, errorBanner
, dataModel
,
30 selectionModel
, metadataModel
, thumbnailModel
, context
, volumeManager
,
31 toggleMode
, displayStringFunction
) {
33 * @type {!HTMLElement}
37 this.container_
= container
;
44 this.document_
= assert(container
.ownerDocument
);
47 * @type {!HTMLElement}
50 this.content
= content
;
53 * @type {!HTMLElement}
57 this.toolbar_
= toolbar
;
60 * @type {!ImageEditor.Prompt}
64 this.prompt_
= prompt
;
67 * @type {!ErrorBanner}
71 this.errorBanner_
= errorBanner
;
74 * @type {!cr.ui.ArrayDataModel}
78 this.dataModel_
= dataModel
;
81 * @type {!cr.ui.ListSelectionModel}
85 this.selectionModel_
= selectionModel
;
92 this.context_
= context
;
95 * @type {!VolumeManager}
99 this.volumeManager_
= volumeManager
;
102 * @type {function(function())}
106 this.toggleMode_
= toggleMode
;
109 * @type {function(string):string}
113 this.displayStringFunction_
= displayStringFunction
;
116 * @type {function(this:SlideMode)}
120 this.onSelectionBound_
= this.onSelection_
.bind(this);
123 * @type {function(this:SlideMode,!Event)}
127 this.onSpliceBound_
= this.onSplice_
.bind(this);
130 * Unique numeric key, incremented per each load attempt used to discard
131 * old attempts. This can happen especially when changing selection fast or
132 * Internet connection is slow.
137 this.currentUniqueKey_
= 0;
143 this.sequenceDirection_
= 0;
149 this.sequenceLength_
= 0;
152 * @type {Array.<number>}
155 this.savedSelection_
= null;
158 * @type {Gallery.Item}
161 this.displayedItem_
= null;
167 this.slideHint_
= null;
173 this.active_
= false;
179 this.leaveAfterSlideshow_
= false;
185 this.fullscreenBeforeSlideshow_
= false;
191 this.slideShowTimeout_
= null;
197 this.spinnerTimer_
= null;
199 window
.addEventListener('resize', this.onResize_
.bind(this));
201 // ----------------------------------------------------------------
202 // Initializes the UI.
205 * Container for displayed image.
206 * @type {!HTMLElement}
210 this.imageContainer_
= util
.createChild(queryRequiredElement(
211 this.document_
, '.content'), 'image-container');
212 this.imageContainer_
.addEventListener('click', this.onClick_
.bind(this));
214 this.document_
.addEventListener('click', this.onDocumentClick_
.bind(this));
217 * Overwrite options and info bubble.
218 * @type {!HTMLElement}
222 this.options_
= util
.createChild(queryRequiredElement(
223 this.toolbar_
, '.filename-spacer'), 'options');
226 * @type {!HTMLElement}
230 this.savedLabel_
= util
.createChild(this.options_
, 'saved');
231 this.savedLabel_
.textContent
= this.displayStringFunction_('GALLERY_SAVED');
234 * @type {!HTMLElement}
237 var overwriteOriginalBox
= util
.createChild(
238 this.options_
, 'overwrite-original');
241 * @type {!HTMLElement}
245 this.overwriteOriginal_
= util
.createChild(
246 overwriteOriginalBox
, '', 'input');
247 this.overwriteOriginal_
.type
= 'checkbox';
248 this.overwriteOriginal_
.id
= 'overwrite-checkbox';
249 chrome
.storage
.local
.get(SlideMode
.OVERWRITE_KEY
, function(values
) {
250 var value
= values
[SlideMode
.OVERWRITE_KEY
];
251 // Out-of-the box default is 'true'
252 this.overwriteOriginal_
.checked
=
253 (value
=== 'false' || value
=== false) ? false : true;
255 this.overwriteOriginal_
.addEventListener('click',
256 this.onOverwriteOriginalClick_
.bind(this));
259 * @type {!HTMLElement}
262 var overwriteLabel
= util
.createChild(overwriteOriginalBox
, '', 'label');
263 overwriteLabel
.textContent
=
264 this.displayStringFunction_('GALLERY_OVERWRITE_ORIGINAL');
265 overwriteLabel
.setAttribute('for', 'overwrite-checkbox');
268 * @type {!HTMLElement}
272 this.bubble_
= util
.createChild(this.toolbar_
, 'bubble');
273 this.bubble_
.hidden
= true;
276 * @type {!HTMLElement}
279 var bubbleContent
= util
.createChild(this.bubble_
);
280 bubbleContent
.innerHTML
= this.displayStringFunction_(
281 'GALLERY_OVERWRITE_BUBBLE');
283 util
.createChild(this.bubble_
, 'pointer bottom', 'span');
286 * @type {!HTMLElement}
289 var bubbleClose
= util
.createChild(this.bubble_
, 'close-x');
290 bubbleClose
.addEventListener('click', this.onCloseBubble_
.bind(this));
293 * Ribbon and related controls.
294 * @type {!HTMLElement}
298 this.arrowBox_
= util
.createChild(this.container_
, 'arrow-box');
301 * @type {!HTMLElement}
305 this.arrowLeft_
= util
.createChild(
306 this.arrowBox_
, 'arrow left tool dimmable');
307 this.arrowLeft_
.addEventListener('click',
308 this.advanceManually
.bind(this, -1));
309 util
.createChild(this.arrowLeft_
);
311 util
.createChild(this.arrowBox_
, 'arrow-spacer');
314 * @type {!HTMLElement}
318 this.arrowRight_
= util
.createChild(
319 this.arrowBox_
, 'arrow right tool dimmable');
320 this.arrowRight_
.addEventListener('click',
321 this.advanceManually
.bind(this, 1));
322 util
.createChild(this.arrowRight_
);
325 * @type {!HTMLElement}
329 this.ribbonSpacer_
= queryRequiredElement(this.toolbar_
, '.ribbon-spacer');
336 this.ribbon_
= new Ribbon(
337 this.document_
, this.dataModel_
, this.selectionModel_
, thumbnailModel
);
338 this.ribbonSpacer_
.appendChild(this.ribbon_
);
340 util
.createChild(this.container_
, 'spinner');
343 * @type {!HTMLElement}
346 var slideShowButton
= queryRequiredElement(this.toolbar_
, 'button.slideshow');
347 slideShowButton
.title
= this.displayStringFunction_('GALLERY_SLIDESHOW');
348 slideShowButton
.addEventListener('click',
349 this.startSlideshow
.bind(this, SlideMode
.SLIDESHOW_INTERVAL_FIRST
));
352 * @type {!HTMLElement}
355 var slideShowToolbar
= util
.createChild(
356 this.container_
, 'tool slideshow-toolbar');
357 util
.createChild(slideShowToolbar
, 'slideshow-play').
358 addEventListener('click', this.toggleSlideshowPause_
.bind(this));
359 util
.createChild(slideShowToolbar
, 'slideshow-end').
360 addEventListener('click', this.stopSlideshow_
.bind(this));
364 * @type {!HTMLElement}
368 this.editButton_
= queryRequiredElement(this.toolbar_
, 'button.edit');
369 this.editButton_
.title
= this.displayStringFunction_('GALLERY_EDIT');
370 this.editButton_
.disabled
= true; // Disabled by default.
371 this.editButton_
.addEventListener('click', this.toggleEditor
.bind(this));
374 * @type {!HTMLElement}
378 this.printButton_
= queryRequiredElement(this.toolbar_
, 'button.print');
379 this.printButton_
.title
= this.displayStringFunction_('GALLERY_PRINT');
380 this.printButton_
.disabled
= true; // Disabled by default.
381 this.printButton_
.addEventListener('click', this.print_
.bind(this));
384 * @type {!HTMLElement}
388 this.editBarSpacer_
= queryRequiredElement(this.toolbar_
, '.edit-bar-spacer');
391 * @type {!HTMLElement}
395 this.editBarMain_
= util
.createChild(this.editBarSpacer_
, 'edit-main');
398 * @type {!HTMLElement}
402 this.editBarMode_
= util
.createChild(this.container_
, 'edit-modal');
405 * @type {!HTMLElement}
409 this.editBarModeWrapper_
= util
.createChild(
410 this.editBarMode_
, 'edit-modal-wrapper dimmable');
411 this.editBarModeWrapper_
.hidden
= true;
414 * Objects supporting image display and editing.
419 this.viewport_
= new Viewport();
426 this.imageView_
= new ImageView(
427 this.imageContainer_
,
432 * @type {!ImageEditor}
436 this.editor_
= new ImageEditor(
441 root
: this.container_
,
442 image
: this.imageContainer_
,
443 toolbar
: this.editBarMain_
,
444 mode
: this.editBarModeWrapper_
446 SlideMode
.EDITOR_MODES
,
447 this.displayStringFunction_
,
448 this.onToolsVisibilityChanged_
.bind(this));
451 * @type {!TouchHandler}
455 this.touchHandlers_
= new TouchHandler(this.imageContainer_
, this);
459 * List of available editor modes.
460 * @type {!Array.<ImageEditor.Mode>}
463 SlideMode
.EDITOR_MODES
= [
464 new ImageEditor
.Mode
.InstantAutofix(),
465 new ImageEditor
.Mode
.Crop(),
466 new ImageEditor
.Mode
.Exposure(),
467 new ImageEditor
.Mode
.OneClick(
468 'rotate_left', 'GALLERY_ROTATE_LEFT', new Command
.Rotate(-1)),
469 new ImageEditor
.Mode
.OneClick(
470 'rotate_right', 'GALLERY_ROTATE_RIGHT', new Command
.Rotate(1))
474 * Map of the key identifier and offset delta.
475 * @enum {!Array.<number>})
478 SlideMode
.KEY_OFFSET_MAP
= {
486 * SlideMode extends cr.EventTarget.
488 SlideMode
.prototype.__proto__
= cr
.EventTarget
.prototype;
491 * @return {string} Mode name.
493 SlideMode
.prototype.getName = function() { return 'slide'; };
496 * @return {string} Mode title.
498 SlideMode
.prototype.getTitle = function() { return 'GALLERY_SLIDE'; };
501 * @return {!Viewport} Viewport.
503 SlideMode
.prototype.getViewport = function() { return this.viewport_
; };
506 * Load items, display the selected item.
507 * @param {ImageRect} zoomFromRect Rectangle for zoom effect.
508 * @param {function()} displayCallback Called when the image is displayed.
509 * @param {function()} loadCallback Called when the image is displayed.
511 SlideMode
.prototype.enter = function(
512 zoomFromRect
, displayCallback
, loadCallback
) {
513 this.sequenceDirection_
= 0;
514 this.sequenceLength_
= 0;
516 var loadDone = function(loadType
, delay
) {
519 this.selectionModel_
.addEventListener('change', this.onSelectionBound_
);
520 this.dataModel_
.addEventListener('splice', this.onSpliceBound_
);
522 ImageUtil
.setAttribute(this.arrowBox_
, 'active', this.getItemCount_() > 1);
523 this.ribbon_
.enable();
525 // Wait 1000ms after the animation is done, then prefetch the next image.
526 this.requestPrefetch(1, delay
+ 1000);
528 if (loadCallback
) loadCallback();
531 // The latest |leave| call might have left the image animating. Remove it.
534 new Promise(function(fulfill
) {
535 // If the items are empty, just show the error message.
536 if (this.getItemCount_() === 0) {
537 this.displayedItem_
= null;
538 //TODO(hirono) Show this message in the grid mode too.
539 this.errorBanner_
.show('GALLERY_NO_IMAGES');
544 // Remember the selection if it is empty or multiple. It will be restored
545 // in |leave| if the user did not changing the selection manually.
546 var currentSelection
= this.selectionModel_
.selectedIndexes
;
547 if (currentSelection
.length
=== 1)
548 this.savedSelection_
= null;
550 this.savedSelection_
= currentSelection
;
552 // Ensure valid single selection.
553 // Note that the SlideMode object is not listening to selection change yet.
554 this.select(Math
.max(0, this.getSelectedIndex()));
556 // Show the selected item ASAP, then complete the initialization
557 // (loading the ribbon thumbnails can take some time).
558 var selectedItem
= this.getSelectedItem();
559 this.displayedItem_
= selectedItem
;
561 // Load the image of the item.
565 this.imageView_
.createZoomEffect(zoomFromRect
) :
566 new ImageView
.Effect
.None(),
568 function(loadType
, delay
) {
571 }.bind(this)).then(function(delay
) {
572 // Turn the mode active.
574 ImageUtil
.setAttribute(this.arrowBox_
, 'active', this.getItemCount_() > 1);
575 this.ribbon_
.enable();
577 // Register handlers.
578 this.selectionModel_
.addEventListener('change', this.onSelectionBound_
);
579 this.dataModel_
.addEventListener('splice', this.onSpliceBound_
);
580 this.touchHandlers_
.enabled
= true;
582 // Wait 1000ms after the animation is done, then prefetch the next image.
583 this.requestPrefetch(1, delay
+ 1000);
585 // Call load callback.
588 }.bind(this)).catch(function(error
) {
589 console
.error(error
.stack
, error
);
595 * @param {ImageRect} zoomToRect Rectangle for zoom effect.
596 * @param {function()} callback Called when the image is committed and
597 * the zoom-out animation has started.
599 SlideMode
.prototype.leave = function(zoomToRect
, callback
) {
600 var commitDone = function() {
602 this.stopSlideshow_();
603 ImageUtil
.setAttribute(this.arrowBox_
, 'active', false);
604 this.selectionModel_
.removeEventListener(
605 'change', this.onSelectionBound_
);
606 this.dataModel_
.removeEventListener('splice', this.onSpliceBound_
);
607 this.ribbon_
.disable();
608 this.active_
= false;
609 if (this.savedSelection_
)
610 this.selectionModel_
.selectedIndexes
= this.savedSelection_
;
611 this.unloadImage_(zoomToRect
);
615 this.viewport_
.resetView();
616 if (this.getItemCount_() === 0) {
617 this.errorBanner_
.clear();
620 this.commitItem_(commitDone
);
623 // Disable the slide-mode only buttons when leaving.
624 this.editButton_
.disabled
= true;
625 this.printButton_
.disabled
= true;
627 // Disable touch operation.
628 this.touchHandlers_
.enabled
= false;
633 * Execute an action when the editor is not busy.
635 * @param {function()} action Function to execute.
637 SlideMode
.prototype.executeWhenReady = function(action
) {
638 this.editor_
.executeWhenReady(action
);
642 * @return {boolean} True if the mode has active tools (that should not fade).
644 SlideMode
.prototype.hasActiveTool = function() {
645 return this.isEditing();
649 * @return {number} Item count.
652 SlideMode
.prototype.getItemCount_ = function() {
653 return this.dataModel_
.length
;
657 * @param {number} index Index.
658 * @return {Gallery.Item} Item.
660 SlideMode
.prototype.getItem = function(index
) {
662 /** @type {(Gallery.Item|undefined)} */ (this.dataModel_
.item(index
));
663 return item
=== undefined ? null : item
;
667 * @return {number} Selected index.
669 SlideMode
.prototype.getSelectedIndex = function() {
670 return this.selectionModel_
.selectedIndex
;
674 * @return {ImageRect} Screen rectangle of the selected image.
676 SlideMode
.prototype.getSelectedImageRect = function() {
677 if (this.getSelectedIndex() < 0)
680 return this.viewport_
.getImageBoundsOnScreen();
684 * @return {Gallery.Item} Selected item.
686 SlideMode
.prototype.getSelectedItem = function() {
687 return this.getItem(this.getSelectedIndex());
691 * Toggles the full screen mode.
694 SlideMode
.prototype.toggleFullScreen_ = function() {
695 util
.toggleFullScreen(this.context_
.appWindow
,
696 !util
.isFullScreen(this.context_
.appWindow
));
700 * Selection change handler.
702 * Commits the current image and displays the newly selected image.
705 SlideMode
.prototype.onSelection_ = function() {
706 if (this.selectionModel_
.selectedIndexes
.length
=== 0)
707 return; // Ignore temporary empty selection.
709 // Forget the saved selection if the user changed the selection manually.
710 if (!this.isSlideshowOn_())
711 this.savedSelection_
= null;
713 if (this.getSelectedItem() === this.displayedItem_
)
714 return; // Do not reselect.
716 this.commitItem_(this.loadSelectedItem_
.bind(this));
720 * Handles changes in tools visibility, and if the header is dimmed, then
721 * requests disabling the draggable app region.
725 SlideMode
.prototype.onToolsVisibilityChanged_ = function() {
726 var headerDimmed
= queryRequiredElement(this.document_
, '.header')
727 .hasAttribute('dimmed');
728 this.context_
.onAppRegionChanged(!headerDimmed
);
732 * Change the selection.
734 * @param {number} index New selected index.
735 * @param {number=} opt_slideHint Slide animation direction (-1|1).
737 SlideMode
.prototype.select = function(index
, opt_slideHint
) {
738 this.slideHint_
= opt_slideHint
|| null;
739 this.selectionModel_
.selectedIndex
= index
;
740 this.selectionModel_
.leadIndex
= index
;
744 * Load the selected item.
748 SlideMode
.prototype.loadSelectedItem_ = function() {
749 var slideHint
= this.slideHint_
;
750 this.slideHint_
= null;
752 if (this.getSelectedItem() === this.displayedItem_
)
753 return; // Do not reselect.
755 var index
= this.getSelectedIndex();
756 var displayedIndex
= this.dataModel_
.indexOf(this.displayedItem_
);
758 slideHint
|| (displayedIndex
> 0 ? index
- displayedIndex
: 1);
760 if (Math
.abs(step
) != 1) {
761 // Long leap, the sequence is broken, we have no good prefetch candidate.
762 this.sequenceDirection_
= 0;
763 this.sequenceLength_
= 0;
764 } else if (this.sequenceDirection_
=== step
) {
765 // Keeping going in sequence.
766 this.sequenceLength_
++;
768 // Reversed the direction. Reset the counter.
769 this.sequenceDirection_
= step
;
770 this.sequenceLength_
= 1;
773 this.displayedItem_
= this.getSelectedItem();
774 var selectedItem
= assertInstanceof(this.getSelectedItem(), Gallery
.Item
);
776 if (this.sequenceLength_
<= 1) {
777 // We have just broke the sequence. Touch the current image so that it stays
778 // in the cache longer.
779 this.imageView_
.prefetch(selectedItem
);
782 function shouldPrefetch(loadType
, step
, sequenceLength
) {
783 // Never prefetch when selecting out of sequence.
784 if (Math
.abs(step
) != 1)
787 // Always prefetch if the previous load was from cache.
788 if (loadType
=== ImageView
.LoadType
.CACHED_FULL
)
791 // Prefetch if we have been going in the same direction for long enough.
792 return sequenceLength
>= 3;
795 this.currentUniqueKey_
++;
796 var selectedUniqueKey
= this.currentUniqueKey_
;
798 // Discard, since another load has been invoked after this one.
799 if (selectedUniqueKey
!= this.currentUniqueKey_
)
804 new ImageView
.Effect
.Slide(step
, this.isSlideshowPlaying_()),
805 function() {} /* no displayCallback */,
806 function(loadType
, delay
) {
807 // Discard, since another load has been invoked after this one.
808 if (selectedUniqueKey
!= this.currentUniqueKey_
)
810 if (shouldPrefetch(loadType
, step
, this.sequenceLength_
))
811 this.requestPrefetch(step
, delay
);
812 if (this.isSlideshowPlaying_())
813 this.scheduleNextSlide_();
818 * Unload the current image.
820 * @param {ImageRect=} opt_zoomToRect Rectangle for zoom effect.
823 SlideMode
.prototype.unloadImage_ = function(opt_zoomToRect
) {
824 this.imageView_
.unload(opt_zoomToRect
);
828 * Data model 'splice' event handler.
829 * @param {!Event} event Event.
833 SlideMode
.prototype.onSplice_ = function(event
) {
834 ImageUtil
.setAttribute(this.arrowBox_
, 'active', this.getItemCount_() > 1);
836 // Splice invalidates saved indices, drop the saved selection.
837 this.savedSelection_
= null;
839 if (event
.removed
.length
!= 1)
842 // Delay the selection to let the ribbon splice handler work first.
843 setTimeout(function() {
844 var displayedItemNotRemvoed
= event
.removed
.every(function(item
) {
845 return item
!== this.displayedItem_
;
847 if (displayedItemNotRemvoed
)
850 if (event
.index
< this.dataModel_
.length
) {
851 // There is the next item, select it.
852 // The next item is now at the same index as the removed one, so we need
853 // to correct displayIndex_ so that loadSelectedItem_ does not think
854 // we are re-selecting the same item (and does right-to-left slide-in
856 nextIndex
= event
.index
;
857 } else if (this.dataModel_
.length
) {
858 // Removed item is the rightmost, but there are more items.
859 nextIndex
= event
.index
- 1; // Select the new last index.
861 // No items left. Unload the image, disable edit and print button, and
863 this.commitItem_(function() {
865 this.printButton_
.disabled
= true;
866 this.editButton_
.disabled
= true;
867 this.errorBanner_
.show('GALLERY_NO_IMAGES');
871 // To force to dispatch a selection change event, clear selection before.
872 this.selectionModel_
.clear();
873 this.select(nextIndex
);
878 * @param {number} direction -1 for left, 1 for right.
879 * @return {number} Next index in the given direction, with wrapping.
882 SlideMode
.prototype.getNextSelectedIndex_ = function(direction
) {
883 function advance(index
, limit
) {
884 index
+= (direction
> 0 ? 1 : -1);
892 // If the saved selection is multiple the Slideshow should cycle through
893 // the saved selection.
894 if (this.isSlideshowOn_() &&
895 this.savedSelection_
&& this.savedSelection_
.length
> 1) {
896 var pos
= advance(this.savedSelection_
.indexOf(this.getSelectedIndex()),
897 this.savedSelection_
.length
);
898 return this.savedSelection_
[pos
];
900 return advance(this.getSelectedIndex(), this.getItemCount_());
905 * Advance the selection based on the pressed key ID.
906 * @param {string} keyID Key identifier.
908 SlideMode
.prototype.advanceWithKeyboard = function(keyID
) {
909 var prev
= (keyID
=== 'Up' ||
911 keyID
=== 'MediaPreviousTrack');
912 this.advanceManually(prev
? -1 : 1);
916 * Advance the selection as a result of a user action (as opposed to an
917 * automatic change in the slideshow mode).
918 * @param {number} direction -1 for left, 1 for right.
920 SlideMode
.prototype.advanceManually = function(direction
) {
921 if (this.isSlideshowPlaying_())
922 this.pauseSlideshow_();
923 cr
.dispatchSimpleEvent(this, 'useraction');
924 this.selectNext(direction
);
928 * Select the next item.
929 * @param {number} direction -1 for left, 1 for right.
931 SlideMode
.prototype.selectNext = function(direction
) {
932 this.select(this.getNextSelectedIndex_(direction
), direction
);
936 * Select the first item.
938 SlideMode
.prototype.selectFirst = function() {
943 * Select the last item.
945 SlideMode
.prototype.selectLast = function() {
946 this.select(this.getItemCount_() - 1);
952 * Load and display an item.
954 * @param {!Gallery.Item} item Item.
955 * @param {!ImageView.Effect} effect Transition effect object.
956 * @param {function()} displayCallback Called when the image is displayed
957 * (which can happen before the image load due to caching).
958 * @param {function(number, number)} loadCallback Called when the image is fully
962 SlideMode
.prototype.loadItem_ = function(
963 item
, effect
, displayCallback
, loadCallback
) {
964 this.showSpinner_(true);
966 var loadDone
= this.itemLoaded_
.bind(this, item
, loadCallback
);
968 var displayDone = function() {
969 cr
.dispatchSimpleEvent(this, 'image-displayed');
973 this.editor_
.openSession(
976 this.saveCurrentImage_
.bind(this, item
),
982 * A callback function when the editor opens a editing session for an image.
983 * @param {!Gallery.Item} item Gallery item.
984 * @param {function(number, number)} loadCallback Called when the image is fully
986 * @param {number} loadType Load type.
987 * @param {number} delay Delay.
988 * @param {*=} opt_error Error.
991 SlideMode
.prototype.itemLoaded_ = function(
992 item
, loadCallback
, loadType
, delay
, opt_error
) {
993 var entry
= item
.getEntry();
995 this.showSpinner_(false);
996 if (loadType
=== ImageView
.LoadType
.ERROR
) {
997 // if we have a specific error, then display it
999 this.errorBanner_
.show(/** @type {string} */ (opt_error
));
1001 // otherwise try to infer general error
1002 this.errorBanner_
.show('GALLERY_IMAGE_ERROR');
1004 } else if (loadType
=== ImageView
.LoadType
.OFFLINE
) {
1005 this.errorBanner_
.show('GALLERY_IMAGE_OFFLINE');
1008 ImageUtil
.metrics
.recordUserAction(ImageUtil
.getMetricName('View'));
1010 var toMillions = function(number
) {
1011 return Math
.round(number
/ (1000 * 1000));
1014 ImageUtil
.metrics
.recordSmallCount(ImageUtil
.getMetricName('Size.MB'),
1015 toMillions(item
.getMetadataItem().size
));
1017 var canvas
= this.imageView_
.getCanvas();
1018 ImageUtil
.metrics
.recordSmallCount(ImageUtil
.getMetricName('Size.MPix'),
1019 toMillions(canvas
.width
* canvas
.height
));
1021 var extIndex
= entry
.name
.lastIndexOf('.');
1022 var ext
= extIndex
< 0 ? '' :
1023 entry
.name
.substr(extIndex
+ 1).toLowerCase();
1024 if (ext
=== 'jpeg') ext
= 'jpg';
1025 ImageUtil
.metrics
.recordEnum(
1026 ImageUtil
.getMetricName('FileType'), ext
, ImageUtil
.FILE_TYPES
);
1028 // Enable or disable buttons for editing and printing.
1030 this.editButton_
.disabled
= true;
1031 this.printButton_
.disabled
= true;
1033 this.editButton_
.disabled
= false;
1034 this.printButton_
.disabled
= false;
1037 // For once edited image, disallow the 'overwrite' setting change.
1038 ImageUtil
.setAttribute(this.options_
, 'saved',
1039 !this.getSelectedItem().isOriginal());
1041 chrome
.storage
.local
.get(SlideMode
.OVERWRITE_BUBBLE_KEY
,
1043 var times
= values
[SlideMode
.OVERWRITE_BUBBLE_KEY
] || 0;
1044 if (times
< SlideMode
.OVERWRITE_BUBBLE_MAX_TIMES
) {
1045 this.bubble_
.hidden
= false;
1046 if (this.isEditing()) {
1048 items
[SlideMode
.OVERWRITE_BUBBLE_KEY
] = times
+ 1;
1049 chrome
.storage
.local
.set(items
);
1054 loadCallback(loadType
, delay
);
1058 * Commit changes to the current item and reset all messages/indicators.
1060 * @param {function()} callback Callback.
1063 SlideMode
.prototype.commitItem_ = function(callback
) {
1064 this.showSpinner_(false);
1065 this.errorBanner_
.clear();
1066 this.editor_
.getPrompt().hide();
1067 this.editor_
.closeSession(callback
);
1071 * Request a prefetch for the next image.
1073 * @param {number} direction -1 or 1.
1074 * @param {number} delay Delay in ms. Used to prevent the CPU-heavy image
1075 * loading from disrupting the animation that might be still in progress.
1077 SlideMode
.prototype.requestPrefetch = function(direction
, delay
) {
1078 if (this.getItemCount_() <= 1) return;
1080 var index
= this.getNextSelectedIndex_(direction
);
1081 this.imageView_
.prefetch(assert(this.getItem(index
)), delay
);
1087 * Click handler for the image container.
1089 * @param {!Event} event Mouse click event.
1092 SlideMode
.prototype.onClick_ = function(event
) {
1096 * Click handler for the entire document.
1097 * @param {!Event} event Mouse click event.
1100 SlideMode
.prototype.onDocumentClick_ = function(event
) {
1101 // Events created in fakeMouseClick in test util don't pass this test.
1102 if (!window
.IN_TEST
)
1103 event
= assertInstanceof(event
, MouseEvent
);
1105 var targetElement
= assertInstanceof(event
.target
, HTMLElement
);
1106 // Close the bubble if clicked outside of it and if it is visible.
1107 if (!this.bubble_
.contains(targetElement
) &&
1108 !this.editButton_
.contains(targetElement
) &&
1109 !this.arrowLeft_
.contains(targetElement
) &&
1110 !this.arrowRight_
.contains(targetElement
) &&
1111 !this.bubble_
.hidden
) {
1112 this.bubble_
.hidden
= true;
1119 * @param {!Event} event Event.
1120 * @return {boolean} True if handled.
1122 SlideMode
.prototype.onKeyDown = function(event
) {
1123 var keyID
= util
.getKeyModifiers(event
) + event
.keyIdentifier
;
1125 if (this.isSlideshowOn_()) {
1127 case 'U+001B': // Escape exits the slideshow.
1129 this.stopSlideshow_(event
);
1132 case 'U+0020': // Space pauses/resumes the slideshow.
1133 case 'MediaPlayPause':
1134 this.toggleSlideshowPause_();
1141 case 'MediaNextTrack':
1142 case 'MediaPreviousTrack':
1143 this.advanceWithKeyboard(keyID
);
1146 return true; // Consume all keystrokes in the slideshow mode.
1149 if (this.isEditing() && this.editor_
.onKeyDown(event
))
1153 case 'Ctrl-U+0050': // Ctrl+'p' prints the current image.
1154 if (!this.printButton_
.disabled
)
1158 case 'U+0045': // 'e' toggles the editor.
1159 if (!this.editButton_
.disabled
)
1160 this.toggleEditor(event
);
1163 case 'U+001B': // Escape
1164 if (this.isEditing()) {
1165 this.toggleEditor(event
);
1166 } else if (this.viewport_
.isZoomed()) {
1167 this.viewport_
.resetView();
1168 this.touchHandlers_
.stopOperation();
1169 this.imageView_
.applyViewportChange();
1171 return false; // Not handled.
1185 if (!this.isEditing() && this.viewport_
.isZoomed()) {
1186 var delta
= SlideMode
.KEY_OFFSET_MAP
[keyID
];
1187 this.viewport_
.setOffset(
1188 ~~(this.viewport_
.getOffsetX() +
1189 delta
[0] * this.viewport_
.getZoom()),
1190 ~~(this.viewport_
.getOffsetY() +
1191 delta
[1] * this.viewport_
.getZoom()));
1192 this.touchHandlers_
.stopOperation();
1193 this.imageView_
.applyViewportChange();
1195 this.advanceWithKeyboard(keyID
);
1198 case 'MediaNextTrack':
1199 case 'MediaPreviousTrack':
1200 this.advanceWithKeyboard(keyID
);
1203 case 'Ctrl-U+00BB': // Ctrl+'=' zoom in.
1204 if (!this.isEditing()) {
1205 this.viewport_
.zoomIn();
1206 this.touchHandlers_
.stopOperation();
1207 this.imageView_
.applyViewportChange();
1211 case 'Ctrl-U+00BD': // Ctrl+'-' zoom out.
1212 if (!this.isEditing()) {
1213 this.viewport_
.zoomOut();
1214 this.touchHandlers_
.stopOperation();
1215 this.imageView_
.applyViewportChange();
1219 case 'Ctrl-U+0030': // Ctrl+'0' zoom reset.
1220 if (!this.isEditing()) {
1221 this.viewport_
.setZoom(1.0);
1222 this.touchHandlers_
.stopOperation();
1223 this.imageView_
.applyViewportChange();
1235 SlideMode
.prototype.onResize_ = function() {
1236 this.viewport_
.setScreenSize(
1237 this.container_
.clientWidth
, this.container_
.clientHeight
);
1238 this.touchHandlers_
.stopOperation();
1239 this.editor_
.getBuffer().draw();
1243 * Update thumbnails.
1245 SlideMode
.prototype.updateThumbnails = function() {
1246 this.ribbon_
.reset();
1248 this.ribbon_
.redraw();
1254 * Save the current image to a file.
1256 * @param {!Gallery.Item} item Item to save the image.
1257 * @param {function()} callback Callback.
1260 SlideMode
.prototype.saveCurrentImage_ = function(item
, callback
) {
1261 this.showSpinner_(true);
1263 var savedPromise
= this.dataModel_
.saveItem(
1264 this.volumeManager_
,
1266 this.imageView_
.getCanvas(),
1267 this.shouldOverwriteOriginal_());
1269 savedPromise
.catch(function(error
) {
1270 // TODO(hirono): Implement write error handling.
1271 // Until then pretend that the save succeeded.
1272 console
.error(error
.stack
|| error
);
1273 }).then(function() {
1274 this.showSpinner_(false);
1275 this.flashSavedLabel_();
1277 // Allow changing the 'Overwrite original' setting only if the user
1278 // used Undo to restore the original image AND it is not a copy.
1279 // Otherwise lock the setting in its current state.
1280 var mayChangeOverwrite
= !this.editor_
.canUndo() && item
.isOriginal();
1281 ImageUtil
.setAttribute(this.options_
, 'saved', !mayChangeOverwrite
);
1283 // Record UMA for the first edit.
1284 if (this.imageView_
.getContentRevision() === 1)
1285 ImageUtil
.metrics
.recordUserAction(ImageUtil
.getMetricName('Edit'));
1288 cr
.dispatchSimpleEvent(this, 'image-saved');
1289 }.bind(this)).catch(function(error
) {
1290 console
.error(error
.stack
|| error
);
1295 * Flash 'Saved' label briefly to indicate that the image has been saved.
1298 SlideMode
.prototype.flashSavedLabel_ = function() {
1299 var setLabelHighlighted
=
1300 ImageUtil
.setAttribute
.bind(null, this.savedLabel_
, 'highlighted');
1301 setTimeout(setLabelHighlighted
.bind(null, true), 0);
1302 setTimeout(setLabelHighlighted
.bind(null, false), 300);
1306 * Local storage key for the 'Overwrite original' setting.
1309 SlideMode
.OVERWRITE_KEY
= 'gallery-overwrite-original';
1312 * Local storage key for the number of times that
1313 * the overwrite info bubble has been displayed.
1316 SlideMode
.OVERWRITE_BUBBLE_KEY
= 'gallery-overwrite-bubble';
1319 * Max number that the overwrite info bubble is shown.
1322 SlideMode
.OVERWRITE_BUBBLE_MAX_TIMES
= 5;
1325 * @return {boolean} True if 'Overwrite original' is set.
1328 SlideMode
.prototype.shouldOverwriteOriginal_ = function() {
1329 return this.overwriteOriginal_
.checked
;
1333 * 'Overwrite original' checkbox handler.
1334 * @param {!Event} event Event.
1337 SlideMode
.prototype.onOverwriteOriginalClick_ = function(event
) {
1339 items
[SlideMode
.OVERWRITE_KEY
] = event
.target
.checked
;
1340 chrome
.storage
.local
.set(items
);
1344 * Overwrite info bubble close handler.
1347 SlideMode
.prototype.onCloseBubble_ = function() {
1348 this.bubble_
.hidden
= true;
1350 items
[SlideMode
.OVERWRITE_BUBBLE_KEY
] =
1351 SlideMode
.OVERWRITE_BUBBLE_MAX_TIMES
;
1352 chrome
.storage
.local
.set(items
);
1358 * Slideshow interval in ms.
1360 SlideMode
.SLIDESHOW_INTERVAL
= 5000;
1363 * First slideshow interval in ms. It should be shorter so that the user
1364 * is not guessing whether the button worked.
1366 SlideMode
.SLIDESHOW_INTERVAL_FIRST
= 1000;
1369 * Empirically determined duration of the fullscreen toggle animation.
1371 SlideMode
.FULLSCREEN_TOGGLE_DELAY
= 500;
1374 * @return {boolean} True if the slideshow is on.
1377 SlideMode
.prototype.isSlideshowOn_ = function() {
1378 return this.container_
.hasAttribute('slideshow');
1382 * Starts the slideshow.
1383 * @param {number=} opt_interval First interval in ms.
1384 * @param {Event=} opt_event Event.
1386 SlideMode
.prototype.startSlideshow = function(opt_interval
, opt_event
) {
1388 this.viewport_
.resetView();
1389 this.imageView_
.applyViewportChange();
1391 // Disable touch operation.
1392 this.touchHandlers_
.enabled
= false;
1394 // Set the attribute early to prevent the toolbar from flashing when
1395 // the slideshow is being started from the mosaic view.
1396 this.container_
.setAttribute('slideshow', 'playing');
1399 this.stopEditing_();
1401 // We are in the Mosaic mode. Toggle the mode but remember to return.
1402 this.leaveAfterSlideshow_
= true;
1404 // Wait until the zoom animation from the mosaic mode is done.
1405 var startSlideshowAfterTransition = function() {
1406 setTimeout(function() {
1407 this.startSlideshow
.call(this, SlideMode
.SLIDESHOW_INTERVAL
, opt_event
);
1408 }.bind(this), ImageView
.MODE_TRANSITION_DURATION
);
1410 this.toggleMode_(startSlideshowAfterTransition
);
1414 if (opt_event
) // Caused by user action, notify the Gallery.
1415 cr
.dispatchSimpleEvent(this, 'useraction');
1417 this.fullscreenBeforeSlideshow_
= util
.isFullScreen(this.context_
.appWindow
);
1418 if (!this.fullscreenBeforeSlideshow_
) {
1419 this.toggleFullScreen_();
1420 opt_interval
= (opt_interval
|| SlideMode
.SLIDESHOW_INTERVAL
) +
1421 SlideMode
.FULLSCREEN_TOGGLE_DELAY
;
1424 this.resumeSlideshow_(opt_interval
);
1428 * Stops the slideshow.
1429 * @param {Event=} opt_event Event.
1432 SlideMode
.prototype.stopSlideshow_ = function(opt_event
) {
1433 if (!this.isSlideshowOn_())
1436 if (opt_event
) // Caused by user action, notify the Gallery.
1437 cr
.dispatchSimpleEvent(this, 'useraction');
1439 this.pauseSlideshow_();
1440 this.container_
.removeAttribute('slideshow');
1442 // Do not restore fullscreen if we exited fullscreen while in slideshow.
1443 var fullscreen
= util
.isFullScreen(this.context_
.appWindow
);
1444 var toggleModeDelay
= 0;
1445 if (!this.fullscreenBeforeSlideshow_
&& fullscreen
) {
1446 this.toggleFullScreen_();
1447 toggleModeDelay
= SlideMode
.FULLSCREEN_TOGGLE_DELAY
;
1449 if (this.leaveAfterSlideshow_
) {
1450 this.leaveAfterSlideshow_
= false;
1451 setTimeout(this.toggleMode_
.bind(this), toggleModeDelay
);
1454 // Re-enable touch operation.
1455 this.touchHandlers_
.enabled
= true;
1459 * @return {boolean} True if the slideshow is playing (not paused).
1462 SlideMode
.prototype.isSlideshowPlaying_ = function() {
1463 return this.container_
.getAttribute('slideshow') === 'playing';
1467 * Pauses/resumes the slideshow.
1470 SlideMode
.prototype.toggleSlideshowPause_ = function() {
1471 cr
.dispatchSimpleEvent(this, 'useraction'); // Show the tools.
1472 if (this.isSlideshowPlaying_()) {
1473 this.pauseSlideshow_();
1475 this.resumeSlideshow_(SlideMode
.SLIDESHOW_INTERVAL_FIRST
);
1480 * @param {number=} opt_interval Slideshow interval in ms.
1483 SlideMode
.prototype.scheduleNextSlide_ = function(opt_interval
) {
1484 console
.assert(this.isSlideshowPlaying_(), 'Inconsistent slideshow state');
1486 if (this.slideShowTimeout_
)
1487 clearTimeout(this.slideShowTimeout_
);
1489 this.slideShowTimeout_
= setTimeout(function() {
1490 this.slideShowTimeout_
= null;
1492 }.bind(this), opt_interval
|| SlideMode
.SLIDESHOW_INTERVAL
);
1496 * Resumes the slideshow.
1497 * @param {number=} opt_interval Slideshow interval in ms.
1500 SlideMode
.prototype.resumeSlideshow_ = function(opt_interval
) {
1501 this.container_
.setAttribute('slideshow', 'playing');
1502 this.scheduleNextSlide_(opt_interval
);
1506 * Pauses the slideshow.
1509 SlideMode
.prototype.pauseSlideshow_ = function() {
1510 this.container_
.setAttribute('slideshow', 'paused');
1511 if (this.slideShowTimeout_
) {
1512 clearTimeout(this.slideShowTimeout_
);
1513 this.slideShowTimeout_
= null;
1518 * @return {boolean} True if the editor is active.
1520 SlideMode
.prototype.isEditing = function() {
1521 return this.container_
.hasAttribute('editing');
1528 SlideMode
.prototype.stopEditing_ = function() {
1529 if (this.isEditing())
1530 this.toggleEditor();
1534 * Activate/deactivate editor.
1535 * @param {Event=} opt_event Event.
1537 SlideMode
.prototype.toggleEditor = function(opt_event
) {
1538 if (opt_event
) // Caused by user action, notify the Gallery.
1539 cr
.dispatchSimpleEvent(this, 'useraction');
1541 if (!this.active_
) {
1542 this.toggleMode_(this.toggleEditor
.bind(this));
1546 this.stopSlideshow_();
1548 ImageUtil
.setAttribute(this.container_
, 'editing', !this.isEditing());
1550 if (this.isEditing()) { // isEditing has just been flipped to a new value.
1552 this.viewport_
.resetView();
1553 this.imageView_
.applyViewportChange();
1554 if (this.context_
.readonlyDirName
) {
1555 this.editor_
.getPrompt().showAt(
1556 'top', 'GALLERY_READONLY_WARNING', 0, this.context_
.readonlyDirName
);
1558 this.touchHandlers_
.enabled
= false;
1560 this.editor_
.getPrompt().hide();
1561 this.editor_
.leaveModeGently();
1562 this.touchHandlers_
.enabled
= true;
1567 * Prints the current item.
1570 SlideMode
.prototype.print_ = function() {
1571 cr
.dispatchSimpleEvent(this, 'useraction');
1576 * Shows/hides the busy spinner.
1578 * @param {boolean} on True if show, false if hide.
1581 SlideMode
.prototype.showSpinner_ = function(on
) {
1582 if (this.spinnerTimer_
) {
1583 clearTimeout(this.spinnerTimer_
);
1584 this.spinnerTimer_
= null;
1588 this.spinnerTimer_
= setTimeout(function() {
1589 this.spinnerTimer_
= null;
1590 ImageUtil
.setAttribute(this.container_
, 'spinner', true);
1591 }.bind(this), 1000);
1593 ImageUtil
.setAttribute(this.container_
, 'spinner', false);
1598 * Apply the change of viewport.
1600 SlideMode
.prototype.applyViewportChange = function() {
1601 this.imageView_
.applyViewportChange();
1605 * Touch handlers of the slide mode.
1606 * @param {!Element} targetElement Event source.
1607 * @param {!SlideMode} slideMode Slide mode to be operated by the handler.
1611 function TouchHandler(targetElement
, slideMode
) {
1618 this.targetElement_
= targetElement
;
1621 * Target of touch operations.
1622 * @type {!SlideMode}
1626 this.slideMode_
= slideMode
;
1629 * Flag to enable/disable touch operation.
1633 this.enabled_
= true;
1636 * Whether it is in a touch operation that is started from targetElement or
1641 this.touchStarted_
= false;
1644 * The swipe action that should happen only once in an operation is already
1652 * Event on beginning of the current gesture.
1653 * The variable is updated when the number of touch finger changed.
1654 * @type {TouchEvent}
1657 this.gestureStartEvent_
= null;
1660 * Rotation value on beginning of the current gesture.
1664 this.gestureStartRotation_
= 0;
1668 * @type {TouchEvent}
1671 this.lastEvent_
= null;
1674 * Zoom value just after last touch event.
1678 this.lastZoom_
= 1.0;
1680 targetElement
.addEventListener('touchstart', this.onTouchStart_
.bind(this));
1681 var onTouchEventBound
= this.onTouchEvent_
.bind(this);
1682 targetElement
.ownerDocument
.addEventListener('touchmove', onTouchEventBound
);
1683 targetElement
.ownerDocument
.addEventListener('touchend', onTouchEventBound
);
1685 targetElement
.addEventListener('mousewheel', this.onMouseWheel_
.bind(this));
1689 * If the user touched the image and moved the finger more than SWIPE_THRESHOLD
1690 * horizontally it's considered as a swipe gesture (change the current image).
1694 TouchHandler
.SWIPE_THRESHOLD
= 100;
1697 * Rotation threshold in degrees.
1701 TouchHandler
.ROTATION_THRESHOLD
= 25;
1704 * Obtains distance between fingers.
1705 * @param {!TouchEvent} event Touch event. It should include more than two
1707 * @return {number} Distance between touch[0] and touch[1].
1709 TouchHandler
.getDistance = function(event
) {
1710 var touch1
= event
.touches
[0];
1711 var touch2
= event
.touches
[1];
1712 var dx
= touch1
.clientX
- touch2
.clientX
;
1713 var dy
= touch1
.clientY
- touch2
.clientY
;
1714 return Math
.sqrt(dx
* dx
+ dy
* dy
);
1718 * Obtains the degrees of the pinch twist angle.
1719 * @param {!TouchEvent} event1 Start touch event. It should include more than
1721 * @param {!TouchEvent} event2 Current touch event. It should include more than
1723 * @return {number} Degrees of the pinch twist angle.
1725 TouchHandler
.getTwistAngle = function(event1
, event2
) {
1726 var dx1
= event1
.touches
[1].clientX
- event1
.touches
[0].clientX
;
1727 var dy1
= event1
.touches
[1].clientY
- event1
.touches
[0].clientY
;
1728 var dx2
= event2
.touches
[1].clientX
- event2
.touches
[0].clientX
;
1729 var dy2
= event2
.touches
[1].clientY
- event2
.touches
[0].clientY
;
1730 var innerProduct
= dx1
* dx2
+ dy1
* dy2
; // |v1| * |v2| * cos(t) = x / r
1731 var outerProduct
= dx1
* dy2
- dy1
* dx2
; // |v1| * |v2| * sin(t) = y / r
1732 return Math
.atan2(outerProduct
, innerProduct
) * 180 / Math
.PI
; // atan(y / x)
1735 TouchHandler
.prototype = /** @struct */ {
1737 * @param {boolean} flag New value.
1740 this.enabled_
= flag
;
1742 this.stopOperation();
1747 * Stops the current touch operation.
1749 TouchHandler
.prototype.stopOperation = function() {
1750 this.touchStarted_
= false;
1752 this.gestureStartEvent_
= null;
1753 this.lastEvent_
= null;
1754 this.lastZoom_
= 1.0;
1758 * Handles touch start events.
1759 * @param {!Event} event Touch event.
1762 TouchHandler
.prototype.onTouchStart_ = function(event
) {
1763 event
= assertInstanceof(event
, TouchEvent
);
1764 if (this.enabled_
&& event
.touches
.length
=== 1)
1765 this.touchStarted_
= true;
1769 * Handles touch move and touch end events.
1770 * @param {!Event} event Touch event.
1773 TouchHandler
.prototype.onTouchEvent_ = function(event
) {
1774 event
= assertInstanceof(event
, TouchEvent
);
1775 // Check if the current touch operation started from the target element or
1777 if (!this.touchStarted_
)
1780 // Check if the current touch operation ends with the event.
1781 if (event
.touches
.length
=== 0) {
1782 this.stopOperation();
1786 // Check if a new gesture started or not.
1787 var viewport
= this.slideMode_
.getViewport();
1788 if (!this.lastEvent_
||
1789 this.lastEvent_
.touches
.length
!== event
.touches
.length
) {
1790 if (event
.touches
.length
=== 2 ||
1791 event
.touches
.length
=== 1) {
1792 this.gestureStartEvent_
= event
;
1793 this.gestureStartRotation_
= viewport
.getRotation();
1794 this.lastEvent_
= event
;
1795 this.lastZoom_
= viewport
.getZoom();
1797 this.gestureStartEvent_
= null;
1798 this.gestureStartRotation_
= 0;
1799 this.lastEvent_
= null;
1800 this.lastZoom_
= 1.0;
1805 // Handle the gesture movement.
1806 switch (event
.touches
.length
) {
1808 if (viewport
.isZoomed()) {
1809 // Scrolling an image by swipe.
1810 var dx
= event
.touches
[0].screenX
- this.lastEvent_
.touches
[0].screenX
;
1811 var dy
= event
.touches
[0].screenY
- this.lastEvent_
.touches
[0].screenY
;
1813 viewport
.getOffsetX() + dx
, viewport
.getOffsetY() + dy
);
1814 this.slideMode_
.applyViewportChange();
1816 // Traversing images by swipe.
1820 event
.touches
[0].clientX
-
1821 this.gestureStartEvent_
.touches
[0].clientX
;
1822 if (dx
> TouchHandler
.SWIPE_THRESHOLD
) {
1823 this.slideMode_
.advanceManually(-1);
1825 } else if (dx
< -TouchHandler
.SWIPE_THRESHOLD
) {
1826 this.slideMode_
.advanceManually(1);
1834 var distance1
= TouchHandler
.getDistance(this.lastEvent_
);
1835 var distance2
= TouchHandler
.getDistance(event
);
1836 if (distance1
=== 0)
1838 var zoom
= distance2
/ distance1
* this.lastZoom_
;
1839 viewport
.setZoom(zoom
);
1842 assert(this.gestureStartEvent_
);
1843 var angle
= TouchHandler
.getTwistAngle(this.gestureStartEvent_
, event
);
1844 if (angle
> TouchHandler
.ROTATION_THRESHOLD
)
1845 viewport
.setRotation(this.gestureStartRotation_
+ 1);
1846 else if (angle
< -TouchHandler
.ROTATION_THRESHOLD
)
1847 viewport
.setRotation(this.gestureStartRotation_
- 1);
1849 viewport
.setRotation(this.gestureStartRotation_
);
1850 this.slideMode_
.applyViewportChange();
1854 // Update the last event.
1855 this.lastEvent_
= event
;
1856 this.lastZoom_
= viewport
.getZoom();
1860 * Handles mouse wheel events.
1861 * @param {!Event} event Wheel event.
1864 TouchHandler
.prototype.onMouseWheel_ = function(event
) {
1865 var event
= assertInstanceof(event
, MouseEvent
);
1866 var viewport
= this.slideMode_
.getViewport();
1867 if (!this.enabled_
|| !viewport
.isZoomed())
1869 this.stopOperation();
1871 viewport
.getOffsetX() + event
.wheelDeltaX
,
1872 viewport
.getOffsetY() + event
.wheelDeltaY
);
1873 this.slideMode_
.applyViewportChange();