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_(); |
}; |