@maxgraph/core
Version:
maxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.
262 lines (258 loc) • 9.19 kB
JavaScript
"use strict";
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2015, JGraph Ltd
Copyright (c) 2006-2015, Gaudenz Alder
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const mathUtils_js_1 = require("../../util/mathUtils.js");
const Point_js_1 = __importDefault(require("../geometry/Point.js"));
const ImageShape_js_1 = __importDefault(require("../shape/node/ImageShape.js"));
const Rectangle_js_1 = __importDefault(require("../geometry/Rectangle.js"));
const RectangleShape_js_1 = __importDefault(require("../shape/node/RectangleShape.js"));
const InternalEvent_js_1 = __importDefault(require("../event/InternalEvent.js"));
const config_js_1 = require("../handler/config.js");
/**
* Implements a single custom handle for vertices.
*
* @class VertexHandle
*/
class VertexHandle {
constructor(state, cursor = 'default', image = null, shape = null) {
this.dependencies = ['snap', 'cells'];
/**
* Specifies the cursor to be used for this handle. Default is 'default'.
*/
this.cursor = 'default';
/**
* Specifies the {@link Image} to be used to render the handle. Default is null.
*/
this.image = null;
/**
* Default is false.
*/
this.ignoreGrid = false;
this.active = true;
this.graph = state.view.graph;
this.state = state;
this.cursor = cursor;
this.image = image;
this.shape = shape;
this.init();
}
/**
* Hook for subclassers to return the current position of the handle.
*/
getPosition(bounds) {
return new Point_js_1.default();
}
/**
* Hooks for subclassers to update the style in the <state>.
*/
setPosition(bounds, pt, me) {
return;
}
/**
* Hook for subclassers to execute the handle.
*/
execute(me) {
return;
}
/**
* Sets the cell style with the given name to the corresponding value in <state>.
*/
copyStyle(key) {
this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
}
/**
* Processes the given {@link MouseEvent} and invokes <setPosition>.
*/
processEvent(me) {
const { scale } = this.graph.view;
const tr = this.graph.view.translate;
let pt = new Point_js_1.default(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);
// Center shape on mouse cursor
if (this.shape != null && this.shape.bounds != null) {
pt.x -= this.shape.bounds.width / scale / 4;
pt.y -= this.shape.bounds.height / scale / 4;
}
// Snaps to grid for the rotated position then applies the rotation for the direction after that
const alpha1 = -(0, mathUtils_js_1.toRadians)(this.getRotation());
const alpha2 = -(0, mathUtils_js_1.toRadians)(this.getTotalRotation()) - alpha1;
pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1), this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2));
this.setPosition(this.state.getPaintBounds(), pt, me);
this.redraw();
}
/**
* Should be called after <setPosition> in <processEvent>.
* This repaints the state using {@link CellRenderer}.
*/
positionChanged() {
if (this.state.text != null) {
this.state.text.apply(this.state);
}
if (this.state.shape != null) {
this.state.shape.apply(this.state);
}
this.graph.cellRenderer.redraw(this.state, true);
}
/**
* Returns the rotation defined in the style of the cell.
*/
getRotation() {
if (this.state.shape != null) {
return this.state.shape.getRotation();
}
return 0;
}
/**
* Returns the rotation from the style and the rotation from the direction of
* the cell.
*/
getTotalRotation() {
if (this.state.shape != null) {
return this.state.shape.getShapeRotation();
}
return 0;
}
/**
* Creates and initializes the shapes required for this handle.
*/
init() {
const html = this.isHtmlRequired();
if (this.image) {
this.shape = new ImageShape_js_1.default(new Rectangle_js_1.default(0, 0, this.image.width, this.image.height), this.image.src);
this.shape.preserveImageAspect = false;
}
else if (!this.shape) {
this.shape = this.createShape(html);
}
this.initShape(html);
}
/**
* Creates and returns the shape for this handle.
*/
createShape(_html) {
const handleSize = config_js_1.HandleConfig.size;
const bounds = new Rectangle_js_1.default(0, 0, handleSize, handleSize);
return new RectangleShape_js_1.default(bounds, config_js_1.HandleConfig.fillColor, config_js_1.HandleConfig.strokeColor);
}
/**
* Initializes <shape> and sets its cursor.
*/
initShape(html) {
const shape = this.shape; // `this.shape` cannot be null.
if (html && shape.isHtmlAllowed()) {
shape.dialect = 'strictHtml';
shape.init(this.graph.container);
}
else {
shape.dialect = this.graph.dialect !== 'svg' ? 'mixedHtml' : 'svg';
if (this.cursor) {
shape.init(this.graph.getView().getOverlayPane());
}
}
InternalEvent_js_1.default.redirectMouseEvents(shape.node, this.graph, this.state);
shape.node.style.cursor = this.cursor;
}
/**
* Renders the shape for this handle.
*/
redraw() {
if (this.shape && this.state.shape) {
let pt = this.getPosition(this.state.getPaintBounds());
if (pt) {
const alpha = (0, mathUtils_js_1.toRadians)(this.getTotalRotation());
pt = this.rotatePoint(this.flipPoint(pt), alpha);
const { scale } = this.graph.view;
const tr = this.graph.view.translate;
const shapeBounds = this.shape.bounds;
shapeBounds.x = Math.floor((pt.x + tr.x) * scale - shapeBounds.width / 2);
shapeBounds.y = Math.floor((pt.y + tr.y) * scale - shapeBounds.height / 2);
// Needed to force update of text bounds
this.shape.redraw();
}
}
}
/**
* Returns true if this handle should be rendered in HTML. This returns true if
* the text node is in the graph container.
*/
isHtmlRequired() {
return !!this.state.text && this.state.text.node.parentNode === this.graph.container;
}
/**
* Rotates the point by the given angle.
*/
rotatePoint(pt, alpha) {
const bounds = this.state.getCellBounds();
const cx = new Point_js_1.default(bounds.getCenterX(), bounds.getCenterY());
const cos = Math.cos(alpha);
const sin = Math.sin(alpha);
return (0, mathUtils_js_1.getRotatedPoint)(pt, cos, sin, cx);
}
/**
* Flips the given point vertically and/or horizontally.
*/
flipPoint(pt) {
if (this.state.shape) {
const bounds = this.state.getCellBounds();
if (this.state.shape.flipH) {
pt.x = 2 * bounds.x + bounds.width - pt.x;
}
if (this.state.shape.flipV) {
pt.y = 2 * bounds.y + bounds.height - pt.y;
}
}
return pt;
}
/**
* Snaps the given point to the grid if ignore is false. This modifies
* the given point in-place and also returns it.
*/
snapPoint(pt, ignore) {
if (!ignore) {
pt.x = this.graph.snap(pt.x);
pt.y = this.graph.snap(pt.y);
}
return pt;
}
/**
* Shows or hides this handle.
*/
setVisible(visible) {
if (this.shape && this.shape.node) {
this.shape.node.style.display = visible ? '' : 'none';
}
}
/**
* Resets the state of this handle by setting its visibility to true.
*/
reset() {
this.setVisible(true);
this.state.style = this.graph.getCellStyle(this.state.cell);
this.positionChanged();
}
/**
* Destroys this handle.
*/
destroy() {
if (this.shape) {
this.shape.destroy();
this.shape = null;
}
}
}
exports.default = VertexHandle;