import React from "react"; import { observer } from "mobx-react"; import { computed, makeObservable, observable, runInAction } from "mobx"; import { BoundingRectBuilder, Rect, rectExpand } from "eez-studio-shared/geometry"; import type { IFlowContext } from "project-editor/flow/flow-interfaces"; import type { IMouseHandler } from "project-editor/flow/editor/mouse-handler"; import { isSelectionMoveable } from "project-editor/flow/editor/mouse-handler"; import { getObjectBoundingRect } from "project-editor/flow/editor/bounding-rects"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { DragAndDropManager } from "project-editor/core/dd"; import { ProjectContext } from "project-editor/project/context"; //////////////////////////////////////////////////////////////////////////////// const SelectedObject = observer( class SelectedObject extends React.Component< { className?: string; rect?: Rect; }, {} > { render() { const { className } = this.props; let rect = this.props.rect; if (!rect) { return null; } if ( className === "EezStudio_FlowEditorSelection_SelectedObjectsParent" ) { rect = rectExpand(rect, 2); } return (
); } } ); //////////////////////////////////////////////////////////////////////////////// export const Selection = observer( class Selection extends React.Component< { context: IFlowContext; mouseHandler?: IMouseHandler; }, {} > { static contextType = ProjectContext; declare context: React.ContextType; selectionNodeRef = React.createRef(); requestAnimationFrameId: any; _selectedObjectRects: Rect[] = []; _selectedObjectsParentRect: Rect | undefined = undefined; constructor(props: { context: IFlowContext; mouseHandler?: IMouseHandler; }) { super(props); makeObservable(this, { selectedObjects: computed, _selectedObjectRects: observable, _selectedObjectsParentRect: observable, selectedObjectsBoundingRect: computed }); } getRects = () => { const getSelectedObjectRects = () => { const viewState = this.props.context.viewState; return this.selectedObjects .map(selectedObject => getObjectBoundingRect(viewState, selectedObject) ) .map(rect => viewState.transform.pageToOffsetRect(rect!)); }; const getSelectedObjectsParentRect = () => { const viewState = this.props.context.viewState; const selectedObjects = this.selectedObjects; if ( !selectedObjects.every( selectedObject => selectedObject.object instanceof ProjectEditor.ActionComponentClass ) ) { let parent = this.props.context.document.findObjectParent( selectedObjects[0] ); if (parent) { let i: number; for (i = 1; i < selectedObjects.length; ++i) { if ( this.props.context.document.findObjectParent( selectedObjects[i] ) != parent ) { break; } } if ( i === selectedObjects.length && parent.showSelectedObjectsParent ) { const parentRect = getObjectBoundingRect( viewState, parent ); if (parentRect) { return viewState.transform.pageToOffsetRect( parentRect ); } } } } return undefined; }; function compareRects(r1: Rect | undefined, r2: Rect | undefined) { if (!r1) { return r2 != undefined; } if (!r2) { return true; } return ( r1.left != r2.left || r1.top != r2.top || r1.width != r2.width || r1.height != r2.height ); } function compareRectArray( arr1: Rect[] | undefined, arr2: Rect[] | undefined ) { if (!arr1) { return arr2 != undefined; } if (!arr2) { return true; } if (arr1.length != arr2.length) { return true; } for (let i = 0; i < arr1.length; i++) { if (compareRects(arr1[i], arr2[i])) { return true; } } return false; } const selectedObjectRects = getSelectedObjectRects(); if ( compareRectArray(selectedObjectRects, this._selectedObjectRects) ) { runInAction(() => { this._selectedObjectRects = selectedObjectRects; }); this.showSelection(); } const selectedObjectsParentRect = getSelectedObjectsParentRect(); if ( compareRects( selectedObjectsParentRect, this._selectedObjectsParentRect ) ) { runInAction(() => { this._selectedObjectsParentRect = selectedObjectsParentRect; }); this.showSelection(); } this.requestAnimationFrameId = requestAnimationFrame(this.getRects); }; componentDidMount() { this.getRects(); } componentWillUnmount() { cancelAnimationFrame(this.requestAnimationFrameId); } showSelectionTimeoutId: any; showSelection() { if (this.context.projectTypeTraits.isDashboard) { return; } if (this.showSelectionTimeoutId) { clearTimeout(this.showSelectionTimeoutId); } this.showSelectionTimeoutId = setTimeout(() => { this.showSelectionTimeoutId = undefined; if (this.selectionNodeRef.current) { this.selectionNodeRef.current.style.display = "block"; } }, 50); } get selectedObjects() { return this.props.context.viewState.selectedObjects.filter( selectedObject => (!this.props.mouseHandler || selectedObject.isSelectable) && !( selectedObject.object instanceof ProjectEditor.ConnectionLineClass || selectedObject.object instanceof ProjectEditor.ActionClass ) ); } get selectedObjectRects() { return this._selectedObjectRects; } get selectedObjectsParentRect() { return this._selectedObjectsParentRect; } get selectedObjectsBoundingRect() { let boundingRectBuilder = new BoundingRectBuilder(); for (let i = 0; i < this.selectedObjectRects.length; i++) { boundingRectBuilder.addRect(this.selectedObjectRects[i]); } return boundingRectBuilder.getRect(); } getResizeHandlers(boundingRect: Rect) { const resizeHandlers = this.props.context.viewState.getResizeHandlers(); if (!resizeHandlers || resizeHandlers.length === 0) { return null; } let left = boundingRect.left; let width = boundingRect.width; let top = boundingRect.top; let height = boundingRect.height; const B = 8; // HANDLE SIZE const A = B / 2; if (width < 3 * B) { width = 3 * B; left -= (width - boundingRect.width) / 2; } if (height < 3 * B) { height = 3 * B; top -= (height - boundingRect.height) / 2; } return resizeHandlers.map(resizeHandler => { const x = left + (resizeHandler.x * width) / 100; const y = top + (resizeHandler.y * height) / 100; const style = { left: x - A + "px", top: y - A + "px", width: B + "px", height: B + "px", cursor: resizeHandler.type }; return (
); }); } render() { let selectedObjects = this.selectedObjects; const isSelectionVisible = selectedObjects.length > 0; const isSelectedObjectComponentPaletteItem = selectedObjects.length === 1 && selectedObjects[0].object === DragAndDropManager.dragObject; let selectedObjectRectsElement; let selectedObjectsBoundingRectElement; let resizeHandlersElement; let selectedObjectsParentElement; if (isSelectionVisible) { // build selectedObjectRectsElement const selectedObjectClassName = selectedObjects.length > 1 || !selectedObjects[0].isSelectable ? "EezStudio_FlowEditorSelection_SelectedObject" : "EezStudio_FlowEditorSelection_BoundingRect"; selectedObjectRectsElement = selectedObjects.map( (object, i) => ( ) ); // build selectedObjectsBoundingRectElement if (selectedObjects.length > 1) { let style: React.CSSProperties = { position: "absolute", left: this.selectedObjectsBoundingRect.left, top: this.selectedObjectsBoundingRect.top, width: this.selectedObjectsBoundingRect.width, height: this.selectedObjectsBoundingRect.height }; selectedObjectsBoundingRectElement = (
); } // build resizeHandlersElement if ( !this.props.mouseHandler && !selectedObjects.find( selectedObject => !selectedObject.isSelectable ) ) { resizeHandlersElement = this.getResizeHandlers( this.selectedObjectsBoundingRect ); } // if (this.selectedObjectsParentRect) { // selectedObjectsParentElement = ( // // ); // } } const style: React.CSSProperties = { pointerEvents: isSelectedObjectComponentPaletteItem ? "none" : undefined }; if (!isSelectionMoveable(this.props.context)) { style.cursor = "default"; } return (
{isSelectionVisible && ( {selectedObjectsParentElement}
{selectedObjectRectsElement} {selectedObjectsBoundingRectElement} {resizeHandlersElement}
)}
); } } );