import * as React from 'react'; import { Layer } from './layer'; import { domUtils } from '@gedit/utils/lib/browser'; import { EditorState, EditorStateConfigEntity, PathPointSelectionEntity, PlaygroundConfigEntity, RulerConfigEntity, SelectorConfigEntity, SnaplineConfigEntity } from './config'; import { able, Entity, entity, TransformData } from '../../common'; import { Rectangle } from '@gedit/math'; import { Dragable, Resizable, ResizePayload, ResizeType, Rotatable, RotatePayload, Selectable, SelectControlKey, SelectPayload, SelectState } from '../able'; import { PlaygroundDrag } from '../utils/playground-drag'; import { SelectorMultipleRenderer } from './selector/selector-multiple-renderer'; import { SelectorEntityRendererProps } from './selector/selector-entity-renderer'; import { SelectorBoundsProps } from './selector/selector-bounds'; import { PipelineLayerPriority } from '../pipeline'; import { Adsorber } from '../utils/adsorber'; function getSelectControlKeys(selectedEntities: Entity[]): SelectControlKey[] { const arr: SelectControlKey[][] = selectedEntities.map(e => e.getData(SelectState)!.controlKeys); if (arr.length <= 1) return arr[0] || []; const others = arr.slice(1); return arr[0].filter(key => key !== 'rotate' && !others.find(o => !o.includes(key))); } export class SelectorLayer extends Layer { @entity(PlaygroundConfigEntity) protected playgroundConfigEntity: PlaygroundConfigEntity; @entity(SelectorConfigEntity) protected selectorConfigEntity: SelectorConfigEntity; @entity(RulerConfigEntity) protected rulerConfigEntity: RulerConfigEntity; @entity(SnaplineConfigEntity) protected snaplineEntity: SnaplineConfigEntity; @entity(EditorStateConfigEntity) protected editorStateConfig: EditorStateConfigEntity; @entity(PathPointSelectionEntity) protected pathPointSelection: PathPointSelectionEntity; @able(Selectable) protected selectableNodes: Entity[]; @able(Dragable) protected dragableNodes: Entity[]; readonly node = domUtils.createDivWithClass('gedit-selector'); protected selectorBounds?: Rectangle; protected adsorbRefs?: Rectangle[]; protected adsorbLines?: Adsorber.Line[]; constructor() { super(); this.onControlClick = this.onControlClick.bind(this); this.toDispose.pushAll([ this.selectorResize, this.selectorRotate ]); } /** * 选择器旋转 */ protected selectorRotate = new PlaygroundDrag({ onDragStart: e => { this.adsorbRefs = Adsorber.getRefsFromEntities(this.entityManager, this.selectedEntities, this.config); this.adsorbLines = this.snaplineEntity.getAdsoberLines(); this.selectedEntities.forEach(n => n.addAbles(Rotatable)); this.dispatch(RotatePayload, { ...e, adsorbRefs: this.adsorbRefs, adsorbLines: this.adsorbLines, }); }, onDrag: e => { this.dispatch(RotatePayload, { ...e, adsorbRefs: this.adsorbRefs, adsorbLines: this.adsorbLines, }); }, onDragEnd: e => { this.adsorbLines = undefined; this.adsorbRefs = undefined; this.dispatch(RotatePayload, e); this.selectedEntities.forEach(n => n.removeAbles(Rotatable)); this.draw(); } }); protected selectorResize = new PlaygroundDrag({ onDragStart: (e, ctx) => { this.selectedEntities.forEach(n => n.addAbles(Resizable)); this.adsorbRefs = Adsorber.getRefsFromEntities(this.entityManager, this.selectedEntities, this.config); this.adsorbLines = this.snaplineEntity.getAdsoberLines(); this.dispatch(ResizePayload, { type: ctx!, startPos: e.startPos, endPos: e.endPos, scale: e.scale, isStart: e.isStart, isMoving: e.isMoving, adsorbLines: this.adsorbLines, adsorbRefs: this.adsorbRefs, }); }, onDrag: (e, ctx) => { this.dispatch(ResizePayload, { type: ctx!, startPos: e.startPos, endPos: e.endPos, scale: e.scale, isStart: e.isStart, isMoving: e.isMoving, adsorbLines: this.adsorbLines, adsorbRefs: this.adsorbRefs, }); }, onDragEnd: (e, ctx) => { this.dispatch(ResizePayload, { type: ctx!, startPos: e.startPos, endPos: e.endPos, scale: e.scale, isStart: e.isStart, isMoving: e.isMoving, }); this.adsorbLines = undefined; this.adsorbRefs = undefined; this.selectedEntities.forEach(n => n.removeAbles(Resizable)); this.draw(); } }); /** * 数据准备后状态 */ onReady(): void { this.toDispose.pushAll([ this.listenPlaygroundEvent('mousemove', this.onHover.bind(this), PipelineLayerPriority.TOOL_LAYER), this.listenPlaygroundEvent('mousedown', this.onMouseDown.bind(this), PipelineLayerPriority.TOOL_LAYER), this.listenPlaygroundEvent('dblclick', this.onDoubleClick.bind(this), PipelineLayerPriority.TOOL_LAYER), this.pathPointSelection.onSelectionChanged(() => { if (this.pathPointSelection.selection?.length) { this.clearHoveredNode(); this.draw(); } }), ]); } protected onDoubleClick(e: MouseEvent): boolean | undefined { const target = e.target as HTMLElement; if (this.isDisabled || this.isInOperation() || (target && target.classList.contains('gedit-selector-control')) // 双击选择框内容 || !this.isSelectorBoundsContains(e) ) { return; }; const selectNode = this.getNodeByMouseEvent(e, false); if (selectNode) { const zIndexHovered = Selectable.getZIndex(selectNode); const additional = e.metaKey || e.shiftKey || e.ctrlKey; this.clearHoveredNode(); this.clearSelectedNodes(); this.dispatch(SelectPayload, {zIndex: zIndexHovered, additional, selectionService: this.selectionService }); return true; } // const ignoreEntities = this.selectedEntities; } /** * 点击控制器 */ protected onControlClick(key: string, e: React.MouseEvent): void { this.clearHoveredNode(); e.stopPropagation(); e.preventDefault(); if (key === 'rotate') { this.selectorRotate.start(e.clientX, e.clientY, this.config); } else { this.selectorResize.start(e.clientX, e.clientY, this.config, key as ResizeType); } } protected isMoving: boolean = false; protected isInOperation(): boolean { return this.isMoving || this.selectorRotate.isStarted || this.selectorResize.isStarted; } protected startMoving(e: MouseEvent): void { this.startDrag( e.clientX, e.clientY, { entities: this.selectedEntities, adsorbLines: this.snaplineEntity.getAdsoberLines(), onDrag: () => { this.isMoving = true; // this.pipelineNode.style.cursor = 'move'; }, onDragEnd: dragEvent => { this.isMoving = false; // this.pipelineNode.style.cursor = 'default'; const delta = { x: dragEvent.endPos.x - dragEvent.startPos.x, y: dragEvent.endPos.y - dragEvent.startPos.y, }; if (delta.x !== 0 && delta.y !== 0) { this.draw(); } else { // 选择 this.tryToSelectHoverred(); } }, } ); } protected tryToSelectHoverred(): undefined | boolean { if (this.isInOperation() || this.isDisabled) return; const hoveredEntity = this.hoveredEntity; if (hoveredEntity) { const zIndexHovered = Selectable.getZIndex(hoveredEntity); this.dispatch(SelectPayload, {zIndex: zIndexHovered, selectionService: this.selectionService }); } return; } protected onMouseDown(e: MouseEvent): undefined | boolean { if (this.isDisabled) return; // 操作时候阻止后续执行 if (this.isInOperation()) return true; // 点击hover if (this.hoveredEntity) { const additional = e.metaKey || e.shiftKey || e.ctrlKey; const zIndexHovered = Selectable.getZIndex(this.hoveredEntity); this.dispatch(SelectPayload, {zIndex: zIndexHovered, additional, selectionService: this.selectionService }, () => { if (!additional) { // 这个在选择器选中后再触发 this.startMoving(e); } }); return true; } // 鼠标在选择框内部 if (this.isSelectorBoundsContains(e)) { this.startMoving(e); return true; } this.clearSelectedNodes(); // 发送一个空的触发Select after, 用于更新全局的selectionService if (this.entityManager.getEntitiesByAble(Selectable).length) { // selectable 没注册,会导致元素坐标全部归 0; this.dispatch(SelectPayload, { selectionService: this.selectionService }); } } onBlur(): void { // 去掉hover const hoveredEntity = this.hoveredEntity; const state = hoveredEntity ? hoveredEntity.getData(SelectState) : undefined; if (state) { state.hovered = false; } else { this.draw(); } } onFocus(): void { this.draw(); } // private hoveredTimeout?: number; /** * hover状态 * @param e * @protected */ protected onHover(e: MouseEvent): undefined | boolean { // if (this.hoveredTimeout) { // window.clearTimeout(this.hoveredTimeout); // this.hoveredTimeout = undefined; // } const target = e.target as HTMLElement; if (this.isDisabled || this.isInOperation() || (target && target.classList.contains('gedit-selector-control'))) { this.clearHoveredNode(); return; }; const oldHoveredEntity = this.hoveredEntity; const oldState = oldHoveredEntity ? oldHoveredEntity.getData(SelectState) : undefined; // 鼠标在选择框内 if (this.isSelectorBoundsContains(e)) { if (oldState) oldState.hovered = false; return; } const hoveredNode = this.getNodeByMouseEvent(e); const stateHovered = hoveredNode ? hoveredNode.getData(SelectState) : undefined; if (stateHovered !== oldState) { if (oldState) oldState.hovered = false; if (stateHovered) { stateHovered.hovered = true; } } } /** * 通过鼠标获取节点 * @param e * @param ignoreSelected */ getNodeByMouseEvent(e: MouseEvent, ignoreSelected: boolean = true): Entity | undefined { // 查找hover的节点的zIndex let zIndexHovered: number | undefined; let transformHovered: TransformData; let current: Entity | undefined; const mousePos = this.getPosFromMouseEvent(e, true); this.selectableNodes.forEach(node => { const state = node.getData(SelectState); const transform = node.getData(TransformData); if (!state || !transform || !state.mouseSelect || (ignoreSelected && this.isSelected(node))) return; // 如果父节点被hovered,则优先选择父节点 if (transformHovered && transformHovered.isParentTransform(transform)) { return; } if (transform.contains(mousePos.x, mousePos.y, state.displayType === 'circle')) { if (zIndexHovered === undefined || zIndexHovered < state.zIndex) { // // 如果选中的元素在hover的元素内部,则忽略 // if (selectedBounds && mouseInSelector && transform.bounds.containsRectangle(selectedBounds)) { // return; // } zIndexHovered = state.zIndex; transformHovered = transform; current = node; } } }); return current; } /** * 判断节点是否选中 * @param node */ isSelected(node: Entity): boolean { const state = node.getData(SelectState)!; if (state.selected) return true; const selectedTransforms = this.selectedEntities.map(s => s.getData(TransformData)!); const transform = node.getData(TransformData)!; return !!selectedTransforms.find(trans => transform === trans || transform.isParentTransform(trans) || trans.isParentTransform(transform)); } get selectedEntities(): Entity[] { return this.selectableNodes.filter(node => node.getData(SelectState)?.selected); } get hoveredEntity(): Entity | undefined { return this.selectableNodes.find(node => node.getData(SelectState)?.hovered); } get isDisabled(): boolean { const currentState = this.editorStateConfig.getCurrentState(); // 按住 ALT 键时,选择器不 disabled; return (currentState !== EditorState.STATE_SELECT && currentState !== EditorState.SHOW_SCENE_RANGE_RULER) || this.selectorConfigEntity.disabled // pathPoint 有值时,不允许选择 || !!(this.pathPointSelection.selection?.length); } get isVisible(): boolean { const currentState = this.editorStateConfig.getCurrentState(); return !currentState?.disableSelector; } /** * 判断选择框是否包含鼠标点 * @param e * @protected */ protected isSelectorBoundsContains(e: MouseEvent): boolean { const mousePos = this.getPosFromMouseEvent(e, true); if (!this.selectorBounds) return false; // 如果是一个需要考虑选择框旋转情况 if (this.selectedEntities.length === 1) { const state = this.selectedEntities[0].getData(SelectState)!; const selectedTransform: TransformData = this.selectedEntities[0].getData(TransformData)!; // 容器默认采用子元素当选择框 if (!selectedTransform.isContainer) { return selectedTransform.contains(mousePos.x, mousePos.y, state.displayType === 'circle'); } } // 选择多个则使用外围框 return this.selectorBounds.contains(mousePos.x, mousePos.y); } clearSelectedNodes(): void { this.selectedEntities.forEach(node => { const data = node.getData(SelectState)!; data.selected = false; }); } clearHoveredNode(): void { const hoveredEntity = this.hoveredEntity; if (hoveredEntity) { hoveredEntity.getData(SelectState)!.hovered = false; } } /** * 渲染实体数据 */ drawEntities(selectedEntities: Entity[]): React.ReactNode[] { const selectorConfigEntity = this.selectorConfigEntity; const EntityRenderer = selectorConfigEntity.getSelectorEntityRenderer(); const isDisabled = !!this.selectorConfigEntity.config.disabled; return this.selectableNodes.map(e => { const props = this.getEntityRendererProps(e, selectedEntities, isDisabled); if (!props) return <>; return ; }); } getEntityRendererProps(selectableEntity: Entity, selectedEntities: Entity[], isDisabled: boolean): (SelectorEntityRendererProps & { key: string }) | undefined { const state = selectableEntity.getData(SelectState); if (!state) return undefined; const selected = !isDisabled && state.selected; const hovered = !isDisabled && state.hovered && !this.isInOperation(); // 操作时候禁用hover let boundsProps: SelectorBoundsProps = {}; if (selected) { boundsProps = this.selectorConfigEntity.config.selectSelectedBounds || {}; } else if (hovered) { boundsProps = this.selectorConfigEntity.config.selectHoveredBounds || {}; } return { key: selectableEntity.id, dispatch: this.dispatch, playgroundConfig: this.config, selected, hovered, multipleSelected: selectedEntities.length > 1, isMoving: this.isMoving && selected, isInOperation: this.isInOperation() && selected, entity: selectableEntity, scale: this.config.finalScale, displayType: state.displayType, selectableEntities: this.selectableNodes, selectControlKeys: state.controlKeys, onControlClick: this.onControlClick, focused: this.isFocused, originBtnDisabled: state.originBtnDisabled, boundsProps, }; } draw(): React.JSX.Element { if (!this.isVisible) { return <>; } const { selectedEntities } = this; const selectConfig = this.selectorConfigEntity.config; const selectedTransforms: TransformData[] = selectedEntities.map(node => node.getData(TransformData)!); const bounds = this.selectorBounds = Rectangle.enlarge(selectedTransforms.map(t => t.bounds)); return <> {this.drawEntities(selectedEntities)} 1} boundsProps={selectConfig.selectSelectedBounds} onControlClick={this.onControlClick} displayType={'rectangle'} focused={this.isFocused} /> ; } }