@eclipse-glsp/client
Version:
A sprotty-based client for GLSP
330 lines • 17 kB
JavaScript
"use strict";
/********************************************************************************
* Copyright (c) 2024 EclipseSource 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
********************************************************************************/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MoveableRoutingHandle = exports.MoveableResizeHandle = exports.ChangeBoundsTracker = exports.TrackedElementResize = exports.DEFAULT_RESIZE_OPTIONS = exports.TrackedMove = exports.TrackedElementMove = exports.DEFAULT_MOVE_OPTIONS = void 0;
const sprotty_1 = require("@eclipse-glsp/sprotty");
const gmodel_util_1 = require("../../../utils/gmodel-util");
const model_1 = require("../../change-bounds/model");
const tracker_1 = require("../../change-bounds/tracker");
exports.DEFAULT_MOVE_OPTIONS = {
snap: true,
restrict: true,
validate: true,
skipStatic: true,
skipInvalid: false
};
var TrackedElementMove;
(function (TrackedElementMove) {
function is(obj) {
return ((0, sprotty_1.hasObjectProp)(obj, 'element') &&
(0, sprotty_1.hasObjectProp)(obj, 'fromPosition') &&
(0, sprotty_1.hasObjectProp)(obj, 'toPosition') &&
(0, sprotty_1.hasBooleanProp)(obj, 'valid'));
}
TrackedElementMove.is = is;
})(TrackedElementMove || (exports.TrackedElementMove = TrackedElementMove = {}));
var TrackedMove;
(function (TrackedMove) {
function is(obj) {
return sprotty_1.Movement.is(obj) && (0, sprotty_1.hasBooleanProp)(obj, 'valid');
}
TrackedMove.is = is;
})(TrackedMove || (exports.TrackedMove = TrackedMove = {}));
exports.DEFAULT_RESIZE_OPTIONS = {
snap: true,
restrict: true,
validate: true,
symmetric: true,
constrainResize: true,
skipStatic: true,
skipInvalidSize: false,
skipInvalidMove: false
};
var TrackedElementResize;
(function (TrackedElementResize) {
function is(obj) {
return ((0, sprotty_1.isBoundsAware)(obj.element) && (0, sprotty_1.hasObjectProp)(obj, 'fromBounds') && (0, sprotty_1.hasObjectProp)(obj, 'toBounds') && (0, sprotty_1.hasObjectProp)(obj, 'valid'));
}
TrackedElementResize.is = is;
})(TrackedElementResize || (exports.TrackedElementResize = TrackedElementResize = {}));
class ChangeBoundsTracker {
constructor(manager) {
this.manager = manager;
this.diagramMovement = new tracker_1.DiagramMovementCalculator(manager.positionTracker);
}
startTracking() {
this.diagramMovement.init();
return this;
}
updateTrackingPosition(param) {
const update = TrackedMove.is(param) ? sprotty_1.Vector.max(...param.elementMoves.map(move => move.moveVector)) : param;
this.diagramMovement.updatePosition(update);
}
isTracking() {
return this.diagramMovement.hasPosition;
}
stopTracking() {
this.diagramMovement.dispose();
return this;
}
//
// MOVE
//
moveElements(elements, opts) {
const options = this.resolveMoveOptions(opts);
const update = this.calculateDiagramMovement();
const move = { ...update, elementMoves: [], valid: true, options };
if (sprotty_1.Vector.isZero(update.vector) && options.skipStatic) {
// no movement detected so elements won't be moved, exit early
return move;
}
// calculate move for each element
const elementsToMove = this.getMoveableElements(elements, options);
for (const element of elementsToMove) {
const elementMove = this.calculateElementMove(element, update.vector, options);
if (!this.skipElementMove(elementMove, options)) {
move.elementMoves.push(elementMove);
move.valid && (move.valid = elementMove.valid);
}
}
return move;
}
resolveMoveOptions(opts) {
var _a, _b;
return {
...exports.DEFAULT_MOVE_OPTIONS,
...opts,
snap: this.manager.usePositionSnap((_a = opts === null || opts === void 0 ? void 0 : opts.snap) !== null && _a !== void 0 ? _a : exports.DEFAULT_MOVE_OPTIONS.snap),
restrict: this.manager.useMovementRestriction((_b = opts === null || opts === void 0 ? void 0 : opts.restrict) !== null && _b !== void 0 ? _b : exports.DEFAULT_MOVE_OPTIONS.restrict)
};
}
calculateDiagramMovement() {
return this.diagramMovement.calculateMoveToCurrent();
}
getMoveableElements(elements, options) {
var _a;
return Array.isArray(elements) ? elements : (0, gmodel_util_1.getElements)(elements.ctx.index, elements.elementIDs, (_a = elements.guard) !== null && _a !== void 0 ? _a : sprotty_1.isMoveable);
}
skipElementMove(elementMove, options) {
return (options.skipInvalid && !elementMove.valid) || (options.skipStatic && sprotty_1.Vector.isZero(elementMove.moveVector));
}
calculateElementMove(element, vector, options) {
const fromPosition = element.position;
const toPosition = sprotty_1.Point.add(fromPosition, vector);
const move = { element, fromPosition, toPosition, valid: true, moveVector: vector, sourceVector: vector };
if (options.snap) {
move.toPosition = this.snapPosition(move, options);
}
if (options.restrict) {
move.toPosition = this.restrictMovement(move, options);
}
if (options.validate) {
move.valid = this.validateElementMove(move, options);
}
move.moveVector = sprotty_1.Point.vector(move.fromPosition, move.toPosition);
return move;
}
snapPosition(elementMove, opts) {
return this.manager.snapPosition(elementMove.element, elementMove.toPosition);
}
restrictMovement(elementMove, opts) {
const movement = sprotty_1.Point.move(elementMove.fromPosition, elementMove.toPosition);
return this.manager.restrictMovement(elementMove.element, movement).to;
}
validateElementMove(elementMove, opts) {
return this.manager.hasValidPosition(elementMove.element, elementMove.toPosition);
}
//
// RESIZE
//
resizeElements(handle, opts) {
const options = this.resolveResizeOptions(opts);
const update = this.calculateDiagramMovement();
const handleMove = this.calculateHandleMove(new MoveableResizeHandle(handle), update.vector, options);
const resize = { ...update, valid: { move: true, size: true }, options, handleMove, elementResizes: [] };
if (sprotty_1.Vector.isZero(handleMove.moveVector) && options.skipStatic) {
// no movement detected so elements won't be moved, exit early
return resize;
}
// calculate resize for each element (typically only one element is resized at a time but customizations are possible)
const elementsToResize = this.getResizableElements(handle, options);
for (const element of elementsToResize) {
const elementResize = this.calculateElementResize(element, handleMove, options);
if (!this.skipElementResize(elementResize, options)) {
resize.elementResizes.push(elementResize);
resize.valid.move = resize.valid.move && elementResize.valid.move;
resize.valid.size = resize.valid.size && elementResize.valid.size;
}
}
return resize;
}
resolveResizeOptions(opts) {
var _a, _b, _c;
return {
...exports.DEFAULT_RESIZE_OPTIONS,
...opts,
snap: this.manager.usePositionSnap((_a = opts === null || opts === void 0 ? void 0 : opts.snap) !== null && _a !== void 0 ? _a : exports.DEFAULT_RESIZE_OPTIONS.snap),
restrict: this.manager.useMovementRestriction((_b = opts === null || opts === void 0 ? void 0 : opts.restrict) !== null && _b !== void 0 ? _b : exports.DEFAULT_RESIZE_OPTIONS.restrict),
symmetric: this.manager.useSymmetricResize((_c = opts === null || opts === void 0 ? void 0 : opts.symmetric) !== null && _c !== void 0 ? _c : exports.DEFAULT_RESIZE_OPTIONS.symmetric)
};
}
calculateHandleMove(handle, diagramMovement, opts) {
const moveOptions = this.resolveMoveOptions({ ...opts, validate: false });
return this.calculateElementMove(handle, diagramMovement, moveOptions);
}
getResizableElements(handle, options) {
return [handle.parent];
}
skipElementResize(elementResize, options) {
return ((options.skipInvalidMove && !elementResize.valid.move) ||
(options.skipInvalidSize && !elementResize.valid.size) ||
(options.skipStatic && sprotty_1.Dimension.equals(elementResize.fromBounds, elementResize.toBounds)));
}
calculateElementResize(element, handleMove, options) {
const fromBounds = element.bounds;
const toBounds = this.calculateElementBounds(element, handleMove, options);
const resize = { element, fromBounds, toBounds, valid: { size: true, move: true } };
if (options.validate) {
resize.valid.size = this.manager.hasValidSize(resize.element, resize.toBounds);
resize.valid.move = handleMove.valid && this.manager.hasValidPosition(resize.element, resize.toBounds);
}
return resize;
}
calculateElementBounds(element, handleMove, options) {
let toBounds = this.calculateBounds(element.bounds, handleMove);
if (options.symmetric) {
const symmetricHandleMove = this.calculateSymmetricHandleMove(handleMove, options);
toBounds = this.calculateBounds(toBounds, symmetricHandleMove);
}
if (!options.constrainResize || this.manager.hasValidSize(element, toBounds)) {
return toBounds;
}
// we need to adjust to the minimum size but it is not enough to simply set the size
// we need to make sure that the element is still at the expected position
// we therefore constrain the movement vector to actually avoid going below the minimum size
const minimum = this.manager.getMinimumSize(element);
handleMove.moveVector = this.constrainResizeVector(element.bounds, handleMove, minimum);
if (options.symmetric) {
// if we have symmetric resize we want to distribute the constrained movement vector to both sides
// but only for the dimension that was actually resized beyond the minimum
handleMove.moveVector.x = element.bounds.width > minimum.width ? handleMove.moveVector.x / 2 : handleMove.moveVector.x;
handleMove.moveVector.y = element.bounds.height > minimum.height ? handleMove.moveVector.y / 2 : handleMove.moveVector.y;
}
toBounds = this.calculateBounds(element.bounds, handleMove);
if (options.symmetric) {
// since we already distributed the available movement vector, we do not want to snap the symmetric handle move
const symmetricHandleMove = this.calculateSymmetricHandleMove(handleMove, { ...options, snap: false });
toBounds = this.calculateBounds(toBounds, symmetricHandleMove);
}
return toBounds;
}
calculateSymmetricHandleMove(handleMove, options) {
const moveOptions = this.resolveMoveOptions({ ...options, validate: false, restrict: false });
return this.calculateElementMove(handleMove.element.opposite(), sprotty_1.Vector.reverse(handleMove.moveVector), moveOptions);
}
calculateBounds(src, handleMove) {
if (!handleMove || sprotty_1.Vector.isZero(handleMove.moveVector)) {
return src;
}
return this.doCalculateBounds(src, handleMove.moveVector, handleMove.element.location);
}
doCalculateBounds(src, vector, location) {
switch (location) {
case model_1.ResizeHandleLocation.TopLeft:
return { x: src.x + vector.x, y: src.y + vector.y, width: src.width - vector.x, height: src.height - vector.y };
case model_1.ResizeHandleLocation.Top:
return { ...src, y: src.y + vector.y, height: src.height - vector.y };
case model_1.ResizeHandleLocation.TopRight:
return { ...src, y: src.y + vector.y, width: src.width + vector.x, height: src.height - vector.y };
case model_1.ResizeHandleLocation.Right:
return { ...src, width: src.width + vector.x };
case model_1.ResizeHandleLocation.BottomRight:
return { ...src, width: src.width + vector.x, height: src.height + vector.y };
case model_1.ResizeHandleLocation.Bottom:
return { ...src, height: src.height + vector.y };
case model_1.ResizeHandleLocation.BottomLeft:
return { ...src, x: src.x + vector.x, width: src.width - vector.x, height: src.height + vector.y };
case model_1.ResizeHandleLocation.Left:
return { ...src, x: src.x + vector.x, width: src.width - vector.x };
}
}
constrainResizeVector(src, handleMove, minimum) {
const vector = handleMove.moveVector;
switch (handleMove.element.location) {
case model_1.ResizeHandleLocation.TopLeft:
vector.x = src.width - vector.x < minimum.width ? src.width - minimum.width : vector.x;
vector.y = src.height - vector.y < minimum.height ? src.height - minimum.height : vector.y;
break;
case model_1.ResizeHandleLocation.Top:
vector.y = src.height - vector.y < minimum.height ? src.height - minimum.height : vector.y;
break;
case model_1.ResizeHandleLocation.TopRight:
vector.x = src.width + vector.x < minimum.width ? minimum.width - src.width : vector.x;
vector.y = src.height - vector.y < minimum.height ? src.height - minimum.height : vector.y;
break;
case model_1.ResizeHandleLocation.Right:
vector.x = src.width + vector.x < minimum.width ? minimum.width - src.width : vector.x;
break;
case model_1.ResizeHandleLocation.BottomRight:
vector.x = src.width + vector.x < minimum.width ? minimum.width - src.width : vector.x;
vector.y = src.height + vector.y < minimum.height ? minimum.height - src.height : vector.y;
break;
case model_1.ResizeHandleLocation.Bottom:
vector.y = src.height + vector.y < minimum.height ? minimum.height - src.height : vector.y;
break;
case model_1.ResizeHandleLocation.BottomLeft:
vector.x = src.width - vector.x < minimum.width ? src.width - minimum.width : vector.x;
vector.y = src.height + vector.y < minimum.height ? minimum.height - src.height : vector.y;
break;
case model_1.ResizeHandleLocation.Left:
vector.x = src.width - vector.x < minimum.width ? src.width - minimum.width : vector.x;
break;
}
return vector;
}
dispose() {
this.stopTracking();
}
}
exports.ChangeBoundsTracker = ChangeBoundsTracker;
class MoveableResizeHandle extends model_1.GResizeHandle {
constructor(handle, location = handle.location, position = model_1.GResizeHandle.getHandlePosition(handle.parent, location)) {
super(location, handle.type, handle.hoverFeedback);
this.handle = handle;
this.location = location;
this.position = position;
this.id = handle.id;
// this only acts as a wrapper so we do not actually add this to the parent but still want the parent reference
this.parent = handle.parent;
}
opposite() {
return new MoveableResizeHandle(this.handle, model_1.ResizeHandleLocation.opposite(this.location));
}
}
exports.MoveableResizeHandle = MoveableResizeHandle;
class MoveableRoutingHandle extends sprotty_1.GRoutingHandle {
constructor(handle, position) {
super();
this.handle = handle;
this.position = position;
this.id = handle.id;
// this only acts as a wrapper so we do not actually add this to the parent but still want the parent reference
this.parent = handle.parent;
}
}
exports.MoveableRoutingHandle = MoveableRoutingHandle;
//# sourceMappingURL=change-bounds-tracker.js.map