1// Copyright (c) 2011 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
5cr.define('cr.ui', function() {
6  /**
7   * Creates a new button element. The repeating button behaves like a
8   * keyboard button, which auto-repeats if held. This button is designed
9   * for use with controls such as brightness and volume adjustment buttons.
10   * @constructor
11   * @extends {HTMLButtonElement}
12   */
13  var RepeatingButton = cr.ui.define('button');
14
15  /**
16   * DOM Events that may be fired by the Repeating button.
17   */
18  RepeatingButton.Event = {
19    BUTTON_HELD: 'buttonHeld'
20  };
21
22  RepeatingButton.prototype = {
23    __proto__: HTMLButtonElement.prototype,
24
25    /**
26     * Delay in milliseconds before the first repeat trigger of the button
27     * held action.
28     * @type {number}
29     * @private
30     */
31    holdDelayTime_: 500,
32
33    /**
34     * Delay in milliseconds between triggers of the button held action.
35     * @type {number}
36     * @private
37     */
38    holdRepeatIntervalTime_: 50,
39
40    /**
41     * Callback function ID when repeated intervals trigger. Initialized when
42     * the button is held for an initial delay period and cleared when the
43     * button is released.
44     * @type {number|undefined}
45     * @private
46     */
47    intervalCallbackId_: undefined,
48
49    /**
50     * Callback function to arm the repeat timer. Initialized when the button
51     * is pressed and cleared when the interval timer is set or the button is
52     * released.
53     * @type {number|undefined}
54     * @private
55     */
56    armRepeaterCallbackId_: undefined,
57
58    /**
59     * Initializes the button.
60     */
61    decorate: function() {
62      this.addEventListener('mousedown', this.buttonDown_.bind(this));
63      this.addEventListener('mouseup', this.buttonUp_.bind(this));
64      this.addEventListener('mouseout', this.buttonUp_.bind(this));
65      this.addEventListener('touchstart', this.touchStart_.bind(this));
66      this.addEventListener('touchend', this.buttonUp_.bind(this));
67      this.addEventListener('touchcancel', this.buttonUp_.bind(this));
68    },
69
70    /**
71     * Called when the user initiates a touch gesture.
72     * @param {!Event} e The triggered event.
73     * @private
74     */
75    touchStart_: function(e) {
76      // Block system level gestures to prevent double tap to zoom. Also,
77      // block following mouse event to prevent double firing of the button
78      // held action in the case of a tap. Otherwise, a single tap action in
79      // webkit generates the following event sequence: touchstart, touchend,
80      // mouseover, mousemove, mousedown, mouseup and click.
81      e.preventDefault();
82      this.buttonDown_(e);
83    },
84
85    /**
86     * Called when the user presses this button.
87     * @param {!Event} e The triggered event.
88     * @private
89     */
90    buttonDown_: function(e) {
91      this.clearTimeout_();
92      // Trigger the button held action immediately, after an initial delay and
93      // then repeated based on a fixed time increment. The time intervals are
94      // in agreement with the defaults for the ChromeOS keyboard and virtual
95      // keyboard.
96      // TODO(kevers): Consider adding a common location for picking up the
97      //               initial delay and repeat interval.
98      this.buttonHeld_();
99      var self = this;
100      var armRepeaterCallback = function() {
101        // In the event of a click/tap operation, this button has already been
102        // released by the time this timeout triggers. Test to ensure that the
103        // button is still being held (i.e. clearTimeout has not been called).
104        if (typeof self.armRepeaterCallbackId_ != 'undefined') {
105          self.armRepeaterCallbackId_ = undefined;
106          self.buttonHeld_();
107          self.intervalCallbackId_ = setInterval(self.buttonHeld_.bind(self),
108                                                 self.holdRepeatIntervalTime_);
109        }
110      };
111      this.armRepeaterCallbackId_ = setTimeout(armRepeaterCallback,
112                                               this.holdDelayTime_);
113    },
114
115    /**
116     * Called when the user releases this button.
117     * @param {!Event} e The triggered event.
118     * @private
119     */
120    buttonUp_: function(e) {
121      this.clearTimeout_();
122    },
123
124    /**
125     * Resets the interval callback.
126     * @private
127     */
128    clearTimeout_: function() {
129      if (typeof this.armRepeaterCallbackId_ != 'undefined') {
130        clearTimeout(this.armRepeaterCallbackId_);
131        this.armRepeaterCallbackId_ = undefined;
132      }
133      if (typeof this.intervalCallbackId_ != 'undefined') {
134        clearInterval(this.intervalCallbackId_);
135        this.intervalCallbackId_ = undefined;
136      }
137    },
138
139    /**
140     * Dispatches the action associated with keeping this button pressed.
141     * @private
142     */
143    buttonHeld_: function() {
144      cr.dispatchSimpleEvent(this, RepeatingButton.Event.BUTTON_HELD);
145    },
146
147    /**
148     * Getter for the initial delay before repeating.
149     * @type {number} The delay in milliseconds.
150     */
151    get repeatDelay() {
152      return this.holdDelayTime_;
153    },
154
155    /**
156     * Setter for the initial delay before repeating.
157     * @type {number} The delay in milliseconds.
158     */
159    set repeatDelay(delay) {
160      this.holdDelayTime_ = delay;
161    },
162
163    /**
164     * Getter for the repeat interval.
165     * @type {number} The repeat interval in milliseconds.
166     */
167    get repeatInterval() {
168      return this.holdRepeatIntervalTime_;
169    },
170
171    /**
172     * Setter for the repeat interval.
173     * @type {number} The interval in milliseconds.
174     */
175   set repeatInterval(delay) {
176     this.holdRepeatIntervalTime_ = delay;
177   }
178  };
179
180  return {
181    RepeatingButton: RepeatingButton
182  };
183});
184
185