Skip to content

Commit

Permalink
Add method to enable/disable cooperativeGestures
Browse files Browse the repository at this point in the history
Fixes maplibre#2057

Also disable cooperative gestures in fullscreen, using these new methods
(Fixes maplibre#1488)
  • Loading branch information
kevinschaul committed Feb 7, 2023
1 parent d9be389 commit 6e9fc94
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 16 deletions.
57 changes: 57 additions & 0 deletions src/ui/control/fullscreen_control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,61 @@ describe('FullscreenControl', () => {
fullscreen._fullscreenButton.dispatchEvent(click);
expect(fullscreenend).toHaveBeenCalled();
});

test('disables cooperative gestures when fullscreen becomes active', () => {
const cooperativeGestures = true;
const map = createMap({cooperativeGestures});
const fullscreen = new FullscreenControl({});

map.addControl(fullscreen);

const click = new window.Event('click');

// Simulate a click to the fullscreen button
fullscreen._fullscreenButton.dispatchEvent(click);
expect(map.getCooperativeGestures()).toBeFalsy();

// Second simulated click would exit fullscreen mode
fullscreen._fullscreenButton.dispatchEvent(click);
expect(map.getCooperativeGestures()).toBe(cooperativeGestures);
});

test('reenables cooperative gestures custom options when fullscreen exits', () => {
const cooperativeGestures = {
'windowsHelpText': 'Custom message',
'macHelpText': 'Custom message',
'mobileHelpText': 'Custom message',
};
const map = createMap({cooperativeGestures});
const fullscreen = new FullscreenControl({});

map.addControl(fullscreen);

const click = new window.Event('click');

// Simulate a click to the fullscreen button
fullscreen._fullscreenButton.dispatchEvent(click);
expect(map.getCooperativeGestures()).toBeFalsy();

// Second simulated click would exit fullscreen mode
fullscreen._fullscreenButton.dispatchEvent(click);
expect(map.getCooperativeGestures()).toEqual(cooperativeGestures);
});

test('if never set, cooperative gestures remain disabled when fullscreen exits', () => {
const map = createMap({cooperativeGestures: false});
const fullscreen = new FullscreenControl({});

map.addControl(fullscreen);

const click = new window.Event('click');

// Simulate a click to the fullscreen button
fullscreen._fullscreenButton.dispatchEvent(click);
expect(map.getCooperativeGestures()).toBeFalsy();

// Second simulated click would exit fullscreen mode
fullscreen._fullscreenButton.dispatchEvent(click);
expect(map.getCooperativeGestures()).toBeFalsy();
});
});
12 changes: 12 additions & 0 deletions src/ui/control/fullscreen_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {warnOnce} from '../../util/util';
import {Event, Evented} from '../../util/evented';
import type Map from '../map';
import type {IControl} from './control';
import type {GestureOptions} from '../map';

