blob: a1fdbea86886ad0ca3b58946c1cec4e147c0005f [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Dimmable UI Controller.
* @param {!HTMLElement} container
* @constructor
* @struct
*/
function DimmableUIController(container) {
/**
* @private {!HTMLElement}
* @const
*/
this.container_ = container;
/**
* @private {NodeList}
*/
this.tools_ = null;
/**
* @private {number}
*/
this.timeoutId_ = 0;
/**
* @private {boolean}
*/
this.isCursorInTools_ = false;
/**
* @private {GalleryMode|undefined}
*/
this.mode_ = undefined;
/**
* @private {GallerySubMode|undefined}
*/
this.subMode_ = undefined;
/**
* @private {boolean}
*/
this.spokenFeedbackEnabled_ = false;
/**
* @private {boolean}
*/
this.loading_ = false;
/**
* @private {boolean}
*/
this.renaming_ = false;
/**
* @private {boolean}
*/
this.disabled_ = false;
/**
* @private {number}
*/
this.madeVisibleAt_ = 0;
this.container_.addEventListener('click', this.onClick_.bind(this));
this.container_.addEventListener('mousemove', this.onMousemove_.bind(this));
this.container_.addEventListener(
'touchstart', this.onTouchOperation_.bind(this));
this.container_.addEventListener(
'touchmove', this.onTouchOperation_.bind(this));
this.container_.addEventListener(
'touchend', this.onTouchOperation_.bind(this));
this.container_.addEventListener(
'touchcancel', this.onTouchOperation_.bind(this));
chrome.accessibilityFeatures.spokenFeedback.onChange.addListener(
this.onGetOrChangedSpokenFeedbackConfiguration_.bind(this));
chrome.accessibilityFeatures.spokenFeedback.get({},
this.onGetOrChangedSpokenFeedbackConfiguration_.bind(this));
}
/**
* Default timeout.
* @const {number}
*/
DimmableUIController.DEFAULT_TIMEOUT = 3000; // ms
/**
* We don't allow user to change visibility of tools shorter than this interval.
* This is necessary not to hide tools immediately after they become visible by
* touchstart event when user taps UI to make them visible.
* @const {number}
*/
DimmableUIController.MIN_OPERATION_INTERVAL = 500; // ms
/**
* Returns true if this controller should be disabled.
* @param {GalleryMode|undefined} mode
* @param {GallerySubMode|undefined} subMode
* @param {boolean} loading
* @param {boolean} spokenFeedbackEnabled
* @param {boolean} renaming
* @return {boolean}
*/
DimmableUIController.shouldBeDisabled = function(
mode, subMode, loading, spokenFeedbackEnabled, renaming) {
return spokenFeedbackEnabled || mode === undefined || subMode === undefined ||
mode === GalleryMode.THUMBNAIL ||
(mode === GalleryMode.SLIDE && subMode === GallerySubMode.EDIT) ||
(mode === GalleryMode.SLIDE && subMode === GallerySubMode.BROWSE &&
(loading || renaming));
};
/**
* Sets current mode of Gallery.
* @param {GalleryMode} mode
* @param {GallerySubMode} subMode
*/
DimmableUIController.prototype.setCurrentMode = function(mode, subMode) {
if (this.mode_ === mode && this.subMode_ === subMode) {
return;
}
this.mode_ = mode;
this.subMode_ = subMode;
this.updateAvailability_();
};
/**
* Sets whether user is renaming an image or not.
* @param {boolean} renaming
*/
DimmableUIController.prototype.setRenaming = function(renaming) {
if (this.renaming_ === renaming) {
return;
}
this.renaming_ = renaming;
this.updateAvailability_();
};
/**
* Sets whether gallery is currently loading an image or not.
* @param {boolean} loading
*/
DimmableUIController.prototype.setLoading = function(loading) {
if (this.loading_ === loading) {
return;
}
this.loading_ = loading;
this.updateAvailability_();
};
/**
* Handles click event.
* @param {!Event} event An event.
* @private
*/
DimmableUIController.prototype.onClick_ = function(event) {
if (this.disabled_ ||
(event.target &&
this.isPartOfTools_(/** @type {!HTMLElement} */ (event.target)))) {
return;
}
this.toggle_();
};
/**
* Handles mousemove event.
* @private
*/
DimmableUIController.prototype.onMousemove_ = function() {
if (this.disabled_) {
return;
}
this.kick();
};
/**
* Handles touch event.
* @private
*/
DimmableUIController.prototype.onTouchOperation_ = function() {
if (this.disabled_) {
return;
}
this.kick();
};
/**
* Handles mouseover event.
* @private
*/
DimmableUIController.prototype.onMouseover_ = function() {
if (this.disabled_) {
return;
}
this.isCursorInTools_ = true;
};
/**
* Handles mouseout event.
* @private
*/
DimmableUIController.prototype.onMouseout_ = function() {
if (this.disabled_) {
return;
}
this.isCursorInTools_ = false;
};
/**
* Returns true if element is a part of tools.
* @param {!HTMLElement} element A html element.
* @return {boolean} True if element is a part of tools.
* @private
*/
DimmableUIController.prototype.isPartOfTools_ = function(element) {
for (var i = 0; i < this.tools_.length; i++) {
if (this.tools_[i].contains(element)) {
return true;
}
}
return false;
};
/**
* Toggles visibility of UI.
* @private
*/
DimmableUIController.prototype.toggle_ = function() {
if (this.isToolsVisible_()) {
this.show_(false);
} else {
this.kick();
}
};
/**
* Returns true if UI is visible.
* @return {boolean} True if UI is visible.
* @private
*/
DimmableUIController.prototype.isToolsVisible_ = function() {
return this.container_.hasAttribute('tools');
};
/**
* Shows UI.
* @param {boolean} show True to show UI.
* @private
*/
DimmableUIController.prototype.show_ = function(show) {
if (this.isToolsVisible_() === show) {
return;
}
if (show) {
this.madeVisibleAt_ = Date.now();
this.container_.setAttribute('tools', true);
} else {
if (Date.now() - this.madeVisibleAt_ <
DimmableUIController.MIN_OPERATION_INTERVAL) {
return;
}
this.container_.removeAttribute('tools');
this.clearTimeout_();
}
};
/**
* Clears current timeout.
* @private
*/
DimmableUIController.prototype.clearTimeout_ = function() {
if (!this.timeoutId_) {
return;
}
clearTimeout(this.timeoutId_);
this.timeoutId_ = 0;
};
/**
* Extends current timeout.
* @param {number=} opt_timeout Timeout.
* @private
*/
DimmableUIController.prototype.extendTimeout_ = function(opt_timeout) {
this.clearTimeout_();
var timeout = opt_timeout || DimmableUIController.DEFAULT_TIMEOUT;
this.timeoutId_ = setTimeout(this.onTimeout_.bind(this), timeout);
};
/**
* Handles timeout.
* @private
*/
DimmableUIController.prototype.onTimeout_ = function() {
// If mouse cursor is on tools, extend timeout.
if (this.isCursorInTools_) {
this.extendTimeout_();
return;
}
this.show_(false /* hide */);
};
/**
* Updates availability of this controller with spoken feedback configuration.
* @param {Object} details
* @private
*/
DimmableUIController.prototype.onGetOrChangedSpokenFeedbackConfiguration_ =
function(details) {
this.spokenFeedbackEnabled_ = !!details.value;
this.updateAvailability_();
};
/**
* Sets tools which are controlled by this controller.
* This method must not be called more than once for an instance.
* @param {!NodeList} tools Tools.
*/
DimmableUIController.prototype.setTools = function(tools) {
assert(this.tools_ === null);
this.tools_ = tools;
for (var i = 0; i < this.tools_.length; i++) {
this.tools_[i].addEventListener('mouseover', this.onMouseover_.bind(this));
this.tools_[i].addEventListener('mouseout', this.onMouseout_.bind(this));
}
};
/**
* Shows UI and set timeout.
* @param {number=} opt_timeout Timeout.
*/
DimmableUIController.prototype.kick = function(opt_timeout) {
if (this.disabled_) {
return;
}
this.show_(true);
this.extendTimeout_(opt_timeout);
};
/**
* Updates availability.
* @private
*/
DimmableUIController.prototype.updateAvailability_ = function() {
var disabled = DimmableUIController.shouldBeDisabled(
this.mode_, this.subMode_, this.loading_, this.spokenFeedbackEnabled_,
this.renaming_);
if (this.disabled_ === disabled) {
return;
}
this.disabled_ = disabled;
if (this.disabled_) {
this.isCursorInTools_ = false;
this.show_(true);
this.clearTimeout_();
} else {
this.kick();
}
};
/**
* Sets cursor's state as out of tools. Mouseout event is not dispatched for
* some cases even when mouse cursor goes out of elements. This method is used
* to handle these cases manually.
*/
DimmableUIController.prototype.setCursorOutOfTools = function() {
this.isCursorInTools_ = false;
};