From 1baa5b95182c5ff8f53355ad9aa3747c8dae28a4 Mon Sep 17 00:00:00 2001 From: Willy Bruns Date: Sun, 21 Aug 2016 16:36:24 -0700 Subject: [PATCH] Only show tab previews if mouse is moving slow enough - fixes #3271: Dont show tab preview unless mouse velocity is low enough - tab preview requires that either: mouse be moving slower than a velocity threshold relative to tab size (so should be relative to resolution) mouse hover for longer than a new mouseover duration threshold (this may be unnecessary, velocity seems fine) - 50% higher mouse velocity is tolerated if user is in "preview mode" (moving mouse from one tab to another, see issue #1434) to avoid flashing out when mousing from one tab preview to another - velocity threshold is set in `constants/settings.js`: `tabs.tab-previews.mouse-velocity-threshold` - mouseover duration threshold is set in `constants/settings.js`: `tabs.tab-previews.mouseover-duration-threshold-ms` --- js/components/tab.js | 81 ++++++++++++++++++++++++++++++++++++++- js/constants/appConfig.js | 2 + js/constants/settings.js | 2 + 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/js/components/tab.js b/js/components/tab.js index c752bb85b69..06c435a10ab 100644 --- a/js/components/tab.js +++ b/js/components/tab.js @@ -1,6 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +const screen = require('electron').screen const React = require('react') @@ -16,6 +17,9 @@ const contextMenus = require('../contextMenus') const dnd = require('../dnd') const windowStore = require('../stores/windowStore') +const getSetting = require('../settings').getSetting +const settings = require('../constants/settings') + class Tab extends ImmutableComponent { constructor () { super() @@ -122,13 +126,86 @@ class Tab extends ImmutableComponent { onMouseEnter (e) { // relatedTarget inside mouseenter checks which element before this event was the pointer on // if this element has a tab-like class, then it's likely that the user was previewing - // a sequency of tabs. Called here as previewMode. + // a sequence of tabs. Called here as previewMode. const previewMode = /tab(?!pages)/i.test(e.relatedTarget.classList) // If user isn't in previewMode, we add a bit of delay to avoid tab from flashing out // as reported here: https://github.com/brave/browser-laptop/issues/1434 + let hoverTimeoutMs = 100 + if (previewMode) { + hoverTimeoutMs = 0 + } + + let scalingFactor = { // scale by component dim so higher resolution displays don't feel different + x: e.target.clientWidth, + y: e.target.clientHeight + } + + let getScaledCursorCoord = function () { + let cursorScreenPoint = screen.getCursorScreenPoint() + return { + x: (cursorScreenPoint.x / scalingFactor.x), + y: (cursorScreenPoint.y / scalingFactor.y), + timestamp: (new Date()).getTime() + } + } + + let getMouseDelta = function (mouseCoordA, mouseCoordB) { + let delta = { + x: mouseCoordA.x - mouseCoordB.x, + y: mouseCoordA.y - mouseCoordB.y, + dt: (mouseCoordA.timestamp - mouseCoordB.timestamp) / 1000 + } + + delta.magnitude = Math.sqrt(delta.x * delta.x + delta.y * delta.y) + delta.velocity = delta.magnitude / delta.dt // relative coords / sec (if will leave in 1s, this would be 1) + + return delta + } + + this.mouseVecData = { + enterTimestamp: (new Date().getTime()), + lastVec: getScaledCursorCoord(), + delta: null + } + + let hoverTimeoutFun = function () { + var curTimestamp = (new Date().getTime()) + + var mouseoverDuration = curTimestamp - this.mouseVecData.enterTimestamp + + let lastVec = this.mouseVecData.lastVec + let curVec = getScaledCursorCoord() + + let delta = getMouseDelta(curVec, lastVec) + let mouseVelocity = delta.velocity + + this.mouseVecData.lastVec = curVec + this.mouseVecData.delta = delta + + let TAB_PREVIEW_MOUSE_VELOCITY_THRESHOLD = getSetting(settings.TAB_PREVIEW_MOUSE_VELOCITY_THRESHOLD) + let TAB_PREVIEW_MOUSEOVER_DURATION_THRESHOLD = getSetting(settings.TAB_PREVIEW_MOUSEOVER_DURATION_THRESHOLD) + + // If user is in "preview mode", let's increase the tolerated velocity by 50% + if (previewMode) { + TAB_PREVIEW_MOUSE_VELOCITY_THRESHOLD *= 1.5 + } + + let criterion = mouseVelocity < TAB_PREVIEW_MOUSE_VELOCITY_THRESHOLD || mouseoverDuration > TAB_PREVIEW_MOUSEOVER_DURATION_THRESHOLD + + if (criterion) { + window.clearTimeout(this.hoverTimeout) + windowActions.setPreviewFrame(this.frame) + + this.mouseVecData.enterTimestamp = null + this.mouseVecData.delta = null + } else { + this.hoverTimeout = window.setTimeout(hoverTimeoutFun, 100) + } + }.bind(this) + this.hoverTimeout = - window.setTimeout(windowActions.setPreviewFrame.bind(null, this.frame), previewMode ? 0 : 200) + window.setTimeout(hoverTimeoutFun, hoverTimeoutMs) } onClickTab (e) { diff --git a/js/constants/appConfig.js b/js/constants/appConfig.js index ff203d01f2c..f8a3fc95bf0 100644 --- a/js/constants/appConfig.js +++ b/js/constants/appConfig.js @@ -96,6 +96,8 @@ module.exports = { 'tabs.paint-tabs': true, 'tabs.tabs-per-page': 10, 'tabs.show-tab-previews': true, + 'tabs.tab-previews.mouse-velocity-threshold': 0.5, + 'tabs.tab-previews.mouseover-duration-threshold-ms': 1250, 'privacy.history-suggestions': true, 'privacy.bookmark-suggestions': true, 'privacy.opened-tab-suggestions': true, diff --git a/js/constants/settings.js b/js/constants/settings.js index 7f489fa1574..ccecd8d39b3 100644 --- a/js/constants/settings.js +++ b/js/constants/settings.js @@ -19,6 +19,8 @@ const settings = { PAINT_TABS: 'tabs.paint-tabs', TABS_PER_PAGE: 'tabs.tabs-per-page', SHOW_TAB_PREVIEWS: 'tabs.show-tab-previews', + TAB_PREVIEW_MOUSE_VELOCITY_THRESHOLD: 'tabs.tab-previews.mouse-velocity-threshold', + TAB_PREVIEW_MOUSEOVER_DURATION_THRESHOLD: 'tabs.tab-previews.mouseover-duration-threshold-ms', // Privacy Tab HISTORY_SUGGESTIONS: 'privacy.history-suggestions', BOOKMARK_SUGGESTIONS: 'privacy.bookmark-suggestions',