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 when repeated intervals trigger. Initialized when the 42 * button is held for an initial delay period and cleared when the button 43 * is released. 44 * @type {function} 45 * @private 46 */ 47 intervalCallback_: 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 {function} 54 * @private 55 */ 56 armRepeaterCallback_: 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 this.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 (self.armRepeaterCallback_) { 105 self.armRepeaterCallback_ = undefined; 106 self.buttonHeld_(); 107 self.intervalCallback_ = setInterval(self.buttonHeld_.bind(self), 108 self.holdRepeatIntervalTime_); 109 } 110 }; 111 setTimeout(this.armRepeaterCallback_, this.holdDelayTime_); 112 }, 113 114 /** 115 * Called when the user releases this button. 116 * @param {!Event} e The triggered event. 117 * @private 118 */ 119 buttonUp_: function(e) { 120 this.clearTimeout_(); 121 }, 122 123 /** 124 * Resets the interval callback. 125 * @private 126 */ 127 clearTimeout_: function() { 128 if (this.armRepeaterCallback_) { 129 clearTimeout(this.armRepeaterCallback_); 130 this.armRepeaterCallback_ = undefined; 131 } 132 if (this.intervalCallback_) { 133 clearInterval(this.intervalCallback_); 134 this.intervalCallback_ = undefined; 135 } 136 }, 137 138 /** 139 * Dispatches the action associated with keeping this button pressed. 140 * @private 141 */ 142 buttonHeld_: function() { 143 cr.dispatchSimpleEvent(this, RepeatingButton.Event.BUTTON_HELD); 144 }, 145 146 /** 147 * Getter for the initial delay before repeating. 148 * @type {number} The delay in milliseconds. 149 */ 150 get repeatDelay() { 151 return this.holdDelayTime_; 152 }, 153 154 /** 155 * Setter for the initial delay before repeating. 156 * @type {number} The delay in milliseconds. 157 */ 158 set repeatDelay(delay) { 159 this.holdDelayTime_ = delay; 160 }, 161 162 /** 163 * Getter for the repeat interval. 164 * @type {number} The repeat interval in milliseconds. 165 */ 166 get repeatInterval() { 167 return this.holdRepeatIntervalTime_; 168 }, 169 170 /** 171 * Setter for the repeat interval. 172 * @type {number} The interval in milliseconds. 173 */ 174 set repeatInterval(delay) { 175 this.holdRepeatIntervalTime_ = delay; 176 } 177 }; 178 179 return { 180 RepeatingButton: RepeatingButton 181 }; 182}); 183 184