UNPKG

7.24 kBJavaScriptView Raw
1/*
2* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
3*/
4import * as go from '../release/go-module.js';
5/**
6 * The RotateMultipleTool class lets the user rotate multiple objects at a time.
7 * When more than one part is selected, rotates all parts, revolving them about their collective center.
8 * If the control key is held down during rotation, rotates all parts individually.
9 *
10 * Caution: this only works for Groups that do *not* have a Placeholder.
11 *
12 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/RotateMultiple.html">Rotate Multiple</a> sample.
13 * @category Tool Extension
14 */
15export class RotateMultipleTool extends go.RotatingTool {
16 /**
17 * Constructs a RotateMultipleTool and sets the name for the tool.
18 */
19 constructor() {
20 super();
21 /**
22 * Holds references to all selected non-Link Parts and their offset & angles
23 */
24 this._initialInfo = null;
25 /**
26 * Initial angle when rotating as a whole
27 */
28 this._initialAngle = 0;
29 /**
30 * Rotation point of selection
31 */
32 this._centerPoint = new go.Point();
33 this.name = 'RotateMultiple';
34 }
35 /**
36 * Calls {@link RotatingTool#doActivate}, and then remembers the center point of the collection,
37 * and the initial distances and angles of selected parts to the center.
38 */
39 doActivate() {
40 super.doActivate();
41 const diagram = this.diagram;
42 // center point of the collection
43 this._centerPoint = diagram.computePartsBounds(diagram.selection).center;
44 // remember the angle relative to the center point when rotating the whole collection
45 this._initialAngle = this._centerPoint.directionPoint(diagram.lastInput.documentPoint);
46 // remember initial angle and distance for each Part
47 const infos = new go.Map();
48 const tool = this;
49 diagram.selection.each(function (part) {
50 tool.walkTree(part, infos);
51 });
52 this._initialInfo = infos;
53 }
54 /**
55 * @hidden @internal
56 */
57 walkTree(part, infos) {
58 if (part === null || part instanceof go.Link)
59 return;
60 // distance from _centerPoint to locationSpot of part
61 const dist = Math.sqrt(this._centerPoint.distanceSquaredPoint(part.location));
62 // calculate initial relative angle
63 const dir = this._centerPoint.directionPoint(part.location);
64 // saves part-angle combination in array
65 infos.add(part, new PartInfo(dir, dist, part.rotateObject.angle));
66 // recurse into Groups
67 if (part instanceof go.Group) {
68 const it = part.memberParts.iterator;
69 while (it.next())
70 this.walkTree(it.value, infos);
71 }
72 }
73 /**
74 * Clean up any references to Parts.
75 */
76 doDeactivate() {
77 this._initialInfo = null;
78 super.doDeactivate();
79 }
80 /**
81 * Rotate all selected objects about their collective center.
82 * When the control key is held down while rotating, all selected objects are rotated individually.
83 */
84 rotate(newangle) {
85 const diagram = this.diagram;
86 if (this._initialInfo === null)
87 return;
88 const node = this.adornedObject !== null ? this.adornedObject.part : null;
89 if (node === null)
90 return;
91 const e = diagram.lastInput;
92 // when rotating individual parts, remember the original angle difference
93 const angleDiff = newangle - node.rotateObject.angle;
94 const tool = this;
95 this._initialInfo.each(function (kvp) {
96 const part = kvp.key;
97 if (part instanceof go.Link)
98 return; // only Nodes and simple Parts
99 const partInfo = kvp.value;
100 // rotate every selected non-Link Part
101 // find information about the part set in RotateMultipleTool._initialInformation
102 if (e.control || e.meta) {
103 if (node === part) {
104 part.rotateObject.angle = newangle;
105 }
106 else {
107 part.rotateObject.angle += angleDiff;
108 }
109 }
110 else {
111 const radAngle = newangle * (Math.PI / 180); // converts the angle traveled from degrees to radians
112 // calculate the part's x-y location relative to the central rotation point
113 const offsetX = partInfo.distance * Math.cos(radAngle + partInfo.placementAngle);
114 const offsetY = partInfo.distance * Math.sin(radAngle + partInfo.placementAngle);
115 // move part
116 part.location = new go.Point(tool._centerPoint.x + offsetX, tool._centerPoint.y + offsetY);
117 // rotate part
118 part.rotateObject.angle = partInfo.rotationAngle + newangle;
119 }
120 });
121 }
122 /**
123 * Calculate the desired angle with different rotation points,
124 * depending on whether we are rotating the whole selection as one, or Parts individually.
125 * @param {Point} newPoint in document coordinates
126 */
127 computeRotate(newPoint) {
128 const diagram = this.diagram;
129 if (this.adornedObject === null)
130 return 0.0;
131 let angle = 0.0;
132 const e = diagram.lastInput;
133 if (e.control || e.meta) { // relative to the center of the Node whose handle we are rotating
134 const part = this.adornedObject.part;
135 if (part !== null) {
136 const rotationPoint = part.getDocumentPoint(part.locationSpot);
137 angle = rotationPoint.directionPoint(newPoint);
138 }
139 }
140 else { // relative to the center of the whole selection
141 angle = this._centerPoint.directionPoint(newPoint) - this._initialAngle;
142 }
143 if (angle >= 360)
144 angle -= 360;
145 else if (angle < 0)
146 angle += 360;
147 const interval = Math.min(Math.abs(this.snapAngleMultiple), 180);
148 const epsilon = Math.min(Math.abs(this.snapAngleEpsilon), interval / 2);
149 // if it's close to a multiple of INTERVAL degrees, make it exactly so
150 if (!diagram.lastInput.shift && interval > 0 && epsilon > 0) {
151 if (angle % interval < epsilon) {
152 angle = Math.floor(angle / interval) * interval;
153 }
154 else if (angle % interval > interval - epsilon) {
155 angle = (Math.floor(angle / interval) + 1) * interval;
156 }
157 if (angle >= 360)
158 angle -= 360;
159 else if (angle < 0)
160 angle += 360;
161 }
162 return angle;
163 }
164}
165/**
166 * Internal class to remember a Part's offset and angle.
167 */
168class PartInfo {
169 constructor(placementAngle, distance, rotationAngle) {
170 this.placementAngle = placementAngle * (Math.PI / 180); // in radians
171 this.distance = distance;
172 this.rotationAngle = rotationAngle; // in degrees
173 }
174}