blob: bdd3427e2d89b9ed13604d7a994e5bcdcc59cc63 [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.
'use strict';
/** Idle time in ms before the UI is hidden. */
const HIDE_TIMEOUT = 2000;
/** Time in ms after force hide before toolbar is shown again. */
const FORCE_HIDE_TIMEOUT = 1000;
/**
* Velocity required in a mousemove to reveal the UI (pixels/ms). This is
* intended to be high enough that a fast flick of the mouse is required to
* reach it.
*/
const SHOW_VELOCITY = 10;
/** Distance from the top of the screen required to reveal the toolbars. */
const TOP_TOOLBAR_REVEAL_DISTANCE = 100;
/** Distance from the bottom-right of the screen required to reveal toolbars. */
const SIDE_TOOLBAR_REVEAL_DISTANCE_RIGHT = 150;
const SIDE_TOOLBAR_REVEAL_DISTANCE_BOTTOM = 250;
/**
* @param {MouseEvent} e Event to test.
* @return {boolean} True if the mouse is close to the top of the screen.
*/
function isMouseNearTopToolbar(e) {
return e.y < TOP_TOOLBAR_REVEAL_DISTANCE;
}
/**
* @param {MouseEvent} e Event to test.
* @param {Window} window Window to test against.
* @return {boolean} True if the mouse is close to the bottom-right of the
* screen.
*/
function isMouseNearSideToolbar(e, window) {
let atSide = e.x > window.innerWidth - SIDE_TOOLBAR_REVEAL_DISTANCE_RIGHT;
if (isRTL()) {
atSide = e.x < SIDE_TOOLBAR_REVEAL_DISTANCE_RIGHT;
}
const atBottom =
e.y > window.innerHeight - SIDE_TOOLBAR_REVEAL_DISTANCE_BOTTOM;
return atSide && atBottom;
}
/**
* Constructs a Toolbar Manager, responsible for co-ordinating between multiple
* toolbar elements.
*
* @param {Object} window The window containing the UI.
* @param {Object} toolbar The top toolbar element.
* @param {Object} zoomToolbar The zoom toolbar element.
* @constructor
*/
function ToolbarManager(window, toolbar, zoomToolbar) {
this.window_ = window;
this.toolbar_ = toolbar;
this.zoomToolbar_ = zoomToolbar;
this.toolbarTimeout_ = null;
this.isMouseNearTopToolbar_ = false;
this.isMouseNearSideToolbar_ = false;
this.sideToolbarAllowedOnly_ = false;
this.sideToolbarAllowedOnlyTimer_ = null;
this.keyboardNavigationActive = false;
this.lastMovementTimestamp = null;
this.window_.addEventListener('resize', this.resizeDropdowns_.bind(this));
this.resizeDropdowns_();
}
ToolbarManager.prototype = {
handleMouseMove: function(e) {
this.isMouseNearTopToolbar_ = this.toolbar_ && isMouseNearTopToolbar(e);
this.isMouseNearSideToolbar_ = isMouseNearSideToolbar(e, this.window_);
this.keyboardNavigationActive = false;
const touchInteractionActive =
(e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents);
// Allow the top toolbar to be shown if the mouse moves away from the side
// toolbar (as long as the timeout has elapsed).
if (!this.isMouseNearSideToolbar_ && !this.sideToolbarAllowedOnlyTimer_) {
this.sideToolbarAllowedOnly_ = false;
}
// Allow the top toolbar to be shown if the mouse moves to the top edge.
if (this.isMouseNearTopToolbar_) {
this.sideToolbarAllowedOnly_ = false;
}
// Tapping the screen with toolbars open tries to close them.
if (touchInteractionActive && this.zoomToolbar_.isVisible()) {
this.hideToolbarsIfAllowed();
return;
}
// Show the toolbars if the mouse is near the top or bottom-right of the
// screen, if the mouse moved fast, or if the touchscreen was tapped.
if (this.isMouseNearTopToolbar_ || this.isMouseNearSideToolbar_ ||
this.isHighVelocityMouseMove_(e) || touchInteractionActive) {
if (this.sideToolbarAllowedOnly_) {
this.zoomToolbar_.show();
} else {
this.showToolbars();
}
}
this.hideToolbarsAfterTimeout();
},
/**
* Whether a mousemove event is high enough velocity to reveal the toolbars.
*
* @param {MouseEvent} e Event to test.
* @return {boolean} true if the event is a high velocity mousemove, false
* otherwise.
* @private
*/
isHighVelocityMouseMove_: function(e) {
if (e.type == 'mousemove') {
if (this.lastMovementTimestamp == null) {
this.lastMovementTimestamp = this.getCurrentTimestamp_();
} else {
const movement =
Math.sqrt(e.movementX * e.movementX + e.movementY * e.movementY);
const newTime = this.getCurrentTimestamp_();
const interval = newTime - this.lastMovementTimestamp;
this.lastMovementTimestamp = newTime;
if (interval != 0) {
return movement / interval > SHOW_VELOCITY;
}
}
}
return false;
},
/**
* Wrapper around Date.now() to make it easily replaceable for testing.
*
* @return {number}
* @private
*/
getCurrentTimestamp_: function() {
return Date.now();
},
/**
* Display both UI toolbars.
*/
showToolbars: function() {
if (this.toolbar_) {
this.toolbar_.show();
}
this.zoomToolbar_.show();
},
/**
* Show toolbars and mark that navigation is being performed with
* tab/shift-tab. This disables toolbar hiding until the mouse is moved or
* escape is pressed.
*/
showToolbarsForKeyboardNavigation: function() {
this.keyboardNavigationActive = true;
this.showToolbars();
},
/**
* Hide toolbars after a delay, regardless of the position of the mouse.
* Intended to be called when the mouse has moved out of the parent window.
*/
hideToolbarsForMouseOut: function() {
this.isMouseNearTopToolbar_ = false;
this.isMouseNearSideToolbar_ = false;
this.hideToolbarsAfterTimeout();
},
/**
* Check if the toolbars are able to be closed, and close them if they are.
* Toolbars may be kept open based on mouse/keyboard activity and active
* elements.
*/
hideToolbarsIfAllowed: function() {
if (this.isMouseNearSideToolbar_ || this.isMouseNearTopToolbar_) {
return;
}
if (this.toolbar_ && this.toolbar_.shouldKeepOpen()) {
return;
}
if (this.keyboardNavigationActive) {
return;
}
// Remove focus to make any visible tooltips disappear -- otherwise they'll
// still be visible on screen when the toolbar is off screen.
if ((this.toolbar_ && document.activeElement == this.toolbar_) ||
document.activeElement == this.zoomToolbar_) {
document.activeElement.blur();
}
if (this.toolbar_) {
this.toolbar_.hide();
}
this.zoomToolbar_.hide();
},
/**
* Hide the toolbar after the HIDE_TIMEOUT has elapsed.
*/
hideToolbarsAfterTimeout: function() {
if (this.toolbarTimeout_) {
this.window_.clearTimeout(this.toolbarTimeout_);
}
this.toolbarTimeout_ = this.window_.setTimeout(
this.hideToolbarsIfAllowed.bind(this), HIDE_TIMEOUT);
},
/**
* Hide the 'topmost' layer of toolbars. Hides any dropdowns that are open, or
* hides the basic toolbars otherwise.
*/
hideSingleToolbarLayer: function() {
if (!this.toolbar_ || !this.toolbar_.hideDropdowns()) {
this.keyboardNavigationActive = false;
this.hideToolbarsIfAllowed();
}
},
/**
* Hide the top toolbar and keep it hidden until both:
* - The mouse is moved away from the right side of the screen
* - 1 second has passed.
*
* The top toolbar can be immediately re-opened by moving the mouse to the top
* of the screen.
*/
forceHideTopToolbar: function() {
if (!this.toolbar_) {
return;
}
this.toolbar_.hide();
this.sideToolbarAllowedOnly_ = true;
this.sideToolbarAllowedOnlyTimer_ = this.window_.setTimeout(() => {
this.sideToolbarAllowedOnlyTimer_ = null;
}, FORCE_HIDE_TIMEOUT);
},
/**
* Updates the size of toolbar dropdowns based on the positions of the rest of
* the UI.
*
* @private
*/
resizeDropdowns_: function() {
if (!this.toolbar_) {
return;
}
const lowerBound =
this.window_.innerHeight - this.zoomToolbar_.clientHeight;
this.toolbar_.setDropdownLowerBound(lowerBound);
}
};