| Index: third_party/google_input_tools/src/chrome/os/inputview/elements/content/swipeview.js
|
| diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/swipeview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/swipeview.js
|
| index e341c6e925f100f04359393d2b94c0700c7e58f0..43836e4eec452450fa8973a7c1856a9825b8366a 100644
|
| --- a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/swipeview.js
|
| +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/swipeview.js
|
| @@ -15,7 +15,13 @@ goog.provide('i18n.input.chrome.inputview.elements.content.SwipeView');
|
|
|
| goog.require('goog.dom.TagName');
|
| goog.require('goog.dom.classlist');
|
| +goog.require('goog.events');
|
| +goog.require('goog.fx.Animation');
|
| +goog.require('goog.fx.dom.FadeOut');
|
| +goog.require('goog.fx.dom.PredefinedEffect');
|
| +goog.require('goog.fx.easing');
|
| goog.require('goog.style');
|
| +goog.require('i18n.input.chrome.Statistics');
|
| goog.require('i18n.input.chrome.inputview.Css');
|
| goog.require('i18n.input.chrome.inputview.SwipeDirection');
|
| goog.require('i18n.input.chrome.inputview.elements.Element');
|
| @@ -27,11 +33,14 @@ goog.require('i18n.input.chrome.inputview.util');
|
| goog.require('i18n.input.chrome.message.ContextType');
|
|
|
| goog.scope(function() {
|
| +var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView;
|
| +var Container = i18n.input.chrome.inputview.KeyboardContainer;
|
| var ContextType = i18n.input.chrome.message.ContextType;
|
| var Css = i18n.input.chrome.inputview.Css;
|
| var ElementType = i18n.input.chrome.inputview.elements.ElementType;
|
| var EventType = i18n.input.chrome.inputview.events.EventType;
|
| var KeyCodes = i18n.input.chrome.inputview.events.KeyCodes;
|
| +var SwipeDirection = i18n.input.chrome.inputview.SwipeDirection;
|
| var content = i18n.input.chrome.inputview.elements.content;
|
| var util = i18n.input.chrome.inputview.util;
|
|
|
| @@ -41,12 +50,13 @@ var util = i18n.input.chrome.inputview.util;
|
| * The view used to display the selection and deletion swipe tracks.
|
| *
|
| * @param {!i18n.input.chrome.inputview.Adapter} adapter .
|
| + * @param {!CandidateView} candidateView .
|
| * @param {goog.events.EventTarget=} opt_eventTarget The parent event target.
|
| * @constructor
|
| * @extends {i18n.input.chrome.inputview.elements.Element}
|
| */
|
| i18n.input.chrome.inputview.elements.content.SwipeView = function(
|
| - adapter, opt_eventTarget) {
|
| + adapter, candidateView, opt_eventTarget) {
|
| i18n.input.chrome.inputview.elements.content.SwipeView.base(
|
| this, 'constructor', '', ElementType.SWIPE_VIEW, opt_eventTarget);
|
|
|
| @@ -58,6 +68,13 @@ i18n.input.chrome.inputview.elements.content.SwipeView = function(
|
| this.adapter_ = adapter;
|
|
|
| /**
|
| + * The candidate view.
|
| + *
|
| + * @private {!CandidateView}
|
| + */
|
| + this.candidateView_ = candidateView;
|
| +
|
| + /**
|
| * The swipe elements.
|
| *
|
| * @private {!Array<!Element>}
|
| @@ -113,6 +130,20 @@ i18n.input.chrome.inputview.elements.content.SwipeView = function(
|
| this.coverElement_;
|
|
|
| /**
|
| + * The finger tracker affordance.
|
| + *
|
| + * @private {!Element}
|
| + */
|
| + this.fingerTracker_;
|
| +
|
| + /**
|
| + * The ripple element.
|
| + *
|
| + * @private {!Element}
|
| + */
|
| + this.ripple_;
|
| +
|
| + /**
|
| * The index of the alternative element which is highlighted.
|
| *
|
| * @private {number}
|
| @@ -122,7 +153,7 @@ i18n.input.chrome.inputview.elements.content.SwipeView = function(
|
| /**
|
| * The key which triggered this view to be shown.
|
| *
|
| - * @type {!i18n.input.chrome.inputview.elements.content.SoftKey}
|
| + * @type {i18n.input.chrome.inputview.elements.content.SoftKey|undefined}
|
| */
|
| this.triggeredBy;
|
|
|
| @@ -140,6 +171,75 @@ i18n.input.chrome.inputview.elements.content.SwipeView = function(
|
| */
|
| this.armed_ = false;
|
|
|
| + /**
|
| + * Whether to handle swipe events.
|
| + *
|
| + * @type {boolean}
|
| + */
|
| + this.enabled = false;
|
| +
|
| + /**
|
| + * Whether the current keyset supports swipe editing.
|
| + *
|
| + * @private {boolean}
|
| + */
|
| + this.isKeysetSupported_ = false;
|
| +
|
| + /**
|
| + * Fade animation for the finger affordance.
|
| + *
|
| + * @private {!goog.fx.dom.PredefinedEffect}
|
| + */
|
| + this.fadeAnimation_;
|
| +
|
| + /**
|
| + * Scale animation for the finger affordance.
|
| + *
|
| + * @private {!goog.fx.dom.PredefinedEffect}
|
| + */
|
| + this.scaleAnimation_;
|
| +
|
| + /**
|
| + * The statistics object for recording metrics values.
|
| + *
|
| + * @type {!i18n.input.chrome.Statistics}
|
| + * @private
|
| + */
|
| + this.statistics_ = i18n.input.chrome.Statistics.getInstance();
|
| +
|
| + /**
|
| + * Total number of forward movements per swipe. This is relative to the track
|
| + * and so swiping left on a left-to-right track, or swiping right on a
|
| + * left-to-right track.
|
| + *
|
| + * @private {number}
|
| + */
|
| + this.forwardMoves_ = 0;
|
| +
|
| + /**
|
| + * Total number of backward movements per swipe. This is relative to the track
|
| + * and so swiping right on a left-to-right track, or swiping left on a
|
| + * left-to-right track.
|
| + *
|
| + * @private {number}
|
| + */
|
| + this.backwardMoves_ = 0;
|
| +
|
| +
|
| + /**
|
| + * Whether the current track is selection or deletion.
|
| + *
|
| + * @private {boolean}
|
| + */
|
| + this.isSelection_ = true;
|
| +
|
| +
|
| + /**
|
| + * Triggering event identifier.
|
| + *
|
| + * @private {number|undefined}
|
| + */
|
| + this.eventIdentifier_;
|
| };
|
| goog.inherits(i18n.input.chrome.inputview.elements.content.SwipeView,
|
| i18n.input.chrome.inputview.elements.Element);
|
| @@ -152,7 +252,7 @@ var SwipeView = i18n.input.chrome.inputview.elements.content.SwipeView;
|
| * @private {number}
|
| * @const
|
| */
|
| -SwipeView.LENGTH_ = 7;
|
| +SwipeView.LENGTH_ = 15;
|
|
|
|
|
| /**
|
| @@ -171,7 +271,7 @@ SwipeView.INVALID_INDEX_ = -1;
|
| * @private {number}
|
| * @const
|
| */
|
| -SwipeView.FINGER_DISTANCE_TO_CANCEL_SWIPE_ = 20;
|
| +SwipeView.FINGER_DISTANCE_TO_CANCEL_SWIPE_ = 100;
|
|
|
|
|
| /**
|
| @@ -180,34 +280,83 @@ SwipeView.FINGER_DISTANCE_TO_CANCEL_SWIPE_ = 20;
|
| * @private {number}
|
| * @const
|
| */
|
| -SwipeView.SEGMENT_WIDTH_ = 70;
|
| +SwipeView.SEGMENT_WIDTH_ = 40;
|
| +
|
| +
|
| +/**
|
| + * The maximum surrounding text length that's provided.
|
| + *
|
| + * @private {number}
|
| + * @const
|
| + */
|
| +SwipeView.MAX_SURROUNDING_TEXT_LENGTH_ = 100;
|
| +
|
| +
|
| +/**
|
| + * The string representation of  .
|
| + *
|
| + * @private {string}
|
| + * @const
|
| + */
|
| +SwipeView.NBSP_CHAR_ = String.fromCharCode(160);
|
|
|
|
|
| /**
|
| - * The width of a large track segment.
|
| + * The width of the finger affordance.
|
| *
|
| * @private {number}
|
| * @const
|
| */
|
| -SwipeView.LARGE_SEGMENT_WIDTH_ = 100;
|
| +SwipeView.FINGER_TRACKER_WIDTH_ = 64;
|
|
|
|
|
| /**
|
| - * The maximum surrounding text length that's provided.
|
| + * The initial width of the ripple.
|
| *
|
| * @private {number}
|
| * @const
|
| */
|
| -SwipeView.MAX_SURROUNDING_TEXT_LENGTH_ = 100;
|
| +SwipeView.RIPPLE_WIDTH_ = 22;
|
|
|
|
|
| /**
|
| - * The string representation of  .
|
| + * The ripple scale animation duration in ms.
|
| *
|
| - * @private {string}
|
| + * @private {number}
|
| * @const
|
| */
|
| -SwipeView.NBSP_CHAR_ = String.fromCharCode(160);
|
| +SwipeView.SCALE_ANIMATION_TIME_ = 220;
|
| +
|
| +
|
| +/**
|
| + * The ripple scale factor.
|
| + *
|
| + * @private {number}
|
| + * @const
|
| + */
|
| +SwipeView.RIPPLE_SCALE_FACTOR_ = 24;
|
| +
|
| +
|
| +/**
|
| + * The ripple fade animation duration in ms.
|
| + *
|
| + * @private {number}
|
| + * @const
|
| + */
|
| +SwipeView.FADE_ANIMATION_TIME_ = 200;
|
| +
|
| +
|
| +/**
|
| + * Tooltips to display in the candidate window during gesture editing.
|
| + *
|
| + * @enum {string}
|
| + */
|
| +SwipeView.Tooltip = {
|
| + SELECTION: chrome.i18n.getMessage('SWIPE_SELECTION_TOOLTIP'),
|
| + DELETION: chrome.i18n.getMessage('SWIPE_DELETION_TOOLTIP'),
|
| + RESTORATION: chrome.i18n.getMessage('SWIPE_RESTORATION_TOOLTIP')
|
| +};
|
| +var Tooltip = SwipeView.Tooltip;
|
|
|
|
|
| /**
|
| @@ -234,13 +383,15 @@ SwipeView.prototype.onSurroundingTextChanged_ = function(e) {
|
| this.surroundingTextFocus_ = 0;
|
| return;
|
| }
|
| -
|
| this.surroundingTextAnchor_ = e.anchor;
|
| this.surroundingTextFocus_ = e.focus;
|
| -
|
| var text = e.text || '';
|
| var oldText = this.surroundingText_;
|
| var diff = '';
|
| + if (oldText == text) {
|
| + console.error('Duplicate surrounding text event.');
|
| + return;
|
| + }
|
| if (util.isLetterDelete(oldText, text)) {
|
| diff = oldText.slice(-1);
|
| // Check if the transformation from oldtext to text was a single letter being
|
| @@ -293,10 +444,10 @@ SwipeView.prototype.onSurroundingTextChanged_ = function(e) {
|
| SwipeView.prototype.swipeToDelete_ = function(e) {
|
| // Cache whether we were tracking.
|
| var alreadyTracking = this.tracking_;
|
| - var changed = this.highlightItem(e.x, e.y);
|
| - var direction = e.direction;
|
| + var previousIndex = this.getHighlightedIndex();
|
| + var direction = this.swipeOnTrack(e.x, e.y);
|
| // Did not move segments.
|
| - if (!changed) {
|
| + if (!direction) {
|
| // First gesture.
|
| if (!alreadyTracking) {
|
| // All previous deletions count as one now.
|
| @@ -304,40 +455,65 @@ SwipeView.prototype.swipeToDelete_ = function(e) {
|
| var word = this.deletedWords_.join('');
|
| this.deletedWords_ = [word];
|
| // Swiped right, cancel the deletion.
|
| - if (direction & i18n.input.chrome.inputview.SwipeDirection.RIGHT) {
|
| + if (e.direction & SwipeDirection.RIGHT) {
|
| word = this.deletedWords_.pop();
|
| if (word) {
|
| this.adapter_.commitText(word);
|
| }
|
| + } else {
|
| + // Change the tooltip to show restoration instructions.
|
| + this.candidateView_.showTooltip(Tooltip.RESTORATION);
|
| }
|
| }
|
| return;
|
| }
|
| -
|
| - if (direction & i18n.input.chrome.inputview.SwipeDirection.LEFT) {
|
| - this.adapter_.sendKeyDownAndUpEvent(
|
| - '\u0008', KeyCodes.BACKSPACE, undefined, undefined, {
|
| - ctrl: true,
|
| - shift: false
|
| - });
|
| - } else if (direction & i18n.input.chrome.inputview.SwipeDirection.RIGHT) {
|
| - var word = this.deletedWords_.pop();
|
| - if (word) {
|
| - this.adapter_.commitText(word);
|
| + // Always show restoration tooltip after the trackIndex changes.
|
| + this.candidateView_.showTooltip(Tooltip.RESTORATION);
|
| + // Some finger swipes jump tracks, compensate for this.
|
| + var delta = Math.abs(this.getHighlightedIndex() - previousIndex);
|
| + if (direction & SwipeDirection.LEFT) {
|
| + this.forwardMoves_ += delta;
|
| + for (var i = 0; i < delta; i++) {
|
| + this.adapter_.sendKeyDownAndUpEvent(
|
| + '\u0008', KeyCodes.BACKSPACE, undefined, undefined, {
|
| + ctrl: true,
|
| + shift: false
|
| + });
|
| }
|
| - // Restore text we deleted before the track came up, but part of the
|
| - // same gesture.
|
| - if (this.isAtOrigin()) {
|
| - word = this.deletedWords_.pop();
|
| + } else if (direction & SwipeDirection.RIGHT) {
|
| + this.backwardMoves_ += delta;
|
| + for (var i = 0; i < delta; i++) {
|
| + var word = this.deletedWords_.pop();
|
| if (word) {
|
| this.adapter_.commitText(word);
|
| }
|
| + // Restore text we deleted before the track came up, but part of the
|
| + // same gesture.
|
| + if (this.isAtOrigin()) {
|
| + word = this.deletedWords_.pop();
|
| + if (word) {
|
| + this.adapter_.commitText(word);
|
| + }
|
| + break;
|
| + }
|
| }
|
| + } else {
|
| + console.error('Unexpected swipe direction: ' + direction);
|
| }
|
| };
|
|
|
|
|
| /**
|
| + * Sets whether the current keyset supports swipe editting.
|
| + *
|
| + * @param {boolean} supported .
|
| + */
|
| +SwipeView.prototype.setKeysetSupported = function(supported) {
|
| + this.isKeysetSupported_ = supported;
|
| +};
|
| +
|
| +
|
| +/**
|
| * Handles swipe actions on the selection track. Swipes cause the cursor to move
|
| * to the next blank space in the direction of the swipe.
|
| *
|
| @@ -347,9 +523,10 @@ SwipeView.prototype.swipeToDelete_ = function(e) {
|
| SwipeView.prototype.swipeToSelect_ = function(e) {
|
| // Cache whether we were tracking as highlight may change this.
|
| var alreadyTracking = this.tracking_;
|
| - var changed = this.highlightItem(e.x, e.y);
|
| - // First finger movement is onto the blank track. Ignore.
|
| - if (!alreadyTracking || !changed) {
|
| + var previousIndex = this.getHighlightedIndex();
|
| + var direction = this.swipeOnTrack(e.x, e.y);
|
| + // Swipe did not change track index, ignore.
|
| + if (!direction) {
|
| return;
|
| }
|
| var index = this.getHighlightedIndex();
|
| @@ -359,20 +536,35 @@ SwipeView.prototype.swipeToSelect_ = function(e) {
|
| }
|
| // TODO: Set selectWord to true if the shift key is currently pressed.
|
| var selectWord = false;
|
| - var direction = e.direction;
|
| var code;
|
| - if (direction & i18n.input.chrome.inputview.SwipeDirection.LEFT) {
|
| + if (direction & SwipeDirection.LEFT) {
|
| code = KeyCodes.ARROW_LEFT;
|
| - } else if (direction & i18n.input.chrome.inputview.SwipeDirection.RIGHT) {
|
| + } else if (direction & SwipeDirection.RIGHT) {
|
| code = KeyCodes.ARROW_RIGHT;
|
| } else {
|
| + console.error('Unexpected swipe direction: ' + direction);
|
| return;
|
| }
|
| - this.adapter_.sendKeyDownAndUpEvent(
|
| - '', code, undefined, undefined, {
|
| - ctrl: true,
|
| - shift: selectWord
|
| - });
|
| + // Finger swipes sometimes go over multiple tracks. Complete the action for
|
| + // each.
|
| + var delta = Math.abs(index - previousIndex);
|
| + if (delta < 0) {
|
| + console.error('Swipe index did not change.');
|
| + }
|
| + if (this.ltr && code == KeyCodes.ARROW_LEFT ||
|
| + !this.ltr && code == KeyCodes.ARROW_RIGHT) {
|
| + this.forwardMoves_ += delta;
|
| + } else {
|
| + this.backwardMoves_ += delta;
|
| + }
|
| + // TODO: Investigate why pointerbundle skips some swipe events.
|
| + for (var i = 0; i < delta; i++) {
|
| + this.adapter_.sendKeyDownAndUpEvent(
|
| + '', code, undefined, undefined, {
|
| + ctrl: true,
|
| + shift: selectWord
|
| + });
|
| + }
|
| };
|
|
|
|
|
| @@ -384,6 +576,10 @@ SwipeView.prototype.swipeToSelect_ = function(e) {
|
| * @private
|
| */
|
| SwipeView.prototype.handleSwipeAction_ = function(e) {
|
| + if (this.eventIdentifier_ != undefined &&
|
| + this.eventIdentifier_ != e.identifier) {
|
| + return;
|
| + }
|
| if (this.isVisible()) {
|
| if (e.view.type == ElementType.BACKSPACE_KEY) {
|
| this.swipeToDelete_(e);
|
| @@ -405,9 +601,9 @@ SwipeView.prototype.handleSwipeAction_ = function(e) {
|
| if (e.direction & i18n.input.chrome.inputview.SwipeDirection.LEFT) {
|
| var key = /** @type {!content.FunctionalKey} */ (e.view);
|
| // Equivalent to a longpress.
|
| - if (this.adapter_.isGestureDeletionEnabled()) {
|
| - this.showDeletionTrack(key);
|
| - }
|
| + if (this.isDeletionEnabled()) {
|
| + this.showDeletionTrack(key, e.identifier, true);
|
| + }
|
| }
|
| return;
|
| }
|
| @@ -424,6 +620,10 @@ SwipeView.prototype.handlePointerAction_ = function(e) {
|
| if (!e.view) {
|
| return;
|
| }
|
| + if (this.eventIdentifier_ != undefined &&
|
| + e.identifier != this.eventIdentifier_) {
|
| + return;
|
| + }
|
| switch (e.view.type) {
|
| case ElementType.BACKSPACE_KEY:
|
| var key = /** @type {!content.FunctionalKey} */ (e.view);
|
| @@ -438,28 +638,28 @@ SwipeView.prototype.handlePointerAction_ = function(e) {
|
| this.armed_ = false;
|
| }
|
| } else if (e.type == EventType.LONG_PRESS) {
|
| - if (this.adapter_.isGestureDeletionEnabled()) {
|
| - this.showDeletionTrack(key);
|
| - }
|
| + if (this.isDeletionEnabled()) {
|
| + this.showDeletionTrack(key, e.identifier, false);
|
| + }
|
| }
|
| break;
|
| case ElementType.SWIPE_VIEW:
|
| if (e.type == EventType.POINTER_DOWN &&
|
| e.target == this.getCoverElement()) {
|
| - this.hide();
|
| + this.recordAndHide_();
|
| } else if (e.type == EventType.POINTER_UP ||
|
| e.type == EventType.POINTER_OUT) {
|
| - this.hide();
|
| + this.recordAndHide_();
|
| // Reset the deleted words.
|
| this.deletedWords_ = [];
|
| }
|
| break;
|
| case ElementType.SELECT_VIEW:
|
| if (e.type == EventType.POINTER_DOWN) {
|
| - this.showSelectionTrack(e.x, e.y);
|
| + this.showSelectionTrack(e.x, e.y, e.identifier);
|
| }
|
| if (e.type == EventType.POINTER_UP) {
|
| - this.hide();
|
| + this.recordAndHide_();
|
| }
|
| break;
|
| }
|
| @@ -472,13 +672,31 @@ SwipeView.prototype.createDom = function() {
|
|
|
| var dom = this.getDomHelper();
|
| var elem = this.getElement();
|
| + goog.style.setElementShown(elem, false);
|
| goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.SWIPE_VIEW);
|
| this.coverElement_ = dom.createDom(goog.dom.TagName.DIV,
|
| i18n.input.chrome.inputview.Css.TRACK_COVER);
|
| dom.appendChild(document.body, this.coverElement_);
|
| goog.style.setElementShown(this.coverElement_, false);
|
| -
|
| this.coverElement_['view'] = this;
|
| + // Cache finger affordance.
|
| + this.fingerTracker_ = dom.createDom(goog.dom.TagName.DIV,
|
| + i18n.input.chrome.inputview.Css.GESTURE_EDITING_FINGER_TRACKER);
|
| + goog.style.setSize(this.fingerTracker_,
|
| + SwipeView.FINGER_TRACKER_WIDTH_ + 'px',
|
| + SwipeView.FINGER_TRACKER_WIDTH_ + 'px');
|
| + this.ripple_ = dom.createDom(goog.dom.TagName.DIV,
|
| + i18n.input.chrome.inputview.Css.GESTURE_RIPPLE);
|
| + dom.appendChild(this.coverElement_, this.ripple_);
|
| + // Cache ripple animations.
|
| + this.fadeAnimation_ = new goog.fx.dom.FadeOut(this.ripple_,
|
| + SwipeView.FADE_ANIMATION_TIME_, goog.fx.easing.easeIn);
|
| + this.scaleAnimation_ = new ScaleAtPoint(this.ripple_,
|
| + [1, 1],
|
| + [SwipeView.RIPPLE_SCALE_FACTOR_, SwipeView.RIPPLE_SCALE_FACTOR_],
|
| + [-SwipeView.RIPPLE_WIDTH_, -SwipeView.RIPPLE_WIDTH_],
|
| + SwipeView.SCALE_ANIMATION_TIME_,
|
| + goog.fx.easing.easeOut);
|
| };
|
|
|
|
|
| @@ -497,7 +715,6 @@ SwipeView.prototype.enterDocument = function() {
|
| EventType.POINTER_UP,
|
| EventType.POINTER_DOWN,
|
| EventType.POINTER_OUT], this.handlePointerAction_);
|
| - goog.style.setElementShown(this.getElement(), false);
|
| };
|
|
|
|
|
| @@ -508,20 +725,15 @@ SwipeView.prototype.enterDocument = function() {
|
| * @param {number} y
|
| * @param {number} width The width of a key.
|
| * @param {number} height The height of a key.
|
| - * @param {number} firstTrackWidth The width of the first key.
|
| - * @param {number} firstSegmentWidth The width of the first buffer segment.
|
| - * @param {string=} opt_character Characters on each key.
|
| - * @param {Css=} opt_css Optional icon css class.
|
| * @private
|
| */
|
| -SwipeView.prototype.showDeletionTrack_ = function(x, y, width, height,
|
| - firstTrackWidth, firstSegmentWidth, opt_character, opt_css) {
|
| +SwipeView.prototype.showDeletionTrack_ = function(x, y, width, height) {
|
| this.tracking_ = false;
|
| goog.style.setElementShown(this.getElement(), true);
|
| this.getDomHelper().removeChildren(this.getElement());
|
| + goog.dom.classlist.add(this.getElement(), Css.DELETION_TRACK);
|
| // Each key except last has a separator.
|
| - var totalWidth = ((2 * SwipeView.LENGTH_) - 3) * width;
|
| - totalWidth += firstTrackWidth + firstSegmentWidth;
|
| + var totalWidth = ((2 * SwipeView.LENGTH_) - 1) * width;
|
|
|
| this.ltr = true;
|
| this.highlightIndex_ = 0;
|
| @@ -531,30 +743,37 @@ SwipeView.prototype.showDeletionTrack_ = function(x, y, width, height,
|
| this.ltr = false;
|
| this.highlightIndex_ = SwipeView.LENGTH_ - 1;
|
| }
|
| - if (firstTrackWidth == 0) {
|
| + if (width == 0) {
|
| this.highlightIndex_ = SwipeView.INVALID_INDEX_;
|
| }
|
| + if (this.ltr) {
|
| + goog.dom.classlist.add(this.getElement(), Css.LEFT_TO_RIGHT);
|
| + }
|
| var ltr = this.ltr;
|
| - var isFirstSegment = function(i) {
|
| - return ltr ? i == 0 : i == SwipeView.LENGTH_ - 2;
|
| - };
|
| - var isFirstTrackPiece = function(i) {
|
| - return ltr ? i == 0 : i == SwipeView.LENGTH_ - 1;
|
| - };
|
| for (var i = 0; i < SwipeView.LENGTH_; i++) {
|
| - var trackWidth = isFirstTrackPiece(i) ? firstTrackWidth : width;
|
| - if (trackWidth != 0) {
|
| - var keyElem = this.addKey_(opt_character, opt_css);
|
| - goog.style.setSize(keyElem, trackWidth, height);
|
| - this.trackElements_.push(keyElem);
|
| - }
|
| + var keyElem = this.addKey_();
|
| + goog.style.setSize(keyElem, width, height);
|
| + this.trackElements_.push(keyElem);
|
|
|
| if (i != (SwipeView.LENGTH_ - 1)) {
|
| - var segmentWidth = isFirstSegment(i) ? firstSegmentWidth : width;
|
| - this.addSeparator_(segmentWidth, height);
|
| + this.addSeparator_(width, height);
|
| }
|
| }
|
| - goog.style.setPosition(this.getElement(), x, y);
|
| + // Set position only changes the left and top values, which is problematic.
|
| + // Manually modify css rules instead.
|
| + if (this.ltr) {
|
| + goog.style.setStyle(this.getElement(), {
|
| + 'right': 'initial',
|
| + 'left': x,
|
| + 'top': y
|
| + });
|
| + } else {
|
| + goog.style.setStyle(this.getElement(), {
|
| + 'left': 'initial',
|
| + 'right': screen.width - x - totalWidth,
|
| + 'top': y
|
| + });
|
| + }
|
| // Highlight selected element if it's index is valid.
|
| if (this.highlightIndex_ != SwipeView.INVALID_INDEX_) {
|
| var elem = this.trackElements_[this.highlightIndex_];
|
| @@ -566,20 +785,36 @@ SwipeView.prototype.showDeletionTrack_ = function(x, y, width, height,
|
|
|
|
|
| /**
|
| + * Shows the current finger location at the coordinates provided.
|
| + *
|
| + * @param {number} x .
|
| + * @param {number} y .
|
| + * @private
|
| + */
|
| +SwipeView.prototype.showFinger_ = function(x, y) {
|
| + var dom = this.getDomHelper();
|
| + dom.appendChild(this.getElement(), this.fingerTracker_);
|
| + goog.style.setPosition(this.fingerTracker_,
|
| + x - (SwipeView.FINGER_TRACKER_WIDTH_ / 2),
|
| + y - (SwipeView.FINGER_TRACKER_WIDTH_ / 2));
|
| + goog.style.setElementShown(this.fingerTracker_, true);
|
| +};
|
| +
|
| +
|
| +/**
|
| * Shows the selection swipe tracker.
|
| *
|
| * @param {number} x
|
| * @param {number} y
|
| * @param {number} width The width of a key.
|
| * @param {number} height The height of a key.
|
| - * @param {string} character Characters on each key.
|
| * @private
|
| */
|
| -SwipeView.prototype.showSelectionTrack_ = function(x, y, width, height,
|
| - character) {
|
| +SwipeView.prototype.showSelectionTrack_ = function(x, y, width, height) {
|
| this.tracking_ = false;
|
| goog.style.setElementShown(this.getElement(), true);
|
| this.getDomHelper().removeChildren(this.getElement());
|
| + goog.dom.classlist.add(this.getElement(), Css.SELECTION_TRACK);
|
| // Each key has a separator.
|
| var totalWidth = ((2 * SwipeView.LENGTH_)) * width;
|
|
|
| @@ -590,11 +825,14 @@ SwipeView.prototype.showSelectionTrack_ = function(x, y, width, height,
|
| x -= totalWidth;
|
| this.ltr = false;
|
| }
|
| + if (this.ltr) {
|
| + goog.dom.classlist.add(this.getElement(), Css.LEFT_TO_RIGHT);
|
| + }
|
|
|
| for (var i = 0; i < SwipeView.LENGTH_; i++) {
|
| var keyElem;
|
| if (!this.ltr) {
|
| - keyElem = this.addKey_(character);
|
| + keyElem = this.addKey_();
|
| goog.style.setSize(keyElem, width, height);
|
| this.trackElements_.push(keyElem);
|
| }
|
| @@ -604,12 +842,26 @@ SwipeView.prototype.showSelectionTrack_ = function(x, y, width, height,
|
| this.trackElements_.push(keyElem);
|
|
|
| if (this.ltr) {
|
| - keyElem = this.addKey_(character);
|
| + keyElem = this.addKey_();
|
| goog.style.setSize(keyElem, width, height);
|
| this.trackElements_.push(keyElem);
|
| }
|
| }
|
| - goog.style.setPosition(this.getElement(), x, y);
|
| + // Set position only changes the left and top values, which is problematic.
|
| + // Manually modify css rules instead.
|
| + if (this.ltr) {
|
| + goog.style.setStyle(this.getElement(), {
|
| + 'right': 'initial',
|
| + 'left': x,
|
| + 'top': y
|
| + });
|
| + } else {
|
| + goog.style.setStyle(this.getElement(), {
|
| + 'left': 'initial',
|
| + 'right': 0,
|
| + 'top': y
|
| + });
|
| + }
|
| goog.style.setElementShown(this.coverElement_, true);
|
| this.triggeredBy && this.triggeredBy.setHighlighted(true);
|
| };
|
| @@ -620,8 +872,14 @@ SwipeView.prototype.showSelectionTrack_ = function(x, y, width, height,
|
| *
|
| * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key
|
| * The key triggered this track view.
|
| + * @param {!number} id The triggering event id.
|
| + * @param {boolean} isSwipe Indicates the trigger was a swipe.
|
| */
|
| -SwipeView.prototype.showDeletionTrack = function(key) {
|
| +SwipeView.prototype.showDeletionTrack = function(key, id, isSwipe) {
|
| + this.eventIdentifier_ = id;
|
| + this.isSelection_ = false;
|
| + this.adapter_.setGestureEditingInProgress(true, isSwipe);
|
| + this.candidateView_.showTooltip(Tooltip.DELETION);
|
| this.triggeredBy = key;
|
| var coordinate = goog.style.getClientPosition(key.getElement());
|
| if (key.type == ElementType.BACKSPACE_KEY) {
|
| @@ -629,12 +887,12 @@ SwipeView.prototype.showDeletionTrack = function(key) {
|
| coordinate.x + key.availableWidth,
|
| coordinate.y,
|
| SwipeView.SEGMENT_WIDTH_,
|
| - key.availableHeight,
|
| - key.availableWidth,
|
| - SwipeView.LARGE_SEGMENT_WIDTH_,
|
| - undefined,
|
| - Css.BACKSPACE_ICON);
|
| + SwipeView.SEGMENT_WIDTH_);
|
| }
|
| + var centerX = coordinate.x + (key.availableWidth / 2);
|
| + var centerY = coordinate.y + (key.availableHeight / 2);
|
| + this.animateRipple_(centerX, centerY);
|
| + this.showFinger_(centerX, centerY);
|
| };
|
|
|
|
|
| @@ -643,8 +901,13 @@ SwipeView.prototype.showDeletionTrack = function(key) {
|
| *
|
| * @param {number} x
|
| * @param {number} y
|
| + * @param {number} id The triggering event id.
|
| */
|
| -SwipeView.prototype.showSelectionTrack = function(x, y) {
|
| +SwipeView.prototype.showSelectionTrack = function(x, y, id) {
|
| + this.eventIdentifier_ = id;
|
| + this.isSelection_ = true;
|
| + this.adapter_.setGestureEditingInProgress(true);
|
| + this.candidateView_.showTooltip(Tooltip.SELECTION);
|
| var ltr = (x <= (screen.width / 2));
|
| var halfWidth = SwipeView.SEGMENT_WIDTH_ / 2;
|
| // Center track on finger but force containment.
|
| @@ -654,24 +917,146 @@ SwipeView.prototype.showSelectionTrack = function(x, y) {
|
| ltr ? 0 : screen.width,
|
| trackY,
|
| SwipeView.SEGMENT_WIDTH_,
|
| - SwipeView.SEGMENT_WIDTH_,
|
| - x > (screen.width / 2) ? '<' : '>');
|
| + SwipeView.SEGMENT_WIDTH_);
|
| + this.showFinger_(x, y);
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Creates an animation object that will scale an element at a point.
|
| + *
|
| + * Start, end and origin should be 2 dimensional arrays
|
| + *
|
| + * @param {Element} element Dom Node to be used in the animation.
|
| + * @param {Array<number>} start 2D array for start x-scale and y-scale.
|
| + * @param {Array<number>} end 2D array for end x-scale and y-scale.
|
| + * @param {Array<number>} origin 2D array for origin relative to the elements
|
| + * current position.
|
| + * @param {number} time Length of animation in milliseconds.
|
| + * @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
|
| + * @extends {goog.fx.dom.PredefinedEffect}
|
| + * @constructor
|
| + */
|
| +// TODO: Migrate this to the closure animation library.
|
| +var ScaleAtPoint = function(element, start, end, origin, time, opt_acc) {
|
| + if (start.length != 2 || end.length != 2 || origin.length != 2) {
|
| + throw Error('Start, end and origin arrays must be 2D');
|
| + }
|
| +
|
| + /**
|
| + * Point at which the animation should scale relative to the elements current
|
| + * location.
|
| + *
|
| + * @private {Array<number>}
|
| + */
|
| + this.origin_ = origin;
|
| +
|
| + ScaleAtPoint.base(this, 'constructor', element, start, end, time, opt_acc);
|
| +};
|
| +goog.inherits(ScaleAtPoint, goog.fx.dom.PredefinedEffect);
|
| +
|
| +
|
| +/** override */
|
| +ScaleAtPoint.prototype.updateStyle = function() {
|
| + var transform = [
|
| + 'translate(', this.origin_[0], 'px, ', this.origin_[1], 'px) ',
|
| + 'scale(', this.coords[0], ', ', this.coords[1], ')'
|
| + ].join('');
|
| + goog.style.setStyle(this.element, 'transform', transform);
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Handles the fade start event on the ripple.
|
| + *
|
| + * @private
|
| + */
|
| +SwipeView.prototype.onFadeStarted_ = function() {
|
| + goog.events.unlisten(this.fadeAnimation_,
|
| + goog.fx.Animation.EventType.BEGIN,
|
| + this.onFadeStarted_);
|
| + this.scaleAnimation_.play();
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Animates the ripple effect centered on the coordinates provided.
|
| + *
|
| + * @param {number} x
|
| + * @param {number} y
|
| + * @private
|
| + */
|
| +SwipeView.prototype.animateRipple_ = function(x, y) {
|
| + goog.style.setPosition(this.ripple_, x, y);
|
| + goog.style.setStyle(this.ripple_, 'transform', '');
|
| + goog.style.setElementShown(this.ripple_, true);
|
| + goog.events.listen(this.fadeAnimation_, goog.fx.Animation.EventType.BEGIN,
|
| + this.onFadeStarted_.bind(this));
|
| + this.fadeAnimation_.play();
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Records statistics for this swipe and hides the track.
|
| + *
|
| + * @private
|
| + */
|
| +SwipeView.prototype.recordAndHide_ = function() {
|
| + this.recordStatistics_();
|
| + this.hide_();
|
| };
|
|
|
|
|
| /**
|
| * Hides the swipe view.
|
| + *
|
| + * @private
|
| */
|
| -SwipeView.prototype.hide = function() {
|
| +SwipeView.prototype.hide_ = function() {
|
| + this.adapter_.setGestureEditingInProgress(false);
|
| + this.candidateView_.hideTooltip();
|
| + goog.style.setElementShown(this.ripple_, false);
|
| this.armed_ = false;
|
| this.trackElements_ = [];
|
| this.tracking_ = false;
|
| + this.eventIdentifier_ = undefined;
|
| if (this.triggeredBy) {
|
| this.triggeredBy.setHighlighted(false);
|
| }
|
| + this.triggeredBy = undefined;
|
| goog.style.setElementShown(this.getElement(), false);
|
| goog.style.setElementShown(this.coverElement_, false);
|
| this.highlightIndex_ = SwipeView.INVALID_INDEX_;
|
| + goog.dom.classlist.removeAll(this.getElement(), [
|
| + Css.SWIPE_ACTIVE, Css.SELECTION_TRACK, Css.DELETION_TRACK,
|
| + Css.LEFT_TO_RIGHT
|
| + ]);
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Records statistics from this swipe.
|
| + *
|
| + * @private
|
| + */
|
| +SwipeView.prototype.recordStatistics_ = function() {
|
| + if (this.isSelection_) {
|
| + this.statistics_.recordValue(
|
| + 'InputMethod.VirtualKeyboard.BackwardsMovesPerSwipe',
|
| + this.forwardMoves_, 100, 50);
|
| + this.statistics_.recordValue(
|
| + 'InputMethod.VirtualKeyboard.MovesPerSwipe', this.backwardMoves_,
|
| + 100, 50);
|
| + } else {
|
| + this.statistics_.recordValue(
|
| + 'InputMethod.VirtualKeyboard.WordsDeletedPerSwipe', this.forwardMoves_,
|
| + 100, 50);
|
| + this.statistics_.recordValue(
|
| + 'InputMethod.VirtualKeyboard.WordsRestoredPerSwipe',
|
| + this.backwardMoves_, 100, 50);
|
| + }
|
| + this.forwardMoves_ = 0;
|
| + this.backwardMoves_ = 0;
|
| };
|
|
|
|
|
| @@ -687,26 +1072,38 @@ SwipeView.prototype.isAtOrigin = function() {
|
|
|
|
|
| /**
|
| - * Highlights the item according to the current coordinate of the finger.
|
| + * Swipes to the coordinates specified on the track.
|
| *
|
| * @param {number} x .
|
| * @param {number} y .
|
| - * @return {boolean} Whether it passed into a new segment.
|
| + * @return {SwipeDirection|undefined} Direction swiped else undefined if there
|
| + * there was no change.
|
| */
|
| -SwipeView.prototype.highlightItem = function(x, y) {
|
| +SwipeView.prototype.swipeOnTrack = function(x, y) {
|
| var previousIndex = this.highlightIndex_;
|
| for (var i = 0; i < this.trackElements_.length; i++) {
|
| var elem = this.trackElements_[i];
|
| var coordinate = goog.style.getClientPosition(elem);
|
| var size = goog.style.getSize(elem);
|
| - if (coordinate.x < x && (coordinate.x + size.width) > x) {
|
| + var visible = (goog.style.getComputedStyle(elem, 'display') != 'none');
|
| + if (visible && coordinate.x < x && (coordinate.x + size.width) > x) {
|
| this.highlightIndex_ = i;
|
| this.clearAllHighlights_();
|
| this.setElementBackground_(elem, true);
|
| + goog.dom.classlist.add(this.getElement(), Css.SWIPE_ACTIVE);
|
| }
|
| }
|
| - this.tracking_ = this.tracking_ || (previousIndex != this.highlightIndex_);
|
| - return (previousIndex != this.highlightIndex_);
|
| + var changed = previousIndex != this.highlightIndex_;
|
| + this.tracking_ = this.tracking_ || changed;
|
| + // Update finger affordance.
|
| + goog.style.setPosition(this.fingerTracker_,
|
| + x - (SwipeView.FINGER_TRACKER_WIDTH_ / 2),
|
| + y - (SwipeView.FINGER_TRACKER_WIDTH_ / 2));
|
| + if (!changed || previousIndex == SwipeView.INVALID_INDEX_) {
|
| + return undefined;
|
| + }
|
| + return previousIndex < this.highlightIndex_ ?
|
| + SwipeDirection.RIGHT : SwipeDirection.LEFT;
|
| };
|
|
|
|
|
| @@ -715,8 +1112,7 @@ SwipeView.prototype.highlightItem = function(x, y) {
|
| *
|
| * @private
|
| */
|
| -SwipeView.prototype.clearAllHighlights_ =
|
| - function() {
|
| +SwipeView.prototype.clearAllHighlights_ = function() {
|
| for (var i = 0; i < this.trackElements_.length; i++) {
|
| this.setElementBackground_(this.trackElements_[i], false);
|
| }
|
| @@ -733,11 +1129,9 @@ SwipeView.prototype.clearAllHighlights_ =
|
| SwipeView.prototype.setElementBackground_ =
|
| function(element, highlight) {
|
| if (highlight) {
|
| - goog.dom.classlist.add(element, i18n.input.chrome.inputview.Css
|
| - .ELEMENT_HIGHLIGHT);
|
| + goog.dom.classlist.add(element, Css.ELEMENT_HIGHLIGHT);
|
| } else {
|
| - goog.dom.classlist.remove(element, i18n.input.chrome.inputview.Css
|
| - .ELEMENT_HIGHLIGHT);
|
| + goog.dom.classlist.remove(element, Css.ELEMENT_HIGHLIGHT);
|
| }
|
| };
|
|
|
| @@ -760,6 +1154,7 @@ SwipeView.prototype.addKey_ = function(opt_character, opt_icon_css) {
|
| } else {
|
| keyElem = dom.createDom(goog.dom.TagName.DIV, Css.SWIPE_KEY);
|
| }
|
| + goog.dom.classlist.add(keyElem, Css.SWIPE_PIECE);
|
| if (opt_icon_css) {
|
| var child = dom.createDom(goog.dom.TagName.DIV, opt_icon_css);
|
| dom.appendChild(keyElem, child);
|
| @@ -779,12 +1174,11 @@ SwipeView.prototype.addKey_ = function(opt_character, opt_icon_css) {
|
| */
|
| SwipeView.prototype.addSeparator_ = function(width, height) {
|
| var dom = this.getDomHelper();
|
| - var tableCell = dom.createDom(goog.dom.TagName.DIV,
|
| - i18n.input.chrome.inputview.Css.TABLE_CELL);
|
| - goog.style.setSize(tableCell, width + 'px', height + 'px');
|
| - goog.dom.classlist.add(tableCell, Css.SWIPE_SEPARATOR);
|
| - dom.appendChild(this.getElement(), tableCell);
|
| - return tableCell;
|
| + var separator = dom.createDom(goog.dom.TagName.DIV, Css.SWIPE_SEPARATOR);
|
| + goog.style.setSize(separator, width + 'px', height + 'px');
|
| + goog.dom.classlist.add(separator, Css.SWIPE_PIECE);
|
| + dom.appendChild(this.getElement(), separator);
|
| + return separator;
|
| };
|
|
|
|
|
| @@ -814,10 +1208,31 @@ SwipeView.prototype.getHighlightedIndex = function() {
|
| };
|
|
|
|
|
| +/**
|
| + * Returns whether gesture deletion is enabled for the current context.
|
| + *
|
| + * @return {boolean}
|
| + */
|
| +SwipeView.prototype.isDeletionEnabled = function() {
|
| + // TODO: Omni bar sends wrong anchor/focus when autocompleting
|
| + // URLs. Re-enable when that is fixed.
|
| + if (this.adapter_.contextType == ContextType.URL) {
|
| + return false;
|
| + }
|
| + if (this.adapter_.isA11yMode) {
|
| + return false;
|
| + }
|
| + if (!this.isKeysetSupported_) {
|
| + return false;
|
| + }
|
| + // TODO: Disable if the current layout is emoji or handwriting.
|
| + return this.enabled;
|
| +};
|
| +
|
| +
|
| /** @override */
|
| SwipeView.prototype.resize = function(width, height) {
|
| goog.base(this, 'resize', width, height);
|
| -
|
| goog.style.setSize(this.coverElement_, width, height);
|
| };
|
|
|
| @@ -829,7 +1244,7 @@ SwipeView.prototype.resize = function(width, height) {
|
| SwipeView.prototype.reset = function() {
|
| this.deletedWords_ = [];
|
| this.surroundingText_ = '';
|
| - this.hide();
|
| + this.hide_();
|
| };
|
|
|
|
|
|
|