import { TaskPriority , throttleInAFrame , Vec2 , getEventClientPos , Vec3 , getRelativePositionFromEvent } from '@turbox3d/shared' ; import { CanvasHandlers , InteractiveConfig , InteractiveType , IViewportInfo } from './type' ; import { InteractiveListener } from './listener/index' ; import { InteractiveEvent } from './listener/type' ; import { SceneMouseEvent } from './sceneMouse' ; import { CoordinateController } from './coordinate' ; export interface HitResult < DisplayObject > { /** event 的直接对象 */ originalTarget ? : DisplayObject ; /** originalTarget 或 originalTarget 的祖先中的第一个可被交互的元素 */ target ? : DisplayObject ; /** 选中对象的具体场景鼠标位置 */ originalTargetPoint ? : Vec2 | Vec3 ; } interface Option < Container , DisplayObject > { renderer : HTMLCanvasElement ; container : Container ; canvasHandler : CanvasHandlers ; coordinateType ? : string ; viewport ? : IViewportInfo ; getCoordinateCtrl : ( ) => CoordinateController ; getHitTargetOriginal : ( point : Vec2 , container : Container , configMap : Map < DisplayObject , InteractiveConfig > , interactiveType : InteractiveType ) => HitResult < DisplayObject > ; } export class InteractiveController < Container , DisplayObject > { /** 渲染器对应的 canvas */ private renderer : HTMLCanvasElement ; /** 根容器 */ private container : Container ; private interactiveListener : InteractiveListener ; /** 交互对象的配置 */ private interactiveConfig : Map < DisplayObject , InteractiveConfig > = new Map ( ) ; /** 画布处理事件 */ private canvasHandlers : CanvasHandlers ; /**
     * 当前的拖拽目标
     */ private dragTarget ? : DisplayObject ; /**
     * 持续 hover 的目标
     */ private hoverTarget ? : DisplayObject ; /** 上次点击命中的目标 */ private lastClickTarget ? : DisplayObject ; /** 当前视口的区域信息 */ private viewport ? : IViewportInfo ; /** 坐标系类型 */ private coordinateType ? : string ; /** hitTarget 的实现，不同渲染引擎不一样 */ private getHitTargetOriginal : ( point : Vec2 , container : Container , configMap : Map < DisplayObject , InteractiveConfig > , interactiveType : InteractiveType ) => HitResult < DisplayObject > ; private getCoordinateCtrl : ( ) => CoordinateController ; constructor ( option ) { this . renderer = option . renderer ; this . container = option . container ; this . canvasHandlers = option . canvasHandler ; this . viewport = option . viewport ; this . coordinateType = option . coordinateType ; this . getCoordinateCtrl = option . getCoordinateCtrl ; this . getHitTargetOriginal = option . getHitTargetOriginal ; } param view param config updateInteractiveObject = ( view , config ? ) => { if ( config ) { this . interactiveConfig . set ( view , config ) ; } else { this . interactiveConfig . delete ( view ) ; } } ; /** 更新视口信息 */ updateViewportInfo = ( viewport ) => { this . viewport = viewport ; this . interactiveListener . updateViewportInfo ( viewport ) ; } ; param canvas startListener ( canvas ) { const { Click , DBClick , RightClick , DragStart , DragMove , DragEnd , Hover , Carriage , CarriageEnd , Wheel } = InteractiveEvent ; this . interactiveListener = InteractiveListener . create ( canvas , this . viewport , this . coordinateType ) ; this . interactiveListener . registerListener ( ) ; this . interactiveListener . addEventListener ( Click , this . onClick ) . addEventListener ( DBClick , this . onDBClick ) . addEventListener ( RightClick , this . onRightClick ) . addEventListener ( DragStart , this . onDragStart ) . addEventListener ( DragMove , this . onDragMove ) . addEventListener ( DragEnd , this . onDragEnd ) . addEventListener ( Hover , this . onHover ) . addEventListener ( Carriage , this . onCarriage ) . addEventListener ( CarriageEnd , this . onCarriageEnd ) . addEventListener ( Wheel , this . onWheel ) ; } param canvas removeAllListener ( ) { const { Click , DBClick , RightClick , DragStart , DragMove , DragEnd , Hover , Carriage , CarriageEnd , Wheel } = InteractiveEvent ; this . interactiveListener . removeEventListener ( Click , this . onClick ) . removeEventListener ( DBClick , this . onDBClick ) . removeEventListener ( RightClick , this . onRightClick ) . removeEventListener ( DragStart , this . onDragStart ) . removeEventListener ( DragMove , this . onDragMove ) . removeEventListener ( DragEnd , this . onDragEnd ) . removeEventListener ( Hover , this . onHover ) . removeEventListener ( Carriage , this . onCarriage ) . removeEventListener ( CarriageEnd , this . onCarriageEnd ) . removeEventListener ( Wheel , this . onWheel ) ; this . interactiveListener . dispose ( ) ; } param point hitTarget = ( point ) => { const originalPoint = this . revisePointByViewPort ( point ) ; const { target } = this . getHitTargetOriginal ( originalPoint , this . container , this . interactiveConfig , 'isClickable' ) ; if ( target ) { const config = this . interactiveConfig . get ( target ) ; if ( config && config . getViewEntity ) { return config . getViewEntity ( ) ; } } return undefined ; } ; /**
     * 获取本次鼠标事件的交互对象
     */ private hitTargetHandler ( event , type ) { let originalPoint = getRelativePositionFromEvent ( getEventClientPos ( event ) , this . renderer ) ; if ( ! originalPoint ) { return { } ; } originalPoint = this . revisePointByViewPort ( originalPoint ) ; return this . getHitTargetOriginal ( originalPoint , this . container , this . interactiveConfig , type ) ; } param point hitTargetOriginalByPoint ( point ) { let originalPoint = getRelativePositionFromEvent ( point , this . renderer ) ; if ( ! originalPoint ) { return { } ; } originalPoint = this . revisePointByViewPort ( originalPoint ) ; return this . getHitTargetOriginal ( originalPoint , this . container , this . interactiveConfig , 'isClickable' ) ; } private revisePointByViewPort ( point ) { if ( this . viewport ) { const { x , y } = this . viewport ; return { x : point . x - x , y : point . y - y } ; } return point ; } private onClick = ( event ) => { const { target } = this . hitTargetHandler ( event , 'isClickable' ) ; if ( target ) { this . lastClickTarget = target ; const config = this . interactiveConfig . get ( target ) ; if ( config && config . isClickable ) { if ( config . onClick ) { config . onClick ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } return ; } } this . lastClickTarget = undefined ; this . canvasHandlers . onClick ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } ; private onDBClick = ( event ) => { if ( this . lastClickTarget ) { const config = this . interactiveConfig . get ( this . lastClickTarget ) ; if ( config && config . isClickable && config . onDBClick ) { config . onDBClick ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } } } ; private onRightClick = ( event ) => { const { target } = this . hitTargetHandler ( event , 'isClickable' ) ; if ( target ) { const config = this . interactiveConfig . get ( target ) ; if ( config && config . onRightClick ) { config . onRightClick ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; return ; } } this . canvasHandlers . onRightClick ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } ; private onDragStart = ( event ) => { const { target } = this . hitTargetHandler ( event , 'isDraggable' ) ; if ( target ) { const config = this . interactiveConfig . get ( target ) ; if ( config && config . isDraggable && config . dragStart ) { this . dragTarget = target ; config . dragStart ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } return ; } this . canvasHandlers . onDragStart ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } ; private onDragMove = throttleInAFrame ( ( event ) => { if ( this . dragTarget ) { const config = this . interactiveConfig . get ( this . dragTarget ) ; if ( config && config . isDraggable && config . dragMove ) { config . dragMove ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } return ; } this . canvasHandlers . onDragMove ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } , TaskPriority . UserAction ) ; private onDragEnd = throttleInAFrame ( ( event ) => { if ( this . dragTarget ) { const config = this . interactiveConfig . get ( this . dragTarget ) ; if ( config && config . isDraggable && config . dragEnd ) { config . dragEnd ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } this . dragTarget = undefined ; return ; } this . canvasHandlers . onDragEnd ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } , TaskPriority . UserAction ) ; private onCarriage = throttleInAFrame ( ( event ) => { this . canvasHandlers . onMouseMove ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } , TaskPriority . UserAction ) ; private onCarriageEnd = ( event ) => { this . canvasHandlers . onMouseUp ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } ; private onHover = throttleInAFrame ( ( event ) => { const { target } = this . hitTargetHandler ( event , 'isHoverable' ) ; if ( target ) { if ( this . hoverTarget ) { if ( target !== this . hoverTarget ) { this . onHoverOut ( this . hoverTarget , event ) ; this . onHoverIn ( target , event ) ; } } else { this . onHoverIn ( target , event ) ; } } else if ( this . hoverTarget ) { this . onHoverOut ( this . hoverTarget , event ) ; this . hoverTarget = undefined ; } } , TaskPriority . UserAction ) ; private onHoverIn ( target , event ) { const config = this . interactiveConfig . get ( target ) ; if ( config && config . isHoverable && config . onHoverIn ) { this . hoverTarget = target ; config . onHoverIn ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } } private onHoverOut ( target , event ) { const config = this . interactiveConfig . get ( target ) ; if ( config && config . isHoverable && config . onHoverOut ) { config . onHoverOut ( SceneMouseEvent . create ( event , this . getCoordinateCtrl , this . hitTargetOriginalByPoint ) ) ; } } private onWheel = throttleInAFrame ( ( event ) => { event . stopImmediatePropagation ( ) ; event . preventDefault ( ) ; this . canvasHandlers . onWheel ( event ) ; } , TaskPriority . UserAction ) ; }