1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
2 // limitations under the License.
3 // See the License for the specific language governing permissions and
4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5 // distributed under the License is distributed on an "AS-IS" BASIS,
6 // Unless required by applicable law or agreed to in writing, software
8 // http://www.apache.org/licenses/LICENSE-2.0
10 // You may obtain a copy of the License at
11 // you may not use this file except in compliance with the License.
12 // Licensed under the Apache License, Version 2.0 (the "License");
14 goog.provide('i18n.input.chrome.inputview.handler.PointerActionBundle');
16 goog.require('goog.Timer');
17 goog.require('goog.events.EventTarget');
18 goog.require('goog.events.EventType');
19 goog.require('goog.math.Coordinate');
20 goog.require('i18n.input.chrome.inputview.SwipeDirection');
21 goog.require('i18n.input.chrome.inputview.events.DragEvent');
22 goog.require('i18n.input.chrome.inputview.events.EventType');
23 goog.require('i18n.input.chrome.inputview.events.PointerEvent');
24 goog.require('i18n.input.chrome.inputview.events.SwipeEvent');
25 goog.require('i18n.input.chrome.inputview.handler.SwipeState');
26 goog.require('i18n.input.chrome.inputview.handler.Util');
30 goog.scope(function() {
35 * The handler for long press.
37 * @param {!i18n.input.chrome.inputview.elements.Element} view The view for this
39 * @param {goog.events.EventTarget=} opt_parentEventTarget The parent event
42 * @extends {goog.events.EventTarget}
44 i18n.input.chrome.inputview.handler.PointerActionBundle = function(view,
45 opt_parentEventTarget) {
47 this.setParentEventTarget(opt_parentEventTarget || null);
52 * @type {!i18n.input.chrome.inputview.elements.Element}
59 * @type {!i18n.input.chrome.inputview.handler.SwipeState}
62 this.swipeState_ = new i18n.input.chrome.inputview.handler.SwipeState();
64 goog.inherits(i18n.input.chrome.inputview.handler.PointerActionBundle,
65 goog.events.EventTarget);
66 var PointerActionBundle = i18n.input.chrome.inputview.handler.
68 var Util = i18n.input.chrome.inputview.handler.Util;
72 * The current target after the touch point is moved.
74 * @type {!Node | Element}
77 PointerActionBundle.prototype.currentTarget_;
81 * How many milli-seconds to evaluate a double click event.
86 PointerActionBundle.DOUBLE_CLICK_INTERVAL_ = 500;
95 PointerActionBundle.prototype.longPressTimer_;
99 * The minimum swipe distance.
104 PointerActionBundle.MINIMUM_SWIPE_DISTANCE_ = 20;
108 * The timestamp of the pointer down.
113 PointerActionBundle.prototype.pointerDownTimeStamp_ = 0;
117 * The timestamp of the pointer up.
122 PointerActionBundle.prototype.pointerUpTimeStamp_ = 0;
126 * True if it is double clicking.
131 PointerActionBundle.prototype.isDBLClicking_ = false;
135 * True if it is long pressing.
140 PointerActionBundle.prototype.isLongPressing_ = false;
144 * True if it is flickering.
149 PointerActionBundle.prototype.isFlickering_ = false;
153 * Handles touchmove event for one target.
155 * @param {!Touch | !Event} e .
157 PointerActionBundle.prototype.handlePointerMove = function(e) {
158 var identifier = Util.getEventIdentifier(e);
160 var deltaX = this.swipeState_.previousX == 0 ? 0 : (e.pageX -
161 this.swipeState_.previousX);
162 var deltaY = this.swipeState_.previousY == 0 ? 0 :
163 (e.pageY - this.swipeState_.previousY);
164 this.swipeState_.offsetX += deltaX;
165 this.swipeState_.offsetY += deltaY;
166 this.dispatchEvent(new i18n.input.chrome.inputview.events.DragEvent(
167 this.view, direction, /** @type {!Node} */ (e.target),
168 e.pageX, e.pageY, deltaX, deltaY, identifier));
170 var minimumSwipeDist = PointerActionBundle.
171 MINIMUM_SWIPE_DISTANCE_;
173 if (this.swipeState_.offsetX > minimumSwipeDist) {
174 direction |= i18n.input.chrome.inputview.SwipeDirection.RIGHT;
175 this.swipeState_.offsetX = 0;
176 } else if (this.swipeState_.offsetX < -minimumSwipeDist) {
177 direction |= i18n.input.chrome.inputview.SwipeDirection.LEFT;
178 this.swipeState_.offsetX = 0;
181 if (Math.abs(deltaY) > Math.abs(deltaX)) {
182 if (this.swipeState_.offsetY > minimumSwipeDist) {
183 direction |= i18n.input.chrome.inputview.SwipeDirection.DOWN;
184 this.swipeState_.offsetY = 0;
185 } else if (this.swipeState_.offsetY < -minimumSwipeDist) {
186 direction |= i18n.input.chrome.inputview.SwipeDirection.UP;
187 this.swipeState_.offsetY = 0;
191 this.swipeState_.previousX = e.pageX;
192 this.swipeState_.previousY = e.pageY;
195 // If there is any movement, cancel the longpress timer.
196 goog.Timer.clear(this.longPressTimer_);
197 this.dispatchEvent(new i18n.input.chrome.inputview.events.SwipeEvent(
198 this.view, direction, /** @type {!Node} */ (e.target),
199 e.pageX, e.pageY, identifier));
200 var currentTargetView = Util.getView(this.currentTarget_);
201 this.isFlickering_ = !this.isLongPressing_ && !!(this.view.pointerConfig.
202 flickerDirection & direction) && currentTargetView == this.view;
205 this.maybeSwitchTarget_(
206 new goog.math.Coordinate(e.pageX, e.pageY), identifier);
211 * If the target is switched to a new one, sends out a pointer_over for the new
212 * target and sends out a pointer_out for the old target.
214 * @param {!goog.math.Coordinate} pageOffset .
215 * @param {number} identifier .
218 PointerActionBundle.prototype.maybeSwitchTarget_ = function(pageOffset,
220 if (!this.isFlickering_) {
221 var actualTarget = document.elementFromPoint(pageOffset.x, pageOffset.y);
222 var currentTargetView = Util.getView(this.currentTarget_);
223 var actualTargetView = Util.getView(actualTarget);
224 if (currentTargetView != actualTargetView) {
225 if (currentTargetView) {
226 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
228 i18n.input.chrome.inputview.events.EventType.POINTER_OUT,
229 this.currentTarget_, pageOffset.x, pageOffset.y, identifier));
231 if (actualTargetView) {
232 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
234 i18n.input.chrome.inputview.events.EventType.POINTER_OVER,
235 actualTarget, pageOffset.x, pageOffset.y, identifier));
237 this.currentTarget_ = actualTarget;
244 * Handles pointer up, e.g., mouseup/touchend.
246 * @param {!goog.events.BrowserEvent} e The event.
248 PointerActionBundle.prototype.handlePointerUp = function(e) {
249 goog.Timer.clear(this.longPressTimer_);
250 var pageOffset = this.getPageOffset_(e);
251 var identifier = Util.getEventIdentifier(e);
252 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
253 this.view, i18n.input.chrome.inputview.events.EventType.LONG_PRESS_END,
254 e.target, pageOffset.x, pageOffset.y, identifier));
255 if (this.isDBLClicking_) {
256 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
257 this.view, i18n.input.chrome.inputview.events.EventType.
258 DOUBLE_CLICK_END, e.target, pageOffset.x, pageOffset.y, identifier));
259 } else if (!(this.isLongPressing_ && this.view.pointerConfig.
260 longPressWithoutPointerUp)) {
261 this.maybeSwitchTarget_(pageOffset, identifier);
262 var view = Util.getView(this.currentTarget_);
263 var target = this.currentTarget_;
264 if (this.isFlickering_) {
268 this.pointerUpTimeStamp_ = new Date().getTime();
269 // Note |view| can be null if the finger moves outside of keyboard window
270 // area. This is possible when user try to select an accent character
271 // which is displayed outside of keyboard window. We need to dispatch a
272 // POINTER_UP event to keyboard to commit the selected accent character.
273 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
274 view, i18n.input.chrome.inputview.events.EventType.POINTER_UP,
275 target, pageOffset.x, pageOffset.y, identifier,
276 this.pointerUpTimeStamp_));
278 if (Util.getView(this.currentTarget_) == this.view) {
279 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
280 this.view, i18n.input.chrome.inputview.events.EventType.CLICK,
281 e.target, pageOffset.x, pageOffset.y, identifier));
283 this.isDBLClicking_ = false;
284 this.isLongPressing_ = false;
285 this.isFlickering_ = false;
286 this.swipeState_.reset();
291 * Cancel double click recognition on this target.
293 PointerActionBundle.prototype.cancelDoubleClick = function() {
294 this.pointerDownTimeStamp_ = 0;
299 * Handles pointer down, e.g., mousedown/touchstart.
301 * @param {!goog.events.BrowserEvent} e The event.
303 PointerActionBundle.prototype.handlePointerDown = function(e) {
304 this.currentTarget_ = e.target;
305 goog.Timer.clear(this.longPressTimer_);
306 var identifier = Util.getEventIdentifier(e);
307 if (e.type == goog.events.EventType.TOUCHSTART) {
308 this.maybeTriggerKeyDownLongPress_(e, identifier);
310 this.maybeHandleDBLClick_(e, identifier);
311 if (!this.isDBLClicking_) {
312 var pageOffset = this.getPageOffset_(e);
313 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
314 this.view, i18n.input.chrome.inputview.events.EventType.POINTER_DOWN,
315 e.target, pageOffset.x, pageOffset.y, identifier,
316 this.pointerDownTimeStamp_));
322 * Gets the page offset from the event which may be mouse event or touch event.
324 * @param {!goog.events.BrowserEvent} e .
325 * @return {!goog.math.Coordinate} .
328 PointerActionBundle.prototype.getPageOffset_ = function(e) {
329 var nativeEvt = e.getBrowserEvent();
330 if (nativeEvt.pageX && nativeEvt.pageY) {
331 return new goog.math.Coordinate(nativeEvt.pageX, nativeEvt.pageY);
335 var touchEventList = nativeEvt['changedTouches'];
336 if (!touchEventList || touchEventList.length == 0) {
337 touchEventList = nativeEvt['touches'];
339 if (touchEventList && touchEventList.length > 0) {
340 var touchEvent = touchEventList[0];
341 return new goog.math.Coordinate(touchEvent.pageX, touchEvent.pageY);
344 return new goog.math.Coordinate(0, 0);
349 * Maybe triggers the long press timer when pointer down.
351 * @param {!goog.events.BrowserEvent} e The event.
352 * @param {number} identifier .
355 PointerActionBundle.prototype.maybeTriggerKeyDownLongPress_ = function(e,
357 if (this.view && (this.view.pointerConfig.longPressWithPointerUp ||
358 this.view.pointerConfig.longPressWithoutPointerUp)) {
359 this.longPressTimer_ = goog.Timer.callOnce(
360 goog.bind(this.triggerLongPress_, this, e, identifier),
361 this.view.pointerConfig.longPressDelay, this);
367 * Maybe handle the double click.
369 * @param {!goog.events.BrowserEvent} e .
370 * @param {number} identifier .
373 PointerActionBundle.prototype.maybeHandleDBLClick_ = function(e, identifier) {
374 if (this.view && this.view.pointerConfig.dblClick) {
375 var timeInMs = new Date().getTime();
376 var interval = this.view.pointerConfig.dblClickDelay ||
377 PointerActionBundle.DOUBLE_CLICK_INTERVAL_;
378 var nativeEvt = e.getBrowserEvent();
379 if ((timeInMs - this.pointerDownTimeStamp_) < interval) {
380 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
381 this.view, i18n.input.chrome.inputview.events.EventType.DOUBLE_CLICK,
382 e.target, nativeEvt.pageX, nativeEvt.pageY, identifier));
383 this.isDBLClicking_ = true;
385 this.pointerDownTimeStamp_ = timeInMs;
391 * Triggers long press event.
393 * @param {!goog.events.BrowserEvent} e The event.
394 * @param {number} identifier .
397 PointerActionBundle.prototype.triggerLongPress_ = function(e, identifier) {
398 var nativeEvt = e.getBrowserEvent();
399 if (nativeEvt.touches.length > 1) {
402 this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
403 this.view, i18n.input.chrome.inputview.events.EventType.LONG_PRESS,
404 e.target, nativeEvt.pageX, nativeEvt.pageY, identifier));
405 this.isLongPressing_ = true;
410 PointerActionBundle.prototype.disposeInternal = function() {
411 goog.dispose(this.longPressTimer_);
413 goog.base(this, 'disposeInternal');