1// Copyright (c) 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.
4
5'use strict';
6
7/**
8 * A controller class detects mouse inactivity and hides "tool" elements.
9 *
10 * @param {Element} container The main DOM container.
11 * @param {number=} opt_timeout Hide timeout in ms.
12 * @param {function():boolean=} opt_toolsActive Function that returns |true|
13 *     if the tools are active and should not be hidden.
14 * @constructor
15 */
16function MouseInactivityWatcher(container, opt_timeout, opt_toolsActive) {
17  this.container_ = container;
18  this.timeout_ = opt_timeout || MouseInactivityWatcher.DEFAULT_TIMEOUT;
19  this.toolsActive_ = opt_toolsActive || function() { return false; };
20
21  this.onTimeoutBound_ = this.onTimeout_.bind(this);
22  this.timeoutID_ = null;
23  this.mouseOverTool_ = false;
24
25  this.clientX_ = 0;
26  this.clientY_ = 0;
27
28  /**
29   * Indicates if the inactivity watcher is enabled or disabled. Use getters
30   * and setters.
31   * @type {boolean}
32   * @private
33   **/
34  this.disabled_ = false;
35  this.__defineSetter__('disabled', function(value) {
36    this.disabled_ = value;
37    if (value)
38      this.kick();
39    else
40      this.check();
41  });
42  this.__defineGetter__('disabled', function() {
43    return this.disabled_;
44  });
45
46  this.container_.addEventListener('mousemove', this.onMouseMove_.bind(this));
47  var tools = this.container_.querySelector('.tool');
48  for (var i = 0; i < tools.length; i++) {
49    tools[i].addEventListener('mouseover', this.onToolMouseOver_.bind(this));
50    tools[i].addEventListener('mouseout', this.onToolMouseOut_.bind(this));
51  }
52
53  // Show tools when the user touches the screen.
54  this.container_.addEventListener(
55      'touchstart', this.activityStarted_.bind(this));
56  var initiateFading = this.activityStopped_.bind(this, this.timeout_);
57  this.container_.addEventListener('touchend', initiateFading);
58  this.container_.addEventListener('touchcancel', initiateFading);
59}
60
61/**
62 * Default inactivity timeout.
63 */
64MouseInactivityWatcher.DEFAULT_TIMEOUT = 3000;
65
66/**
67 * @param {boolean} on True if show, false if hide.
68 */
69MouseInactivityWatcher.prototype.showTools = function(on) {
70  if (on)
71    this.container_.setAttribute('tools', 'true');
72  else
73    this.container_.removeAttribute('tools');
74};
75
76/**
77 * To be called when the user started activity. Shows the tools
78 * and cancels the countdown.
79 * @private
80 */
81MouseInactivityWatcher.prototype.activityStarted_ = function() {
82  this.showTools(true);
83
84  if (this.timeoutID_) {
85    clearTimeout(this.timeoutID_);
86    this.timeoutID_ = null;
87  }
88};
89
90/**
91 * Called when user activity has stopped. Re-starts the countdown.
92 * @param {number=} opt_timeout Timeout.
93 * @private
94 */
95MouseInactivityWatcher.prototype.activityStopped_ = function(opt_timeout) {
96  if (this.disabled_ || this.mouseOverTool_ || this.toolsActive_())
97    return;
98
99  if (this.timeoutID_)
100    clearTimeout(this.timeoutID_);
101
102  this.timeoutID_ = setTimeout(
103      this.onTimeoutBound_, opt_timeout || this.timeout_);
104};
105
106/**
107 * Called when a user performed a short action (such as a click or a key press)
108 * that should show the tools if they are not visible.
109 * @param {number=} opt_timeout Timeout.
110 */
111MouseInactivityWatcher.prototype.kick = function(opt_timeout) {
112  this.activityStarted_();
113  this.activityStopped_(opt_timeout);
114};
115
116/**
117 * Check if the tools are active and update the tools visibility accordingly.
118 */
119MouseInactivityWatcher.prototype.check = function() {
120  if (this.toolsActive_())
121    this.activityStarted_();
122  else
123    this.activityStopped_();
124};
125
126/**
127 * Mouse move handler.
128 *
129 * @param {Event} e Event.
130 * @private
131 */
132MouseInactivityWatcher.prototype.onMouseMove_ = function(e) {
133  if (this.clientX_ == e.clientX && this.clientY_ == e.clientY) {
134    // The mouse has not moved, must be the cursor change triggered by
135    // some of the attributes on the root container. Ignore the event.
136    return;
137  }
138  this.clientX_ = e.clientX;
139  this.clientY_ = e.clientY;
140
141  if (this.disabled_)
142    return;
143
144  this.kick();
145};
146
147/**
148 * Mouse over handler on a tool element.
149 *
150 * @param {Event} e Event.
151 * @private
152 */
153MouseInactivityWatcher.prototype.onToolMouseOver_ = function(e) {
154  this.mouseOverTool_ = true;
155  if (!this.disabled_)
156    this.kick();
157};
158
159/**
160 * Mouse out handler on a tool element.
161 *
162 * @param {Event} e Event.
163 * @private
164 */
165MouseInactivityWatcher.prototype.onToolMouseOut_ = function(e) {
166  this.mouseOverTool_ = false;
167  if (!this.disabled_)
168    this.kick();
169};
170
171/**
172 * Timeout handler.
173 * @private
174 */
175MouseInactivityWatcher.prototype.onTimeout_ = function() {
176  this.timeoutID_ = null;
177  if (!this.disabled_ && !this.toolsActive_())
178    this.showTools(false);
179};
180