11/********************************************************************************
2- * Copyright (c) 2019-2024 EclipseSource and others.
2+ * Copyright (c) 2019-2025 EclipseSource and others.
33 *
44 * This program and the accompanying materials are made available under the
55 * terms of the Eclipse Public License v. 2.0 which is available at
1313 *
1414 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
1515 ********************************************************************************/
16- import { Bounds , Dimension , GModelElement , GNode , GParentElement , Point , isBoundsAware , isMoveable } from '@eclipse-glsp/sprotty' ;
16+ import {
17+ Bounds ,
18+ Dimension ,
19+ GChildElement ,
20+ GModelElement ,
21+ GNode ,
22+ GParentElement ,
23+ isBoundsAware ,
24+ isMoveable ,
25+ Point ,
26+ toTypeGuard
27+ } from '@eclipse-glsp/sprotty' ;
1728import { injectable } from 'inversify' ;
18- import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback' ;
19- import { BoundsAwareModelElement } from '../../utils/gmodel-util' ;
29+ import { CSS_GHOST_ELEMENT , ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback' ;
30+ import { BoundsAwareModelElement , getChildren , getParents } from '../../utils/gmodel-util' ;
2031import { toAbsoluteBounds } from '../../utils/viewpoint-util' ;
32+ import { ContainerElement , isContainable } from '../hints/model' ;
2133import { GResizeHandle } from './model' ;
2234
2335/**
@@ -39,34 +51,75 @@ export interface IMovementRestrictor {
3951 cssClasses ?: string [ ] ;
4052}
4153
54+ export interface MoveElementContext {
55+ element : GModelElement ;
56+ elementAtNewLocation : BoundsAwareModelElement ;
57+ parentContainers : ContainerElement [ ] ;
58+ childNodes : GNode [ ] ;
59+ }
60+
4261/**
4362 * A `IMovementRestrictor` that checks for overlapping elements. Move operations
44- * are only valid if the element does not collide with another element after moving.
63+ * are only valid if the element does not collide with another element/node after moving.
4564 */
4665@injectable ( )
4766export class NoOverlapMovementRestrictor implements IMovementRestrictor {
4867 cssClasses = [ 'movement-not-allowed' ] ;
4968
5069 validate ( element : GModelElement , newLocation ?: Point ) : boolean {
51- if ( ! isMoveable ( element ) || ! newLocation ) {
70+ if ( ! ( element instanceof GChildElement ) || ! isMoveable ( element ) || ! newLocation ) {
5271 return false ;
5372 }
54- // Create ghost element at the newLocation
73+
74+ const moveContext = this . createMoveElementContext ( element , newLocation ) ;
75+ const elementsToValidate = Array . from ( element . root . index . all ( ) ) . filter ( e =>
76+ this . isBoundsRelevant ( e , moveContext )
77+ ) as BoundsAwareModelElement [ ] ;
78+
79+ const valid = ! elementsToValidate . some ( e => this . areOverlapping ( e , moveContext . elementAtNewLocation ) ) ;
80+ return valid ;
81+ }
82+
83+ protected createMoveElementContext ( element : GModelElement , newLocation : Point ) : MoveElementContext {
84+ const parentContainers = getParents ( element , isContainable ) ;
85+ const childNodes = getChildren ( element , toTypeGuard ( GNode ) ) ;
86+ // Create a mock element at the newLocation for overlap checking
5587 const dimensions : Dimension = isBoundsAware ( element ) ? element . bounds : { width : 1 , height : 1 } ;
56- const ghostElement = Object . create ( element ) as BoundsAwareModelElement ;
57- ghostElement . bounds = { ...dimensions , ...newLocation } ;
58- ghostElement . type = 'Ghost' ;
59- ghostElement . id = element . id ;
60- return ! Array . from (
61- element . root . index
62- . all ( )
63- . filter ( node => node . id !== ghostElement . id && node !== ghostElement . root && node instanceof GNode )
64- . map ( node => node as BoundsAwareModelElement )
65- ) . some ( e => this . areOverlapping ( e , ghostElement ) ) ;
88+ const elementAtNewLocation = Object . create ( element ) as BoundsAwareModelElement as BoundsAwareModelElement ;
89+ elementAtNewLocation . bounds = { ...dimensions , ...newLocation } ;
90+
91+ return {
92+ element,
93+ elementAtNewLocation,
94+ parentContainers,
95+ childNodes
96+ } ;
6697 }
6798
68- protected isBoundsRelevant ( element : GModelElement , ghostElement : BoundsAwareModelElement ) : element is BoundsAwareModelElement {
69- return element . id !== ghostElement . id && element !== ghostElement . root && element instanceof GNode && isBoundsAware ( element ) ;
99+ protected isBoundsRelevant ( element : GModelElement , moveContext : MoveElementContext ) : element is BoundsAwareModelElement {
100+ // Only consider GNodes that are not the element being moved (or one of its children)
101+ if (
102+ ! ( element instanceof GNode ) ||
103+ element . id === moveContext . element . id ||
104+ moveContext . childNodes . some ( child => child . id === element . id )
105+ ) {
106+ return false ;
107+ }
108+
109+ // Do not consider parent containers of the element being moved
110+ if ( moveContext . parentContainers . length > 0 && moveContext . parentContainers . some ( container => container . id === element . id ) ) {
111+ return false ;
112+ }
113+
114+ // If the element is a ghost element (node creation), don't consider overlap checks for potential parent containers
115+ if (
116+ moveContext . element . cssClasses ?. includes ( CSS_GHOST_ELEMENT ) &&
117+ isContainable ( element ) &&
118+ element . isContainableElement ( moveContext . element . type )
119+ ) {
120+ return false ;
121+ }
122+ return true ;
70123 }
71124
72125 protected areOverlapping ( element1 : BoundsAwareModelElement , element2 : BoundsAwareModelElement ) : boolean {
0 commit comments