| // 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); |
| } |
| }; |