feat(viewer): scope keyboard events to focused canvas (#5740)
This commit is contained in:
committed by
GitHub
parent
d96d1c5446
commit
ac383bca1f
@@ -39,6 +39,7 @@ const createViewer = async (containerName: string, _stream: string) => {
|
||||
const params = DefaultViewerParams
|
||||
params.showStats = true
|
||||
params.verbose = true
|
||||
// params.restrictInputToCanvas = true
|
||||
|
||||
const multiSelectList: SelectionEvent[] = []
|
||||
const viewer: Viewer = new Viewer(container, params)
|
||||
|
||||
@@ -39,7 +39,9 @@ export interface ViewerParams {
|
||||
showStats: boolean
|
||||
environmentSrc: Asset
|
||||
verbose: boolean
|
||||
restrictInputToCanvas: boolean
|
||||
}
|
||||
|
||||
export enum AssetType {
|
||||
TEXTURE_8BPP = 'png', // For now
|
||||
TEXTURE_HDR = 'hdr',
|
||||
@@ -71,7 +73,8 @@ export const DefaultViewerParams: ViewerParams = {
|
||||
id: 'defaultHDRI',
|
||||
src: defaultHdri,
|
||||
type: AssetType.TEXTURE_EXR
|
||||
}
|
||||
},
|
||||
restrictInputToCanvas: false
|
||||
}
|
||||
|
||||
export enum ViewerEvent {
|
||||
@@ -205,6 +208,7 @@ export interface IViewer {
|
||||
|
||||
getRenderer(): SpeckleRenderer
|
||||
getContainer(): HTMLElement
|
||||
getCanvas(): HTMLCanvasElement
|
||||
|
||||
createExtension<T extends Extension>(type: Constructor<T>): T
|
||||
getExtension<T extends Extension>(type: Constructor<T>): T
|
||||
|
||||
@@ -436,7 +436,10 @@ export default class SpeckleRenderer {
|
||||
|
||||
this._pipeline = new DefaultPipeline(this)
|
||||
|
||||
this.input = new Input(this._renderer.domElement)
|
||||
this.input = new Input(
|
||||
this._renderer.domElement,
|
||||
this.viewer.params.restrictInputToCanvas
|
||||
)
|
||||
this.input.on(InputEvent.Click, this.onClick.bind(this))
|
||||
this.input.on(InputEvent.DoubleClick, this.onDoubleClick.bind(this))
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@ export class Viewer extends EventEmitter implements IViewer {
|
||||
return this.speckleRenderer.input
|
||||
}
|
||||
|
||||
public get params(): ViewerParams {
|
||||
return this.startupParams
|
||||
}
|
||||
|
||||
private getConstructorChain(obj: object) {
|
||||
const cs = []
|
||||
let pt = obj
|
||||
@@ -166,6 +170,10 @@ export class Viewer extends EventEmitter implements IViewer {
|
||||
return this.container
|
||||
}
|
||||
|
||||
public getCanvas() {
|
||||
return this.speckleRenderer.renderer.domElement
|
||||
}
|
||||
|
||||
public getRenderer() {
|
||||
return this.speckleRenderer
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ export class CameraController extends Extension implements SpeckleCamera {
|
||||
|
||||
this._flyControls = new FlyControls(
|
||||
this._renderingCamera,
|
||||
this.viewer.getContainer(),
|
||||
this.viewer.getRenderer().input,
|
||||
this.viewer.World,
|
||||
this._options
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PerspectiveCamera } from 'three'
|
||||
import { IViewer } from '../../IViewer.js'
|
||||
import { CameraController } from './CameraController.js'
|
||||
import { InputEvent } from '../input/Input.js'
|
||||
type MoveType = 'forward' | 'back' | 'left' | 'right' | 'up' | 'down'
|
||||
|
||||
export class HybridCameraController extends CameraController {
|
||||
@@ -17,9 +18,9 @@ export class HybridCameraController extends CameraController {
|
||||
|
||||
public constructor(viewer: IViewer) {
|
||||
super(viewer)
|
||||
document.addEventListener('keydown', this.onKeyDown.bind(this))
|
||||
document.addEventListener('keyup', this.onKeyUp.bind(this))
|
||||
document.addEventListener('contextmenu', this.onContextMenu.bind(this))
|
||||
viewer.getRenderer().input.on(InputEvent.KeyUp, this.onKeyUp.bind(this))
|
||||
viewer.getRenderer().input.on(InputEvent.KeyDown, this.onKeyDown.bind(this))
|
||||
viewer.getRenderer().input.on(InputEvent.ContextMenu, this.onContextMenu.bind(this))
|
||||
}
|
||||
|
||||
public onEarlyUpdate(_delta?: number): void {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SpeckleControls } from './SpeckleControls.js'
|
||||
import { World } from '../../World.js'
|
||||
import { AngleDamper } from '../../utils/AngleDamper.js'
|
||||
import { TIME_MS } from '@speckle/shared'
|
||||
import Input, { InputEvent } from '../../input/Input.js'
|
||||
|
||||
const _vectorBuff0 = new Vector3()
|
||||
|
||||
@@ -33,9 +34,9 @@ export interface FlyControlsOptions {
|
||||
}
|
||||
|
||||
class FlyControls extends SpeckleControls {
|
||||
protected input: Input
|
||||
protected _options: Required<FlyControlsOptions>
|
||||
protected _targetCamera: PerspectiveCamera | OrthographicCamera
|
||||
protected container: HTMLElement
|
||||
protected velocity = new Vector3()
|
||||
protected euler = new Euler(0, 0, 0, 'YXZ')
|
||||
protected position = new Vector3()
|
||||
@@ -109,14 +110,14 @@ class FlyControls extends SpeckleControls {
|
||||
|
||||
constructor(
|
||||
camera: PerspectiveCamera | OrthographicCamera,
|
||||
container: HTMLElement,
|
||||
input: Input,
|
||||
world: World,
|
||||
options: Required<FlyControlsOptions>
|
||||
) {
|
||||
super()
|
||||
|
||||
this._targetCamera = camera
|
||||
this.container = container
|
||||
this.input = input
|
||||
this.world = world
|
||||
this._options = Object.assign({}, options)
|
||||
|
||||
@@ -324,20 +325,20 @@ class FlyControls extends SpeckleControls {
|
||||
|
||||
protected connect() {
|
||||
if (this._enabled) return
|
||||
|
||||
this.container.addEventListener('pointermove', this.onMouseMove)
|
||||
document.addEventListener('keydown', this.onKeyDown)
|
||||
document.addEventListener('keyup', this.onKeyUp)
|
||||
document.addEventListener('contextmenu', this.onContextMenu)
|
||||
this.input.on(InputEvent.KeyUp, this.onKeyUp)
|
||||
this.input.on(InputEvent.KeyDown, this.onKeyDown)
|
||||
this.input.on(InputEvent.PointerMove, this.onMouseMove)
|
||||
this.input.on(InputEvent.ContextMenu, this.onContextMenu)
|
||||
}
|
||||
|
||||
protected disconnect() {
|
||||
if (!this._enabled) return
|
||||
|
||||
this.container.removeEventListener('pointermove', this.onMouseMove)
|
||||
document.removeEventListener('keydown', this.onKeyDown)
|
||||
document.removeEventListener('keyup', this.onKeyUp)
|
||||
document.removeEventListener('contextmenu', this.onContextMenu)
|
||||
this.input.removeListener(InputEvent.KeyUp, this.onKeyUp)
|
||||
this.input.removeListener(InputEvent.KeyDown, this.onKeyDown)
|
||||
this.input.removeListener(InputEvent.PointerMove, this.onMouseMove)
|
||||
this.input.removeListener(InputEvent.ContextMenu, this.onContextMenu)
|
||||
|
||||
for (const k in this.keyMap) this.keyMap[k as MoveType] = false
|
||||
}
|
||||
|
||||
@@ -355,11 +356,11 @@ class FlyControls extends SpeckleControls {
|
||||
}
|
||||
|
||||
// event listeners
|
||||
protected onMouseMove = (event: PointerEvent) => {
|
||||
if (event.buttons !== 1 || !this._enabled) return
|
||||
protected onMouseMove = (arg: Vector2 & { event: PointerEvent }) => {
|
||||
if (arg.event.buttons !== 1 || !this._enabled) return
|
||||
|
||||
const movementX = event.movementX || 0
|
||||
const movementY = event.movementY || 0
|
||||
const movementX = arg.event.movementX || 0
|
||||
const movementY = arg.event.movementY || 0
|
||||
const amount = new Vector2()
|
||||
amount.y = movementX * 0.005 * this._options.lookSpeed
|
||||
amount.x = movementY * 0.005 * this._options.lookSpeed
|
||||
|
||||
@@ -618,9 +618,8 @@ export class SectionTool extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
/** Event listeners */
|
||||
document.addEventListener('keydown', this.keydownHandler)
|
||||
document.addEventListener('keyup', this.keyupHandler)
|
||||
this.viewer.getRenderer().input.on(InputEvent.KeyDown, this.keydownHandler)
|
||||
this.viewer.getRenderer().input.on(InputEvent.KeyUp, this.keyupHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1135,10 +1134,14 @@ export class SectionTool extends Extension {
|
||||
*/
|
||||
public dispose() {
|
||||
if (this.keydownHandler) {
|
||||
document.removeEventListener('keydown', this.keydownHandler)
|
||||
this.viewer
|
||||
.getRenderer()
|
||||
.input.removeListener(InputEvent.KeyDown, this.keydownHandler)
|
||||
}
|
||||
if (this.keyupHandler) {
|
||||
document.removeEventListener('keyup', this.keyupHandler)
|
||||
this.viewer
|
||||
.getRenderer()
|
||||
.input.removeListener(InputEvent.KeyUp, this.keyupHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ export enum InputEvent {
|
||||
Wheel = 'wheel',
|
||||
Click = 'click',
|
||||
DoubleClick = 'double-click',
|
||||
KeyUp = 'key-up'
|
||||
KeyUp = 'key-up',
|
||||
KeyDown = 'key-down',
|
||||
ContextMenu = 'context-menu'
|
||||
}
|
||||
|
||||
export interface InputEventPayload {
|
||||
@@ -21,6 +23,8 @@ export interface InputEventPayload {
|
||||
[InputEvent.Click]: Vector2 & { event: PointerEvent; multiSelect: boolean }
|
||||
[InputEvent.DoubleClick]: Vector2 & { event: PointerEvent; multiSelect: boolean }
|
||||
[InputEvent.KeyUp]: KeyboardEvent
|
||||
[InputEvent.KeyDown]: KeyboardEvent
|
||||
[InputEvent.ContextMenu]: PointerEvent
|
||||
}
|
||||
|
||||
//TO DO: Define proper interface for InputEvent data
|
||||
@@ -33,14 +37,20 @@ export default class Input extends EventEmitter {
|
||||
private touchLocation: Touch | undefined
|
||||
private container
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
constructor(container: HTMLElement, restrictKeyInput: boolean = false) {
|
||||
super()
|
||||
this.container = container
|
||||
|
||||
if (restrictKeyInput) {
|
||||
// Make canvas focusable for scoped keyboard events
|
||||
this.container.tabIndex = -1
|
||||
// Remove default focus outline
|
||||
this.container.style.outline = 'none'
|
||||
}
|
||||
// Handle mouseclicks
|
||||
let mdTime: number
|
||||
this.container.addEventListener('pointerdown', (e) => {
|
||||
e.preventDefault()
|
||||
this.container.focus() // preventDefault blocks default focus
|
||||
const loc = this._getNormalisedClickPosition(e)
|
||||
;(loc as unknown as Record<string, unknown>).event = e
|
||||
mdTime = new Date().getTime()
|
||||
@@ -106,11 +116,15 @@ export default class Input extends EventEmitter {
|
||||
this.emit(InputEvent.PointerMove, data)
|
||||
})
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
const keySource = restrictKeyInput ? this.container : document
|
||||
keySource.addEventListener('keyup', (e) => {
|
||||
this.emit(InputEvent.KeyUp, e)
|
||||
})
|
||||
keySource.addEventListener('keydown', (e) => {
|
||||
this.emit(InputEvent.KeyDown, e)
|
||||
})
|
||||
|
||||
document.addEventListener('wheel', (e) => {
|
||||
this.container.addEventListener('wheel', (e) => {
|
||||
this.emit(InputEvent.Wheel, e)
|
||||
})
|
||||
|
||||
@@ -121,6 +135,10 @@ export default class Input extends EventEmitter {
|
||||
this.emit(InputEvent.PointerUp, loc)
|
||||
this.emit(InputEvent.PointerCancel, loc)
|
||||
})
|
||||
|
||||
this.container.addEventListener('contextmenu', (e) => {
|
||||
this.emit(InputEvent.ContextMenu, e)
|
||||
})
|
||||
}
|
||||
|
||||
public on<T extends InputEvent>(
|
||||
|
||||
Reference in New Issue
Block a user