Merge pull request #4324 from itsjohncs/gestures-next-channel
Two-finger swipe now switches windows (#3901)
This commit is contained in:
commit
1d33e0195a
@ -87,6 +87,36 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 v-if="isTouch">Gestures</h2>
|
||||||
|
|
||||||
|
<div v-if="isTouch" class="help-item">
|
||||||
|
<div class="subject gesture">Single-Finger Swipe Left</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Hide sidebar.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isTouch" class="help-item">
|
||||||
|
<div class="subject gesture">Single-Finger Swipe Right</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Show sidebar.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isTouch" class="help-item">
|
||||||
|
<div class="subject gesture">Two-Finger Swipe Left</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Switch to the next window in the channel list.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isTouch" class="help-item">
|
||||||
|
<div class="subject gesture">Two-Finger Swipe Right</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Switch to the previous window in the channel list.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Keyboard Shortcuts</h2>
|
<h2>Keyboard Shortcuts</h2>
|
||||||
|
|
||||||
<div class="help-item">
|
<div class="help-item">
|
||||||
@ -774,6 +804,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isApple: navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) || false,
|
isApple: navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) || false,
|
||||||
|
isTouch: navigator.maxTouchPoints > 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2038,6 +2038,10 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#help .help-item .subject.gesture {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
#help .help-item .description p {
|
#help .help-item .description p {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
108
client/js/helpers/listenForTwoFingerSwipes.js
Normal file
108
client/js/helpers/listenForTwoFingerSwipes.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// onTwoFingerSwipe will be called with a cardinal direction ("n", "e", "s" or
|
||||||
|
// "w") as its only argument.
|
||||||
|
function listenForTwoFingerSwipes(onTwoFingerSwipe) {
|
||||||
|
let history = [];
|
||||||
|
|
||||||
|
document.body.addEventListener(
|
||||||
|
"touchmove",
|
||||||
|
function (event) {
|
||||||
|
if (event.touches.length !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = event.touches.item(0);
|
||||||
|
const b = event.touches.item(1);
|
||||||
|
|
||||||
|
const timestamp = window.performance.now();
|
||||||
|
const center = [(a.screenX + b.screenX) / 2, (a.screenY + b.screenY) / 2];
|
||||||
|
|
||||||
|
if (history.length > 0) {
|
||||||
|
const last = history[history.length - 1];
|
||||||
|
const centersAreEqual =
|
||||||
|
last.center[0] === center[0] && last.center[1] === center[1];
|
||||||
|
|
||||||
|
if (last.timestamp === timestamp || centersAreEqual) {
|
||||||
|
// Touches with the same timestamps or center don't help us
|
||||||
|
// see the speed of movement. Ignore them.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push({timestamp, center});
|
||||||
|
},
|
||||||
|
{passive: true}
|
||||||
|
);
|
||||||
|
|
||||||
|
document.body.addEventListener(
|
||||||
|
"touchend",
|
||||||
|
function () {
|
||||||
|
if (event.touches.length >= 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const direction = getSwipe(history);
|
||||||
|
|
||||||
|
if (direction) {
|
||||||
|
onTwoFingerSwipe(direction);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
history = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{passive: true}
|
||||||
|
);
|
||||||
|
|
||||||
|
document.body.addEventListener(
|
||||||
|
"touchcancel",
|
||||||
|
function () {
|
||||||
|
history = [];
|
||||||
|
},
|
||||||
|
{passive: true}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the cardinal direction of the swipe or null if there is no swipe.
|
||||||
|
function getSwipe(hist) {
|
||||||
|
// Speed is in pixels/millisecond. Must be maintained throughout swipe.
|
||||||
|
const MIN_SWIPE_SPEED = 0.2;
|
||||||
|
|
||||||
|
if (hist.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < hist.length; ++i) {
|
||||||
|
const previous = hist[i - 1];
|
||||||
|
const current = hist[i];
|
||||||
|
|
||||||
|
const speed =
|
||||||
|
distance(previous.center, current.center) /
|
||||||
|
Math.abs(previous.timestamp - current.timestamp);
|
||||||
|
|
||||||
|
if (speed < MIN_SWIPE_SPEED) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCardinalDirection(hist[0].center, hist[hist.length - 1].center);
|
||||||
|
}
|
||||||
|
|
||||||
|
function distance([x1, y1], [x2, y2]) {
|
||||||
|
return Math.hypot(x1 - x2, y1 - y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCardinalDirection([x1, y1], [x2, y2]) {
|
||||||
|
// If θ is the angle of the vector then this is tan(θ)
|
||||||
|
const tangent = (y2 - y1) / (x2 - x1);
|
||||||
|
|
||||||
|
// All values of |tan(-45° to 45°)| are less than 1, same for 145° to 225°
|
||||||
|
if (Math.abs(tangent) < 1) {
|
||||||
|
return x1 < x2 ? "e" : "w";
|
||||||
|
}
|
||||||
|
|
||||||
|
return y1 < y2 ? "s" : "n";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default listenForTwoFingerSwipes;
|
@ -6,6 +6,7 @@ import store from "./store";
|
|||||||
import {switchToChannel, router, navigate} from "./router";
|
import {switchToChannel, router, navigate} from "./router";
|
||||||
import isChannelCollapsed from "./helpers/isChannelCollapsed";
|
import isChannelCollapsed from "./helpers/isChannelCollapsed";
|
||||||
import isIgnoredKeybind from "./helpers/isIgnoredKeybind";
|
import isIgnoredKeybind from "./helpers/isIgnoredKeybind";
|
||||||
|
import listenForTwoFingerSwipes from "./helpers/listenForTwoFingerSwipes";
|
||||||
|
|
||||||
// Switch to the next/previous window in the channel list.
|
// Switch to the next/previous window in the channel list.
|
||||||
Mousetrap.bind(["alt+up", "alt+down"], function (e, keys) {
|
Mousetrap.bind(["alt+up", "alt+down"], function (e, keys) {
|
||||||
@ -13,11 +14,22 @@ Mousetrap.bind(["alt+up", "alt+down"], function (e, keys) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.state.networks.length === 0) {
|
navigateWindow(keys.split("+").pop() === "up" ? -1 : 1);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
listenForTwoFingerSwipes(function (cardinalDirection) {
|
||||||
|
if (cardinalDirection === "e" || cardinalDirection === "w") {
|
||||||
|
navigateWindow(cardinalDirection === "e" ? -1 : 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function navigateWindow(direction) {
|
||||||
|
if (store.state.networks.length === 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const direction = keys.split("+").pop() === "up" ? -1 : 1;
|
|
||||||
const flatChannels = [];
|
const flatChannels = [];
|
||||||
let index = -1;
|
let index = -1;
|
||||||
|
|
||||||
@ -44,9 +56,7 @@ Mousetrap.bind(["alt+up", "alt+down"], function (e, keys) {
|
|||||||
index = (((index + direction) % length) + length) % length;
|
index = (((index + direction) % length) + length) % length;
|
||||||
|
|
||||||
jumpToChannel(flatChannels[index]);
|
jumpToChannel(flatChannels[index]);
|
||||||
|
}
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Switch to the next/previous lobby in the channel list
|
// Switch to the next/previous lobby in the channel list
|
||||||
Mousetrap.bind(["alt+shift+up", "alt+shift+down"], function (e, keys) {
|
Mousetrap.bind(["alt+shift+up", "alt+shift+down"], function (e, keys) {
|
||||||
|
Loading…
Reference in New Issue
Block a user