@eclipse-glsp/client
Version:
A sprotty-based client for GLSP
113 lines (100 loc) • 4.56 kB
text/typescript
/********************************************************************************
* Copyright (c) 2023 Business Informatics Group (TU Wien) and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import {
Bounds,
GChildElement,
GModelElement,
GModelRoot,
GNode,
IActionDispatcher,
Point,
TYPES,
isBoundsAware,
isSelectable,
toArray
} from '@eclipse-glsp/sprotty';
import { inject, injectable } from 'inversify';
import { SelectableBoundsAware } from '../../../utils/gmodel-util';
import { ElementNavigator } from './element-navigator';
export class PositionNavigator implements ElementNavigator {
protected readonly actionDispatcher: IActionDispatcher;
previous(
root: Readonly<GModelRoot>,
current: SelectableBoundsAware,
previousCurrent?: SelectableBoundsAware,
predicate?: (element: GModelElement) => boolean
): GModelElement | undefined {
return this.getNearestElement(root, current, e => this.bounds(root, e).x < this.bounds(root, current).x);
}
next(
root: Readonly<GModelRoot>,
current: SelectableBoundsAware,
previousCurrent?: SelectableBoundsAware,
predicate?: (element: GModelElement) => boolean
): GModelElement | undefined {
return this.getNearestElement(root, current, e => this.bounds(root, e).x > this.bounds(root, current).x);
}
up(
root: Readonly<GModelRoot>,
current: SelectableBoundsAware,
previousCurrent?: SelectableBoundsAware,
predicate?: (element: GModelElement) => boolean
): GModelElement | undefined {
return this.getNearestElement(root, current, e => this.bounds(root, e).y < this.bounds(root, current).y);
}
down(
root: Readonly<GModelRoot>,
current: SelectableBoundsAware,
previousCurrent?: SelectableBoundsAware,
predicate?: (element: GModelElement) => boolean
): GModelElement | undefined {
return this.getNearestElement(root, current, e => this.bounds(root, e).y > this.bounds(root, current).y);
}
protected getNearestElement(
root: Readonly<GModelRoot>,
current: SelectableBoundsAware,
filter: (e: SelectableBoundsAware) => boolean
): GModelElement | undefined {
const elements = this.boundElements(root).filter(filter);
return this.sortByDistance(root, current, elements)[0];
}
protected sortByDistance(root: GModelRoot, current: SelectableBoundsAware, elements: SelectableBoundsAware[]): SelectableBoundsAware[] {
// https://www.tutorialspoint.com/sort-array-of-points-by-ascending-distance-from-a-given-point-javascript
const distance = (coor1: Point, coor2: Point): number => {
const x = coor2.x - coor1.x;
const y = coor2.y - coor1.y;
return Math.sqrt(x * x + y * y);
};
return elements.sort(
(a, b) =>
distance(this.bounds(root, a), this.bounds(root, current)) - distance(this.bounds(root, b), this.bounds(root, current))
);
}
protected boundElements(root: Readonly<GModelRoot>): SelectableBoundsAware[] {
return toArray(root.index.all().filter(e => e instanceof GNode && isSelectable(e) && isBoundsAware(e))) as SelectableBoundsAware[];
}
protected bounds(root: Readonly<GModelRoot>, element: SelectableBoundsAware): Bounds {
return this.boundsInViewport(element, element.bounds, root);
}
protected boundsInViewport(element: GModelElement, bounds: Bounds, viewport: GModelRoot): Bounds {
if (element instanceof GChildElement && element.parent !== viewport) {
return this.boundsInViewport(element.parent, element.parent.localToParent(bounds) as Bounds, viewport);
} else {
return bounds;
}
}
}