UNPKG

devexpress-diagram

Version:

DevExpress Diagram Control

295 lines (283 loc) 14.3 kB
import { Point } from "@devexpress/utils/lib/geometry/point"; import { Vector } from "@devexpress/utils/lib/geometry/vector"; import { ChangeConnectorPointsHistoryItem } from "../../History/Common/ChangeConnectorPointsHistoryItem"; import { History } from "../../History/History"; import { Connector } from "../../Model/Connectors/Connector"; import { ConnectorRenderPoint } from "../../Model/Connectors/ConnectorRenderPoint"; import { ConnectorRenderPointsContext } from "../../Model/Connectors/Routing/ConnectorRenderPointsContext"; import { DiagramItem, ItemKey } from "../../Model/DiagramItem"; import { DiagramModel } from "../../Model/Model"; import { ModelUtils } from "../../Model/ModelUtils"; import { Shape } from "../../Model/Shapes/Shape"; import { DiagramModelOperation } from "../../ModelOperationSettings"; import { Selection } from "../../Selection/Selection"; import { DiagramMouseEvent, MouseButton, MouseEventElementType } from "../Event"; import { MouseHandler } from "../MouseHandler"; import { IVisualizerManager } from "../Visualizers/VisualizersManager"; import { MouseHandlerDraggingState } from "./MouseHandlerDraggingState"; export class DraggingConnector { readonly startPoints: Point[]; readonly startRenderContext: ConnectorRenderPointsContext; constructor(readonly connector: Connector) { this.startPoints = connector.points.map(x => x.clone()); this.startRenderContext = connector.createRenderPointsContext(); } } class DraggingShape { readonly startPosition: Point; constructor(public readonly shape: Shape) { this.startPosition = shape.position.clone(); } } export abstract class MouseHandlerDragDiagramItemStateBase extends MouseHandlerDraggingState { private fixedX: boolean; private fixedY: boolean; private lockInitDrag: boolean; startPoint: Point; selectedItems : { [key: string] : DiagramItem } draggingShapes: DraggingShape[]; draggingConnectors: DraggingConnector[]; draggingConnectorsIndexByKey: { [key: string]: number }; modelConnectorsWithoutBeginItemInfo: { connector: Connector, point: Point }[]; modelConnectorsWithoutEndItemInfo: { connector: Connector, point: Point }[]; startScrollLeft = 0; startScrollTop = 0; shouldClone: boolean; protected constructor(handler: MouseHandler, history: History, protected model: DiagramModel, protected selection: Selection, protected visualizerManager: IVisualizerManager) { super(handler, history); } protected abstract get areValidDraggingShapes(): boolean; protected abstract get areValidDraggingConnectors(): boolean; finish(): void { this.visualizerManager.resetExtensionLines(); this.visualizerManager.resetContainerTarget(); this.visualizerManager.resetConnectionTarget(); this.visualizerManager.resetConnectionPoints(); super.finish(); } onMouseDown(evt: DiagramMouseEvent): void { this.handler.addDiagramItemToSelection(evt); this.shouldClone = this.handler.canCopySelectedItems(evt); this.startPoint = evt.modelPoint; this.initDrag(); this.lockInitDrag = false; if(!this.shouldClone) this.draggingShapes.forEach(draggingShape => this.handler.addInteractingItem(draggingShape.shape, DiagramModelOperation.MoveShape)); super.onMouseDown(evt); } onMouseMove(evt: DiagramMouseEvent): void { this.mouseMoveEvent = evt; if(evt.button !== MouseButton.Left) { this.cancelChanges(); this.handler.switchToDefaultState(); } if(!this.canApplyChangesOnMouseMove(this.startPoint, evt.modelPoint)) return; if(this.handler.canCopySelectedItems(evt)) if(!this.lockInitDrag) { this.cancelChanges(); this.shouldClone = true; this.copySelection(); this.initDrag(); this.lockInitDrag = true; } this.onApplyChanges(evt); this.onAfterApplyChanges(); this.updateContainers(evt); } private updateContainers(evt: DiagramMouseEvent) : void { this.visualizerManager.setExtensionLines(this.selection.getSelectedShapes(false, true)); const container = ModelUtils.findContainerByEventKey(this.model, this.selection, evt.source.key); if(container && this.allowInsertToContainer(evt, container)) this.visualizerManager.setContainerTarget(container, evt.source.type); else this.visualizerManager.resetContainerTarget(); } onMouseUp(evt: DiagramMouseEvent) : void { super.onMouseUp(evt); if(this.handler.canRemoveDiagramItemToSelection(evt) && this.handler.canMultipleSelection(evt)) this.handler.removeDiagramItemFromSelection(evt.button, evt.source.key); } onApplyChanges(evt: DiagramMouseEvent): void { this.calculateFixedPosition(evt); if(this.draggingShapes.length) { const selectedShapes = this.draggingShapes.map(ds => ds.shape); this.draggingShapes.forEach(ds => { let shape = ds.shape; while(shape.container) { if(selectedShapes.indexOf(shape.container) !== -1) return false; shape = shape.container; } this.moveShape(ds, evt); }); const firstDraggingShape = this.draggingShapes[0]; const offset = Vector.fromPoints(firstDraggingShape.startPosition.clone(), firstDraggingShape.shape.position.clone()); if(offset.x || offset.y) this.draggingConnectors.forEach(dc => this.moveConnectorCore(dc.connector, dc.startPoints, dc.startRenderContext, offset)); } else this.draggingConnectors.forEach(x => this.moveConnector(x, evt)); const container = ModelUtils.findContainerByEventKey(this.model, this.selection, evt.source.key); if(container && this.allowInsertToContainer(evt, container)) ModelUtils.insertSelectionToContainer(this.history, this.model, this.selection, container); else ModelUtils.removeSelectionFromContainer(this.history, this.model, this.selection); this.handler.tryUpdateModelSize((offsetLeft, offsetTop) => { this.modelConnectorsWithoutBeginItemInfo.forEach(pi => { pi.point.x += offsetLeft; pi.point.y += offsetTop; }); this.modelConnectorsWithoutEndItemInfo.forEach(pi => { pi.point.x += offsetLeft; pi.point.y += offsetTop; }); }); } getDraggingElementKeys(): ItemKey[] { return this.draggingShapes.map(x => x.shape.key).concat(this.draggingConnectors.map(x => x.connector.key)); } getSnappedPoint(evt: DiagramMouseEvent, point: Point): Point { return this.handler.getSnappedPointOnDragDiagramItem(evt, point, this.fixedX, this.fixedY, this.startPoint); } private initDrag() : void { this.selectedItems = ModelUtils.createSelectedItems(this.selection); this.initDraggingShapes(); if(!this.areValidDraggingShapes) { this.handler.switchToDefaultState(); return; } this.initDraggingConnectors(); if(!this.areValidDraggingConnectors) { this.handler.switchToDefaultState(); return; } this.modelConnectorsWithoutBeginItemInfo = this.createModelConnectorsWithoutBeginItemInfo(); this.modelConnectorsWithoutEndItemInfo = this.createModelConnectorsWithoutEndItemInfo(); } private initDraggingShapes() { this.draggingShapes = this.selection.getSelectedShapes(false, true).map(s => new DraggingShape(s)); } private initDraggingConnectors(): void { this.draggingConnectors = []; this.draggingConnectorsIndexByKey = {}; this.selection.getSelectedConnectors(false, true).forEach(c => this.registerConnector(c)); if(this.shouldClone) return; this.draggingShapes.forEach(x => { const attachedConnectors = x.shape.attachedConnectors; if(attachedConnectors) attachedConnectors.forEach(c => { if(!this.containsDraggingConnectorByKey(c.key)) this.registerConnector(c); }); }); } private copySelection() : void { ModelUtils.cloneSelectionToOffset(this.history, this.model, (key: ItemKey) => { const item = this.model.findItem(key); if(item) this.handler.addInteractingItem(item, DiagramModelOperation.AddShape); }, this.selection, 0, 0); } private calculateFixedPosition(evt: DiagramMouseEvent) { this.fixedX = false; this.fixedY = false; if(this.handler.canCalculateFixedPosition(evt)) { const dx = Math.abs(this.startPoint.x - evt.modelPoint.x); const dy = Math.abs(this.startPoint.y - evt.modelPoint.y); if(dx < dy) this.fixedX = true; else this.fixedY = true; } } private containsDraggingConnectorByKey(key: string): boolean { return this.draggingConnectorsIndexByKey[key] !== undefined; } private allowInsertToContainer(evt: DiagramMouseEvent, container: Shape): boolean { if(this.handler.canMultipleSelection(evt)) return false; return container && container.expanded && ModelUtils.canInsertSelectionToContainer(this.model, this.selection, container); } private registerConnector(connector: Connector) { this.draggingConnectorsIndexByKey[connector.key] = this.draggingConnectors.push(new DraggingConnector(connector)) - 1; } private createModelConnectorsWithoutBeginItemInfo(): { connector: Connector, point: Point }[] { const connectors = this.model.findConnectorsCore(c => !c.beginItem && !this.containsDraggingConnectorByKey(c.key)); return connectors.map(c => { return { connector: c, point: c.points[0].clone() }; }); } private createModelConnectorsWithoutEndItemInfo(): { connector: Connector, point: Point }[] { const connectors = this.model.findConnectorsCore(c => !c.endItem && !this.containsDraggingConnectorByKey(c.key)); return connectors.map(c => { return { connector: c, point: c.points[c.points.length - 1].clone() }; }); } private moveConnector(dc: DraggingConnector, evt: DiagramMouseEvent) { const startPoints = dc.startPoints; const offset = Vector.fromPoints(startPoints[0].clone(), this.getSnappedPoint(evt, startPoints[0]).clone()); if(offset.x || offset.y) this.moveConnectorCore(dc.connector, startPoints, dc.startRenderContext, offset); } private moveConnectorCore(connector: Connector, startPoints: Point[], startRenderContext: ConnectorRenderPointsContext, offset: Vector) : void { if(this.shouldClone || ModelUtils.canMoveConnector(this.selectedItems, connector)) this.offsetConnector(connector, startPoints, startRenderContext, offset); else this.changeConnector(connector); } private moveShape(ds: DraggingShape, evt: DiagramMouseEvent) { const shape = ds.shape; const position = this.getSnappedPoint(evt, ds.startPosition); ModelUtils.setShapePosition(this.history, this.model, shape, position); ModelUtils.updateMovingShapeConnections(this.history, shape, this.modelConnectorsWithoutBeginItemInfo, this.modelConnectorsWithoutEndItemInfo, () => { this.visualizerManager.resetConnectionTarget(); this.visualizerManager.resetConnectionPoints(); }, (shape, connectionPointIndex) => { this.visualizerManager.setConnectionTarget(shape, MouseEventElementType.Shape); this.visualizerManager.setConnectionPoints(shape, MouseEventElementType.Shape, connectionPointIndex, true); }, (connector) => this.handler.addInteractingItem(connector) ); if(!this.draggingConnectors.filter(dc => !!this.selectedItems[dc.connector.key]).length) ModelUtils.updateShapeAttachedConnectors(this.history, this.model, shape); } private offsetConnector(connector: Connector, startPoints: Point[], startRenderContext: ConnectorRenderPointsContext, offset: Vector) { const newPoints = startPoints.map(p => this.offsetPoint(p, offset)); if(!newPoints[0].equals(connector.points[0])) this.history.addAndRedo( new ChangeConnectorPointsHistoryItem( connector.key, newPoints, this.offsetRenderContext(startRenderContext, offset) ) ); } private offsetRenderContext(context: ConnectorRenderPointsContext, offset: Vector) : ConnectorRenderPointsContext { if(context === undefined) return undefined; return new ConnectorRenderPointsContext(context.renderPoints.map(p => { const newPoint = this.offsetPoint(p, offset); return new ConnectorRenderPoint(newPoint.x, newPoint.y, p.pointIndex, p.skipped); }), true, context.actualRoutingMode); } private offsetPoint(point: Point, offset: Vector): Point { const pointOffset = Vector.fromPoints(point, this.startPoint); return this.startPoint.clone().offset(offset.x - pointOffset.x, offset.y - pointOffset.y); } private changeConnector(connector: Connector) { ModelUtils.removeConnectorIntermediatePoints(this.history, connector); ModelUtils.updateConnectorAttachedPoints(this.history, this.model, connector); } }