type FullscreenOptions = {
container?: HTMLElement;
Expand All @@ -13,6 +14,8 @@ type FullscreenOptions = {
/**
* A `FullscreenControl` control contains a button for toggling the map in and out of fullscreen mode.
* When [requestFullscreen](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen) is not supported, fullscreen is handled via CSS properties.
* The map's `cooperativeGestures` option is temporarily disabled while the map
* is in fullscreen mode, and is restored when the map exist fullscreen mode.
*
* @implements {IControl}
* @param {Object} [options]
Expand Down Expand Up @@ -46,6 +49,7 @@ class FullscreenControl extends Evented implements IControl {
_fullscreenchange: string;
_fullscreenButton: HTMLButtonElement;
_container: HTMLElement;
_prevCooperativeGestures: boolean | GestureOptions;

constructor(options: FullscreenOptions = {}) {
super();
Expand Down Expand Up @@ -127,8 +131,16 @@ class FullscreenControl extends Evented implements IControl {

if (this._fullscreen) {
this.fire(new Event('fullscreenstart'));
if (this._map._cooperativeGestures) {
this._prevCooperativeGestures = this._map._cooperativeGestures;
this._map.setCooperativeGestures();
}
} else {
this.fire(new Event('fullscreenend'));
if (this._prevCooperativeGestures) {
this._map.setCooperativeGestures(this._prevCooperativeGestures);
delete this._prevCooperativeGestures;
}
}
}

Expand Down
112 changes: 101 additions & 11 deletions src/ui/handler/cooperative_gestures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import DOM from '../../util/dom';
import simulate from '../../../test/unit/lib/simulate_interaction';
import {setMatchMedia, setPerformance, setWebGlContext} from '../../util/test/util';

function createMap() {
function createMap(cooperativeGestures) {
return new Map({
container: DOM.create('div', '', window.document.body),
style: {
'version': 8,
'sources': {},
'layers': []
},
cooperativeGestures: true
cooperativeGestures
});
}

Expand All @@ -29,7 +29,7 @@ describe('CoopGesturesHandler', () => {
let now = 1555555555555;
browserNow.mockReturnValue(now);

const map = createMap();
const map = createMap(true);
map._renderTaskQueue.run();

const startZoom = map.getZoom();
Expand All @@ -47,21 +47,42 @@ describe('CoopGesturesHandler', () => {
map.remove();
});

test('Zooms on wheel if control key is down', () => {
// NOTE: This should pass regardless of whether cooperativeGestures is enabled or not
test('Zooms on wheel if no key is down after disabling cooperative gestures', () => {
const browserNow = jest.spyOn(browser, 'now');
let now = 1555555555555;
browserNow.mockReturnValue(now);

const map = createMap();
const map = createMap(true);
map.setCooperativeGestures(false);
map._renderTaskQueue.run();

simulate.keydown(document, {type: 'keydown', key: 'Control'});
const startZoom = map.getZoom();
// simulate a single 'wheel' event
simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta});
map._renderTaskQueue.run();

now += 400;
browserNow.mockReturnValue(now);
map._renderTaskQueue.run();

const endZoom = map.getZoom();
expect(endZoom - startZoom).toBeCloseTo(0.0285, 3);

map.remove();
});

test('Zooms on wheel if control key is down', () => {
// NOTE: This should pass regardless of whether cooperativeGestures is enabled or not
const browserNow = jest.spyOn(browser, 'now');
let now = 1555555555555;
browserNow.mockReturnValue(now);

const map = createMap(true);
map._renderTaskQueue.run();

const startZoom = map.getZoom();
// simulate a single 'wheel' event
simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta});
simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, ctrlKey: true});
map._renderTaskQueue.run();

now += 400;
Expand All @@ -75,7 +96,7 @@ describe('CoopGesturesHandler', () => {
});

test('Does not pan on touchmove with a single touch', () => {
const map = createMap();
const map = createMap(true);
const target = map.getCanvas();
const startCenter = map.getCenter();
map._renderTaskQueue.run();
Expand Down Expand Up @@ -104,9 +125,40 @@ describe('CoopGesturesHandler', () => {
map.remove();
});

test('Pans on touchmove with a single touch after disabling cooperative gestures', () => {
const map = createMap(true);
map.setCooperativeGestures(false);
const target = map.getCanvas();
const startCenter = map.getCenter();
map._renderTaskQueue.run();

const dragstart = jest.fn();
const drag = jest.fn();
const dragend = jest.fn();

map.on('dragstart', dragstart);
map.on('drag', drag);
map.on('dragend', dragend);

simulate.touchstart(target, {touches: [{target, clientX: 0, clientY: 0}, {target, clientX: 1, clientY: 1}]});
map._renderTaskQueue.run();

simulate.touchmove(target, {touches: [{target, clientX: 10, clientY: 10}, {target, clientX: 11, clientY: 11}]});
map._renderTaskQueue.run();

simulate.touchend(target);
map._renderTaskQueue.run();

const endCenter = map.getCenter();
expect(endCenter.lng).toBeGreaterThan(startCenter.lng);
expect(endCenter.lat).toBeGreaterThan(startCenter.lat);

map.remove();
});

test('Does pan on touchmove with a double touch', () => {
// NOTE: This should pass regardless of whether cooperativeGestures is enabled or not
const map = createMap();
const map = createMap(true);
const target = map.getCanvas();
const startCenter = map.getCenter();
map._renderTaskQueue.run();
Expand Down Expand Up @@ -137,7 +189,7 @@ describe('CoopGesturesHandler', () => {

test('Drag pitch works with 3 fingers', () => {
// NOTE: This should pass regardless of whether cooperativeGestures is enabled or not
const map = createMap();
const map = createMap(true);
const target = map.getCanvas();
const startPitch = map.getPitch();
map._renderTaskQueue.run();
Expand All @@ -164,4 +216,42 @@ describe('CoopGesturesHandler', () => {

map.remove();
});

test('Initially disabled cooperative gestures can be later enabled', () => {
const browserNow = jest.spyOn(browser, 'now');
let now = 1555555555555;
browserNow.mockReturnValue(now);

const map = createMap(false);
map._renderTaskQueue.run();

const startZoom = map.getZoom();
// simulate a single 'wheel' event
simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta});
map._renderTaskQueue.run();

now += 400;
browserNow.mockReturnValue(now);
map._renderTaskQueue.run();

const midZoom = map.getZoom();
expect(midZoom - startZoom).toBeCloseTo(0.0285, 3);

// Enable cooperative gestures
map.setCooperativeGestures(true);

// This 'wheel' event should not zoom
simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta});
map._renderTaskQueue.run();

now += 400;
browserNow.mockReturnValue(now);
map._renderTaskQueue.run();

const endZoom = map.getZoom();
expect(endZoom).toBeCloseTo(midZoom);

map.remove();
});

});
56 changes: 56 additions & 0 deletions src/ui/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,62 @@ describe('Map', () => {
await sourcePromise;
});

describe('#setCooperativeGestures', () => {
test('returns self', () => {
const map = createMap();
expect(map.setCooperativeGestures(true)).toBe(map);
});

test('can be called more than once', () => {
const map = createMap();
map.setCooperativeGestures(true);
map.setCooperativeGestures(true);
});

test('calling set with no arguments turns cooperative gestures off', done => {
const map = createMap({cooperativeGestures: true});
map.on('load', () => {
map.setCooperativeGestures();
expect(map.getCooperativeGestures()).toBeFalsy();
done();
});
});
});

describe('#getCooperativeGestures', () => {
test('returns the cooperative gestures option', done => {
const map = createMap({cooperativeGestures: true});

map.on('load', () => {
expect(map.getCooperativeGestures()).toBe(true);
done();
});
});

test('returns falsy if cooperative gestures option is not specified', done => {
const map = createMap();

map.on('load', () => {
expect(map.getCooperativeGestures()).toBeFalsy();
done();
});
});

test('returns the cooperative gestures option with custom messages', done => {
const option = {
'windowsHelpText': 'Custom message',
'macHelpText': 'Custom message',
'mobileHelpText': 'Custom message',
};
const map = createMap({cooperativeGestures: option});

map.on('load', () => {
expect(map.getCooperativeGestures()).toEqual(option);
done();
});
});
});

describe('getCameraTargetElevation', () => {
test('Elevation is zero without terrain, and matches any given terrain', () => {
const map = createMap();
Expand Down
Loading

0 comments on commit 6e9fc94

Please sign in to comment.