// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {isRTL} from 'chrome://resources/js/util.js';
import type {ViewerZoomToolbarElement} from './elements/viewer_zoom_toolbar.js';
/**
* Idle time in ms before the UI is hidden.
*/
const HIDE_TIMEOUT: number = 2000;
/**
* 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: number = 10;
/**
* Distance from right of the screen required to reveal toolbars.
*/
const TOOLBAR_REVEAL_DISTANCE_RIGHT: number = 150;
/**
* Distance from bottom of the screen required to reveal toolbars.
*/
const TOOLBAR_REVEAL_DISTANCE_BOTTOM: number = 250;
/**
* @param e Event to test.
* @param window Window to test against.
* @return True if the mouse is close to the bottom-right of the screen.
*/
function isMouseNearToolbar(e: MouseEvent, window: Window): boolean {
const atSide = isRTL() ?
e.x > window.innerWidth - TOOLBAR_REVEAL_DISTANCE_RIGHT :
e.x < TOOLBAR_REVEAL_DISTANCE_RIGHT;
const atBottom = e.y > window.innerHeight - TOOLBAR_REVEAL_DISTANCE_BOTTOM;
return atSide && atBottom;
}
// Responsible for showing and hiding the zoom toolbar.
export class ToolbarManager {
private window_: Window;
private zoomToolbar_: ViewerZoomToolbarElement;
private toolbarTimeout_: number|null = null;
private isMouseNearToolbar_: boolean = false;
private keyboardNavigationActive_: boolean = false;
private lastMovementTimestamp_: number|null = null;
/**
* @param window The window containing the UI.
*/
constructor(window: Window, zoomToolbar: ViewerZoomToolbarElement) {
this.window_ = window;
this.zoomToolbar_ = zoomToolbar;
document.addEventListener('mousemove', e => this.handleMouseMove_(e));
document.addEventListener('mouseout', () => this.hideToolbarForMouseOut_());
this.zoomToolbar_.addEventListener('keyboard-navigation-active', e => {
this.keyboardNavigationActive_ = (e as CustomEvent).detail;
});
}
private handleMouseMove_(e: MouseEvent) {
this.isMouseNearToolbar_ = isMouseNearToolbar(e, this.window_);
this.keyboardNavigationActive_ = false;
const touchInteractionActive =
e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents;
// Tapping the screen with toolbars open tries to close them.
if (touchInteractionActive && this.zoomToolbar_.isVisible()) {
this.hideToolbarIfAllowed_();
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.isMouseNearToolbar_ || this.isHighVelocityMouseMove_(e) ||
touchInteractionActive) {
this.zoomToolbar_.show();
}
this.hideToolbarAfterTimeout();
}
/**
* Whether a mousemove event is high enough velocity to reveal the toolbars.
*/
private isHighVelocityMouseMove_(e: MouseEvent): boolean {
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.
*/
getCurrentTimestamp(): number {
return Date.now();
}
/**
* Show toolbar and mark that navigation is being performed with
* tab/shift-tab. This disables toolbar hiding until the mouse is moved or
* escape is pressed.
*/
showToolbarForKeyboardNavigation() {
this.keyboardNavigationActive_ = true;
this.zoomToolbar_.show();
}
/**
* 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.
*/
private hideToolbarForMouseOut_() {
this.isMouseNearToolbar_ = false;
this.hideToolbarAfterTimeout();
}
/**
* Check if the toolbar is able to be closed, and close it if it is.
* Toolbar may be kept open based on mouse/keyboard activity and active
* elements.
*/
private hideToolbarIfAllowed_() {
if (this.isMouseNearToolbar_ || 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 (document.activeElement === this.zoomToolbar_) {
this.zoomToolbar_.blur();
}
this.zoomToolbar_.hide();
}
/** Hide the toolbar after the HIDE_TIMEOUT has elapsed. */
hideToolbarAfterTimeout() {
if (this.toolbarTimeout_) {
this.window_.clearTimeout(this.toolbarTimeout_);
}
this.toolbarTimeout_ = this.window_.setTimeout(
this.hideToolbarIfAllowed_.bind(this), HIDE_TIMEOUT);
}
/**
* Clears the keyboard navigation state and hides the toolbars after a delay.
*/
resetKeyboardNavigationAndHideToolbar() {
this.keyboardNavigationActive_ = false;
this.hideToolbarAfterTimeout();
}
}