import { Layer } from './layer'; import { PipelineDimension, PipelineLayerPriority } from '../pipeline'; import { EditorState, EditorStateChangeEvent, EditorStateConfigEntity, PlaygroundConfigEntity, PlaygroundConfigEntityData, // RulerConfigEntity, // SCALE_WIDTH, } from './config'; import { entity } from '../../common'; import { domUtils } from '@gedit/utils/lib/browser'; import { PlaygroundGesture } from '../utils/playground-gesture'; import { PlaygroundDrag } from '../utils'; import { Disposable } from '@gedit/utils'; /** * 基础层,控制画布缩放/滚动等操作 */ export class PlaygroundLayer extends Layer { id = 'playgroundLayer'; @entity(PlaygroundConfigEntity) protected playgroundConfigEntity: PlaygroundConfigEntity; @entity(EditorStateConfigEntity) protected editorStateConfig: EditorStateConfigEntity; // @entity(RulerConfigEntity) protected rulerConfigEntity: RulerConfigEntity; readonly loadingNode = domUtils.createDivWithClass( 'gedit-playground-loading' ); private cancelStateListen?: Disposable; private lastShortcutState?: EditorState; private currentGesture?: PlaygroundGesture; private startGrabScroll: { scrollX: number; scrollY: number } = { scrollX: 0, scrollY: 0, }; private size?: PipelineDimension; // private rulerScaleVisible?: boolean; private changeState?: EditorStateChangeEvent; onReady(): void { this.pipelineNode.parentNode!.appendChild(this.loadingNode); // this.rulerScaleVisible = this.rulerConfigEntity.config.scaleVisible; this.toDispose.pushAll([ /** * 在父节点上监听滚动事件 */ this.listenPlaygroundEvent( 'wheel', this.handleWheelEvent.bind(this), PipelineLayerPriority.BASE_LAYER ), this.listenPlaygroundEvent( 'mousedown', (e: MouseEvent) => { if (this.isGrab()) { this.grabDragger.start(e.clientX, e.clientY); } }, PipelineLayerPriority.BASE_LAYER ), this.editorStateConfig.onStateChange(this.onStateChanged.bind(this)), // 监听快捷键 this.listenGlobalEvent( 'keydown', (e: KeyboardEvent) => { const target = e.target as HTMLElement; if ( !this.isFocused || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' ) { return; } const state = this.editorStateConfig.getStateFormShortCut(e); if (state) { e.preventDefault(); e.stopPropagation(); } this.lastShortcutState = state; if (state) { this.editorStateConfig.changeState(state.id); } }, PipelineLayerPriority.BASE_LAYER ), this.listenGlobalEvent('keyup', (e: KeyboardEvent) => { if (this.lastShortcutState && this.lastShortcutState.shortcutAutoEsc) { this.lastShortcutState = undefined; this.editorStateConfig.toDefaultState(); } }), // 标尺状态更新则刷新大小信息 // this.rulerConfigEntity.onConfigChanged(rulerConfig => { // if (rulerConfig.scaleVisible !== this.rulerScaleVisible) { // this.rulerScaleVisible = rulerConfig.scaleVisible; // this.updateSizeWithRulerConfig(); // } // }), this.config.onConfigChanged(async config => { // 进度条不更新;同线程中不会渲染,需要在 game-scene-ide 里的 tryToCreate 换成异步; /* if (config.loadingHTML !== this.loadingNode.innerHTML) { this.loadingNode.innerHTML = config.loadingHTML; } */ }), ]); } keydownEvent = (keyboard: KeyboardEvent) => { const state = this.changeState!.state; // 去掉enter,因为有些输入框会触发enter if ( keyboard.key === 'Escape' && /* || keyboard.key === 'Enter' */ (!state.cancelFactor || state.cancelFactor(this.config, this.changeState)) ) { this.editorStateConfig.toDefaultState(); this.cancelStateListen?.dispose(); this.cancelStateListen = undefined; } }; onStateChanged(e: EditorStateChangeEvent): void { const state = e.state; this.changeState = e; if (this.cancelStateListen) { this.cancelStateListen.dispose(); this.cancelStateListen = undefined; } if (state.handle) { state.handle(this.config, e); } if (state.cursor) { this.playgroundConfigEntity.updateCursor(state.cursor); (this.currentGesture?.target.parentNode as HTMLElement).style.cursor = state.cursor; } else { this.playgroundConfigEntity.updateCursor(''); (this.currentGesture?.target.parentNode as HTMLElement).style.cursor = ''; } // 按esc退出 if (state.cancelMode === 'esc') { // 动态 func 在事件里是不可被清除的; this.cancelStateListen = domUtils.addStandardDisposableListener( document.body, 'keydown', this.keydownEvent, true ); } else if ( state.cancelMode === 'once' && (!state.cancelFactor || state.cancelFactor(this.config, e)) ) { // 只执行一次 this.editorStateConfig.toDefaultState(); } } protected grabDragger = new PlaygroundDrag({ onDragStart: e => { this.config.updateCursor('grabbing'); this.startGrabScroll = { scrollX: this.config.config.scrollX, scrollY: this.config.config.scrollY, }; }, onDrag: e => { this.config.updateConfig({ scrollX: this.startGrabScroll.scrollX - e.endPos.x + e.startPos.x, scrollY: this.startGrabScroll.scrollY - e.endPos.y + e.startPos.y, }); }, onDragEnd: e => { if (this.isGrab()) { // 可能已经取消了 this.config.updateCursor('grab'); } }, }); protected isGrab(): boolean { const currentState = this.editorStateConfig.getCurrentState(); return currentState === EditorState.STATE_GRAB; } createGesture(): void { if (!this.currentGesture) { this.currentGesture = new PlaygroundGesture( this.pipelineNode.parentElement!, this.config ); this.currentGesture.onDispose(() => { this.currentGesture = undefined; }); this.toDispose.push(this.currentGesture); } } /** * 监听resize * @param size */ onResize(size: PipelineDimension): void { this.size = { ...size }; this.updateSizeWithRulerConfig(); } updateSizeWithRulerConfig(): void { const size = this.size; if (!size) return; // if (this.rulerScaleVisible) { // this.config.updateConfig({ // width: size.width - SCALE_WIDTH, // height: size.height - SCALE_WIDTH, // clientX: size.clientX + SCALE_WIDTH, // clientY: size.clientY + SCALE_WIDTH, // }); // } else { this.config.updateConfig({ width: size.width, height: size.height, clientX: size.clientX, clientY: size.clientY, }); // } } protected handleScrollEvent(event: WheelEvent): void { const playgroundConfigEntity = this.playgroundConfigEntity; const scrollX = playgroundConfigEntity.config.scrollX + event.deltaX; const scrollY = playgroundConfigEntity.config.scrollY + event.deltaY; const state: Partial = { scrollX, scrollY, }; playgroundConfigEntity.updateConfig(state); event.preventDefault(); event.stopPropagation(); } /** * 监听滚动事件 * @param event */ protected handleWheelEvent(event: WheelEvent): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any const e = event as any; if (this.currentGesture && this.currentGesture.pinching) return; this.handleScrollEvent(e); } draw(): void { const playgroundConfig = this.playgroundConfigEntity.config; // 标尺去掉; // const { scaleVisible } = this.rulerConfigEntity.config; // 创建手势 if (this.config.zoomEnable) { this.createGesture(); } else if (this.currentGesture) { this.currentGesture.dispose(); } // 设置pipeline的样式 // if (scaleVisible) { // domUtils.setStyle(this.pipelineNode, { // left: SCALE_WIDTH - playgroundConfig.scrollX, // top: SCALE_WIDTH - playgroundConfig.scrollY, // width: playgroundConfig.width, // height: playgroundConfig.height, // }); // } else { domUtils.setStyle(this.pipelineNode, { left: -playgroundConfig.scrollX, top: -playgroundConfig.scrollY, width: playgroundConfig.width, height: playgroundConfig.height, }); // } this.pipelineNode.parentElement!.style.cursor = this.playgroundConfigEntity.cursor; if (this.config.loading) { /* const bounds = this.loadingNode.getBoundingClientRect(); domUtils.setStyle(this.loadingNode, { display: 'block', marginLeft: - bounds.width / 2, marginTop: - bounds.height / 2 }); */ this.loadingNode.style.display = 'block'; } else { this.loadingNode.style.display = 'none'; } } }