Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Refactor gesture handling & improve gesture support on mobile #9223

Closed
wants to merge 9 commits into from
101 changes: 101 additions & 0 deletions src/ui/handler/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// @flow

import type Map from '../map';
import type {InputEvent} from '../handler_manager';
/**
* Base class for gesture handlers which control user interaction with the map
*/
class Handler {
_state: 'disabled' | 'enabled' | 'pending' | 'active';
_options: Object;
_el: HTMLElement;

constructor(map: Map, options: ?Object) {
// this._map = map;
this._el = map.getCanvasContainer();
this._options = {};
if (options) this.setOptions(options);
this._state = 'enabled';
}

/**
* Returns a Boolean indicating whether this handler's interaction is enabled.
*
* @returns {boolean} `true` if the interaction is enabled.
*/
isEnabled() {
return this._state !== 'disabled';
}


/**
* Enables this handler's interaction.
*
*/
enable() {
if (this.isEnabled()) return;
this._state = 'enabled';
}

/**
* Disables this handler's interaction.
*
*/
disable() {
if (!this.isEnabled()) return;
this._state = 'disabled';
}

/**
* Resets a possibly-active handler to enabled state.
*
*/
reset(e: ?InputEvent) {
if (this.isEnabled()) {
this.disable();
this.enable();
}
}

/**
* Set custom options for this handler.
*
* @param {Object} [options] Object in { option: value } format.
*/
setOptions(options: any) {
if (!options) throw new Error('Must provide a valid options object');
let unrecognized = [];
for (const property in options) {
if (this._options[property] === undefined) unrecognized.push(property);
else this._options[property] = options[property];
}
if (unrecognized.length > 0) throw new Error(`Unrecognized option${unrecognized.length > 1 ? 's' : ''}: ${unrecognized.join(', ')}`);
}

/**
* Get the current options for this handler.
*
* @returns {Object} Options object in { option: value } format.
*/
getOptions() {
return this._options;
}

/**
* Process an interaction event received by the map.
* The handler will receive every type of input event, but will only respond to event types for which it has a corresponding method.
* Handlers should implement event-type methods (e.g. .touchmove() or .mousedown()) for events they wish to respond to.
* Each event-type method should either return an object with target data for a map update, //TODO describe data object shape
* or return undefined if no update is required for the event.
*
* @param {Event} [event] Mouse, Touch, Keyboard or Wheel event to be processed
* @returns {Object | undefined} Data (e.g. new transform settings) to be used to update the map, or undefined if no update is required
*/
processInputEvent(e: MouseEvent | TouchEvent | KeyboardEvent | WheelEvent) {
if (!e || !e.type) return console.warn('Invalid input event:', e);
if (!this[e.type] || !(typeof this[e.type] === 'function')) return;
return this[e.type](e);
}
}

export default Handler;
84 changes: 25 additions & 59 deletions src/ui/handler/keyboard.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// @flow

import {bindAll} from '../../util/util';

import {extend} from '../../util/util';
import Handler from './handler.js';
import type Map from '../map';

const panStep = 100,
bearingStep = 15,
pitchStep = 10;
const defaultOptions = {
panStep: 100,
bearingStep: 15,
pitchStep: 10
};

/**
* The `KeyboardHandler` allows the user to zoom, rotate, and pan the map using
Expand All @@ -22,57 +24,25 @@ const panStep = 100,
* - `Shift+⇡`: Increase the pitch by 10 degrees.
* - `Shift+⇣`: Decrease the pitch by 10 degrees.
*/
class KeyboardHandler {
class KeyboardHandler extends Handler {
_map: Map;
_el: HTMLElement;
_enabled: boolean;

/**
* @private
*/
constructor(map: Map) {
this._map = map;
this._el = map.getCanvasContainer();

bindAll([
'_onKeyDown'
], this);
}

/**
* Returns a Boolean indicating whether keyboard interaction is enabled.
*
* @returns {boolean} `true` if keyboard interaction is enabled.
*/
isEnabled() {
return !!this._enabled;
}

/**
* Enables keyboard interaction.
*
* @example
* map.keyboard.enable();
*/
enable() {
if (this.isEnabled()) return;
this._el.addEventListener('keydown', this._onKeyDown, false);
this._enabled = true;
}
_panStep: number;
_bearingStep: number;
_pitchStep: number;

/**
* Disables keyboard interaction.
*
* @example
* map.keyboard.disable();
*/
disable() {
if (!this.isEnabled()) return;
this._el.removeEventListener('keydown', this._onKeyDown);
this._enabled = false;
* @private
*/
constructor(map: Map, options: ?Object) {
super(map, options);
this._map = map;
const stepOptions = extend(defaultOptions, options);
this._panStep = stepOptions.panStep;
this._bearingStep = stepOptions.bearingStep;
this._pitchStep = stepOptions.pitchStep;
}

_onKeyDown(e: KeyboardEvent) {
keydown(e: KeyboardEvent) {
if (e.altKey || e.ctrlKey || e.metaKey) return;

let zoomDir = 0;
Expand All @@ -99,7 +69,6 @@ class KeyboardHandler {
if (e.shiftKey) {
bearingDir = -1;
} else {
e.preventDefault();
xDir = -1;
}
break;
Expand All @@ -108,7 +77,6 @@ class KeyboardHandler {
if (e.shiftKey) {
bearingDir = 1;
} else {
e.preventDefault();
xDir = 1;
}
break;
Expand All @@ -117,7 +85,6 @@ class KeyboardHandler {
if (e.shiftKey) {
pitchDir = 1;
} else {
e.preventDefault();
yDir = -1;
}
break;
Expand All @@ -127,7 +94,6 @@ class KeyboardHandler {
pitchDir = -1;
} else {
yDir = 1;
e.preventDefault();
}
break;

Expand All @@ -144,13 +110,13 @@ class KeyboardHandler {
easing: easeOut,

zoom: zoomDir ? Math.round(zoom) + zoomDir * (e.shiftKey ? 2 : 1) : zoom,
bearing: map.getBearing() + bearingDir * bearingStep,
pitch: map.getPitch() + pitchDir * pitchStep,
offset: [-xDir * panStep, -yDir * panStep],
bearing: map.getBearing() + bearingDir * this._bearingStep,
pitch: map.getPitch() + pitchDir * this._pitchStep,
offset: [-xDir * this._panStep, -yDir * this._panStep],
center: map.getCenter()
};

map.easeTo(easeOptions, {originalEvent: e});
return { easeTo: easeOptions }
}
}

Expand Down
Loading