diff --git a/docs/src/3.1.Worldview.mdx b/docs/src/3.1.Worldview.mdx index 390b07810..90e95db8e 100755 --- a/docs/src/3.1.Worldview.mdx +++ b/docs/src/3.1.Worldview.mdx @@ -8,6 +8,7 @@ | `onCameraStateChange` | `(CameraState) => void` | | callback whenever the camera state changes | | `defaultCameraState` | `$Shape` | `DEFAULT_CAMERA_STATE` | pass in this prop to turn Worldview to an uncontrolled component. `onCameraStateChange` will be ignored | | `keyMap` | `CameraKeyMap` | {} | override default Worldview hotkeys | +| `shiftKeys` | `boolean` | true | enable/disable shift modifier for keyboard controls | | `backgroundColor` | `Vec4` | `[0,0,0,1]` | change the scene background color | | `hitmapOnMouseMove` | `boolean` | | if enabled, the clicked objectId will be returned from the mouse event handler | | `children` | `React.Node` | | regl components to render inside the Worldview | @@ -27,18 +28,20 @@ See [MouseEvents](#/docs/api/mouse-events). ## Keyboard Controls -`keyMap` allows you to customize the keyboard controls for Worldview's camera. The `keyMap` is an optional object whose keys are [KeyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) values, and whose values specify the action to perform when the corresponding key is pressed. To disable a key from the keyboard controls, simply provide a `null`/`false` value for that key. For example, the following would reverse all actions and disable tilting and zooming: +`keyMap` allows you to customize the keyboard controls for Worldview's camera. The `keyMap` is an optional object whose keys are [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) values, and whose values specify the action to perform when the corresponding key is pressed. To disable a key from the keyboard controls, simply provide a `null`/`false` value for that key. For example, the following would reverse all actions and disable tilting and zooming: + +When the Shift key is pressed along with keys in the `keyMap`, the motion will be slowed. To disable this behavior, pass `shiftKeys={false}`. diff --git a/packages/regl-worldview/package-lock.json b/packages/regl-worldview/package-lock.json index 4881f5bf1..54f2d5908 100755 --- a/packages/regl-worldview/package-lock.json +++ b/packages/regl-worldview/package-lock.json @@ -1,6 +1,6 @@ { "name": "regl-worldview", - "version": "0.0.13", + "version": "0.0.21", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/regl-worldview/src/Worldview.js b/packages/regl-worldview/src/Worldview.js index 19d1d89e6..62fcf7b20 100755 --- a/packages/regl-worldview/src/Worldview.js +++ b/packages/regl-worldview/src/Worldview.js @@ -23,6 +23,7 @@ export const DEFAULT_MOUSE_CLICK_RADIUS = 3; export type BaseProps = {| keyMap?: CameraKeyMap, + shiftKeys: boolean, backgroundColor?: Vec4, // rendering the hitmap on mouse move is expensive, so disable it by default hitmapOnMouseMove?: boolean, @@ -74,6 +75,7 @@ export class WorldviewBase extends React.Component { static defaultProps = { backgroundColor: DEFAULT_BACKGROUND_COLOR, + shiftKeys: true, style: {}, }; @@ -260,12 +262,12 @@ export class WorldviewBase extends React.Component { } render() { - const { width, height, showDebug, keyMap, style } = this.props; + const { width, height, showDebug, keyMap, shiftKeys, style } = this.props; const { worldviewContext } = this.state; return (
- + | null>, - onKeyDown?: (KeyboardEvent) => void, |}; // attaches mouse and keyboard listeners to allow for moving the camera on user input @@ -112,6 +112,7 @@ export default class CameraListener extends React.Component { } _getMoveMagnitude() { + // avoid interference with drawing tools if (this._ctrlKey) { return { x: 0, y: 0 }; } @@ -124,15 +125,13 @@ export default class CameraListener extends React.Component { if (perspective) { // in perspective mode its more like flying, so move by the magnitude // we use the camera distance as a heuristic - const magnitude = this._getMagnitude(distance); - - return { x: magnitude, y: magnitude }; + return { x: distance, y: distance }; } // in orthographic mode we know the exact viewable area // which is a square so we can move exactly percentage within it const { width, height } = this._rect; const bounds = getOrthographicBounds(distance, width, height); - return { x: this._getMagnitude(bounds.width), y: this._getMagnitude(bounds.height) }; + return { x: bounds.width, y: bounds.height }; } _onWindowMouseMove = (e: MouseEvent) => { @@ -174,7 +173,7 @@ export default class CameraListener extends React.Component { if (this._isLeftMouseDown()) { const { x, y } = this._getMoveMagnitude(); - cameraMove([moveX * x, -moveY * y]); + cameraMove([this._getMagnitude(moveX * x), this._getMagnitude(-moveY * y)]); } }; @@ -214,12 +213,16 @@ export default class CameraListener extends React.Component { } } - getKeyMotion = (key: string): ?KeyMotion => { + _getKeyMotion = (code: string): ?KeyMotion => { const moveSpeed = this._getMagnitude(KEYBOARD_MOVE_SPEED); const zoomSpeed = this._getMagnitude(KEYBOARD_ZOOM_SPEED); const spinSpeed = this._getMagnitude(KEYBOARD_SPIN_SPEED); - const { keyMap } = this.props; - const action: CameraAction | false = (keyMap && keyMap[key]) || DEFAULT_KEYMAP[key] || false; + const { keyMap, shiftKeys } = this.props; + const action: CameraAction | false = (keyMap && keyMap[code]) || DEFAULT_KEYMAP[code] || false; + + if (this._shiftKey && !shiftKeys) { + return null; + } switch (action) { case "moveRight": @@ -251,10 +254,10 @@ export default class CameraListener extends React.Component { } }; - moveKeyboard(dt: number) { + _moveKeyboard(dt: number) { const motion = { x: 0, y: 0, zoom: 0, yaw: 0, tilt: 0 }; - this._keys.forEach((key) => { - const { x = 0, y = 0, zoom = 0, yaw = 0, tilt = 0 } = this.getKeyMotion(key) || {}; + this._keys.forEach((code) => { + const { x = 0, y = 0, zoom = 0, yaw = 0, tilt = 0 } = this._getKeyMotion(code) || {}; motion.x += x; motion.y += y; motion.zoom += zoom; @@ -288,7 +291,7 @@ export default class CameraListener extends React.Component { return; } this._keyTimer = requestAnimationFrame((stamp) => { - this.moveKeyboard((lastStamp ? stamp - lastStamp : 0) / 1000); + this._moveKeyboard((lastStamp ? stamp - lastStamp : 0) / 1000); this._keyTimer = undefined; // Only start the timer if keys are still pressed. @@ -309,45 +312,45 @@ export default class CameraListener extends React.Component { this._keyTimer = undefined; } - _onKeyDown = (e: KeyboardEvent) => { - const { onKeyDown, keyMap } = this.props; + _onKeyDown = (e: SyntheticKeyboardEvent) => { + const { keyMap } = this.props; this._shiftKey = e.shiftKey; this._metaKey = e.metaKey; this._ctrlKey = e.ctrlKey; + const code = ((e.nativeEvent: any): KeyboardEvent).code; + + // ignore repeated keydown events + if (e.repeat || this._keys.has(code)) { + e.stopPropagation(); + e.preventDefault(); + return; + } if (e.altKey || e.ctrlKey || e.metaKey) { - if (onKeyDown) { - onKeyDown(e); - } // we don't currently handle these modifiers return; } // allow null, false, or empty keymappings which explicitly cancel Worldview from processing that key - if (keyMap && e.key in keyMap && !keyMap[e.key]) { + if (keyMap && code in keyMap && !keyMap[code]) { return false; } - // ignore repeated keydown events - if (this._keys.has(e.key)) { - e.stopPropagation(); - e.preventDefault(); - } else if (this.getKeyMotion(e.key)) { - this._keys.add(e.key); + // if we respond to this key, start the update timer + if (this._getKeyMotion(code)) { + this._keys.add(code); this._startKeyTimer(); e.stopPropagation(); e.preventDefault(); - } else if (onKeyDown) { - onKeyDown(e); } }; - _onKeyUp = (e: KeyboardEvent) => { + _onKeyUp = (e: SyntheticKeyboardEvent) => { this._shiftKey = e.shiftKey; this._metaKey = e.metaKey; this._ctrlKey = e.ctrlKey; - this._keys.delete(e.key); + this._keys.delete(((e.nativeEvent: any): KeyboardEvent).code); }; _onWheel = (e: WheelEvent) => {