-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathSpreadsheetActions.js
377 lines (352 loc) · 14.7 KB
/
SpreadsheetActions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/// <reference types="@mapeditor/tiled-api" />
/* Spreadsheet Actions script by eishiya, last updated 14 Feb 2023
Object shifting by edigeronimo
Adds several actions to the Edit menu to insert and delete columns and rows
at a selection location. The selection size determines the number of columns
or rows that will be inserted or added.
The actions use your selection's bounding box, not each individual selected
row/column! If you have a complex selection, more rows/columns may be
affected than you expect.
When deleting columns/rows, the selected columns/rows will be deleted.
When inserting columns/rows, they will be inserted to the left or above
the selected area, so the inserted columns/rows will end up selected.
When inserting rows/columns and the selection is outside the map, additional
rows/columns may be inserted to ensure the map is large enough to include
the selected area.
By default, this script also shifts over objects. This works well on ortho-
graphic and isometric maps, but may give inconsistent results on staggered
maps. You can set shiftObjects to false below to disable object shifting.
This script will not modify locked layers by default. Prior to Tiled 1.10,
locked Tile Layers become unlocked when the map is resized, so this script
may not work as expected in older versions of Tiled.
If you want this script to modify even those layers that are locked, set
modifyLockedLayers to true below.
*/
var spreadsheetActions = {
//CONFIGURATION:
shiftObjects: true, //Set this to false if you don't want objects to be shifted over.
modifyLockedLayers: false, //Set this to true to modify even locked layers.
//END CONFIGURATION
tileLayers: [], //List of layers on the current map.
objectLayers: [], //List of object layers on the current map.
savedSelection: null, //The user's selection, backed up before we mess with it.
/* Calculates the the map's region.
For finite maps, this is just the map size.
For infinite maps, calculates the total area of all tile layers.
*/
getMapBounds: function(map) {
if(map.infinite) {
let bounds = null;
function updateBounds(layer) {
if(layer.isTileMap || layer.isGroupLayer) {
for(let i = 0; i < layer.layerCount; ++i) {
updateBounds(layer.layerAt(i));
}
} else if(layer.isTileLayer) {
spreadsheetActions.tileLayers.push(layer);
if(map.infinite) {
if(bounds) {
let newBounds = layer.region().boundingRect;
if(newBounds.top < bounds.top) {
bounds.height += (bounds.top - newBounds.top);
bounds.y = newBounds.top;
}
if(newBounds.bottom > bounds.bottom) {
bounds.height += (newBounds.bottom - bounds.bottom +1);
}
if(newBounds.left < bounds.left) {
bounds.width += (bounds.left - newBounds.left);
bounds.x = newBounds.x;
}
if(newBounds.right > bounds.right) {
bounds.width += (newBounds.right - bounds.right +1);
}
} else {
bounds = layer.region().boundingRect;
bounds = Qt.rect(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
}
}
updateBounds(map);
if(bounds)
return bounds;
return Qt.rect(0, 0, 0, 0); //no populated tile layers found!
}
return Qt.rect(0, 0, map.width, map.height);
},
/* Compiles a list of all tile and object layers in the map, recursively.
Doesn't return anything, just saves them to tileLayers and objectLayers.
This can't be merged into getMapBounds because resizing the map later
will invalidate all these layer references.
*/
getMapLayers: function(map) {
spreadsheetActions.tileLayers.length = 0;
spreadsheetActions.objectLayers.length = 0;
function getLayers(layer) {
if( layer.locked && !spreadsheetActions.modifyLockedLayers )
return; //ignore locked layers. Locked groups are effectively locking all children.
if(layer.isTileMap || layer.isGroupLayer) {
for(let i = 0; i < layer.layerCount; ++i) {
getLayers(layer.layerAt(i));
}
} else if(layer.isTileLayer) {
spreadsheetActions.tileLayers.push(layer);
} else if (layer.isObjectLayer && spreadsheetActions.shiftObjects) {
spreadsheetActions.objectLayers.push(layer);
}
}
getLayers(map);
},
//Saves the user's current selection so it can be restored later. This is a full copy, not a reference.
saveSelection: function(map) {
spreadsheetActions.savedSelection = map.selectedArea.get();
},
//Restores the saved selection.
restoreSelection: function(map) {
map.selectedArea.set(spreadsheetActions.savedSelection);
},
insertRows: tiled.registerAction("InsertRowsAt", function(action) {
let map = tiled.activeAsset;
if(!map || !map.isTileMap)
return;
let selection = map.selectedArea.boundingRect; //a reference we can't change...
selection = Qt.rect(selection.x, selection.y, selection.width, selection.height); //...so we copy it
if(selection.height == 0) return;
let mapRegion = spreadsheetActions.getMapBounds(map);
if(map.infinite) {
//Don't bother trying to add rows outside the map on infinite maps
if(selection.y > mapRegion.y + mapRegion.height) return;
if(selection.y + selection.height < mapRegion.y) return;
} else {
//If the selection is outside the map, expand the affected region so the insertions fit
if(selection.y > mapRegion.y + mapRegion.height) { //selection below the map
selection.height += (selection.y - (mapRegion.y + mapRegion.height));
selection.y = mapRegion.y + mapRegion.height;
} else if(selection.y + selection.height < mapRegion.y) { //selection above the map
selection.height = mapRegion.y - selection.y;
}
}
map.macro("Insert Rows", function () {
if(!map.infinite)
map.resize(Qt.size(map.width, map.height + selection.height));
//map.height = map.height + selection.height;
spreadsheetActions.saveSelection(map);
map.selectedArea.set(Qt.rect(0,0,0,0));
spreadsheetActions.getMapLayers(map);
for(layer of spreadsheetActions.tileLayers) {
let layerEdit = layer.edit();
//Copy all the columns from the beginning of the selection to the end of the map, starting from the bottom
for(let y = mapRegion.bottom-1; y >= selection.top; y--) {
for(x = mapRegion.left; x < mapRegion.right; x++) {
let tile = layer.tileAt(x, y);
let flags = layer.flagsAt(x, y);
layerEdit.setTile(x, y+selection.height, tile, flags);
}
}
//Clear the selected rows:
for(let y = selection.top; y < selection.bottom; y++) {
for(x = mapRegion.left; x < mapRegion.right; x++) {
layerEdit.setTile(x, y, null);
}
}
layerEdit.apply();
}
for (layer of spreadsheetActions.objectLayers) {
//Find any objects after the insertion point and shift them
tileSize = map.tileHeight;
for (obj of layer.objects) {
if (obj.y >= selection.y * tileSize) {
obj.y += selection.height * tileSize;
}
}
}
spreadsheetActions.restoreSelection(map);
});
}),
deleteRows: tiled.registerAction("DeleteRowsAt", function(action) {
let map = tiled.activeAsset;
if(!map || !map.isTileMap)
return;
let selection = map.selectedArea.boundingRect; //a reference we can't change...
selection = Qt.rect(selection.x, selection.y, selection.width, selection.height); //...so we copy it
if(selection.height == 0) return;
let mapRegion = spreadsheetActions.getMapBounds(map);
//Don't bother trying to remove rows outside the map on infinite maps
if(selection.top >= mapRegion.bottom) return;
if(selection.bottom <= mapRegion.top) return;
//Make sure the selection doesn't go outside the map bounds, so we don't delete more rows than are actually selected:
if(selection.bottom > mapRegion.bottom) {
selection.height = mapRegion.bottom - selection.y;
}
if(selection.top < mapRegion.top) {
selection.height = selection.bottom - mapRegion.y;
selection.y = mapRegion.y;
}
if(selection.height < 1 || selection.height >= map.height) return;
map.macro("Delete Rows", function() {
spreadsheetActions.saveSelection(map);
map.selectedArea.set(Qt.rect(0,0,0,0));
spreadsheetActions.getMapLayers(map);
for(layer of spreadsheetActions.tileLayers) {
let layerEdit = layer.edit();
//Copy all the rows from the end of the selection to the end of the map, starting from the top
for(let y = selection.bottom; y < mapRegion.bottom; y++) {
for(x = mapRegion.left; x < mapRegion.right; x++) {
let tile = layer.tileAt(x, y);
let flags = layer.flagsAt(x, y);
layerEdit.setTile(x, y-selection.height, tile, flags);
}
}
//On infinite maps, clear the rows beyond the map's new edge
if(map.infinite) {
for(let y = mapRegion.bottom - selection.height; y < mapRegion.bottom; y++) {
for(x = mapRegion.left; x < mapRegion.right; x++) {
layerEdit.setTile(x, y, null);
}
}
}
layerEdit.apply();
}
for (layer of spreadsheetActions.objectLayers) {
//Find any objects after the insertion point and shift them
tileSize = map.tileHeight;
for (obj of layer.objects) {
if (obj.y >= selection.y * tileSize) {
obj.y -= selection.height * tileSize;
}
}
}
spreadsheetActions.restoreSelection(map);
if(!map.infinite)
map.resize(Qt.size(map.width, map.height - selection.height));
});
}),
insertCols: tiled.registerAction("InsertColsAt", function(action) {
let map = tiled.activeAsset;
if(!map || !map.isTileMap)
return;
let selection = map.selectedArea.boundingRect; //a reference we can't change...
selection = Qt.rect(selection.x, selection.y, selection.width, selection.height); //...so we copy it
if(selection.width == 0) return;
let mapRegion = spreadsheetActions.getMapBounds(map);
if(map.infinite) {
//Don't bother trying to add columns outside the map on infinite maps
if(selection.x > mapRegion.x + mapRegion.width) return;
if(selection.x + selection.width < mapRegion.x) return;
} else {
//If the selection is outside the map, expand the affected region so the insertions fit
if(selection.x > mapRegion.x + mapRegion.width) { //selection right of the map
selection.width += (selection.x - (mapRegion.x + mapRegion.width));
selection.x = mapRegion.x + mapRegion.width;
} else if(selection.x + selection.width < mapRegion.x) { //selection left of the map
selection.width = mapRegion.x - selection.x;
}
}
map.macro("Insert Columns", function() {
if(!map.infinite)
map.resize(Qt.size(map.width + selection.width, map.height));
//map.height = map.height + selection.height;
spreadsheetActions.saveSelection(map);
map.selectedArea.set(Qt.rect(0,0,0,0));
spreadsheetActions.getMapLayers(map);
for(layer of spreadsheetActions.tileLayers) {
let layerEdit = layer.edit();
//Copy all the columns from the beginning of the selection to the end of the map, starting from the right
for(let x = mapRegion.right-1; x >= selection.left; x--) {
for(y = mapRegion.top; y < mapRegion.bottom; y++) {
let tile = layer.tileAt(x, y);
let flags = layer.flagsAt(x, y);
layerEdit.setTile(x+selection.width, y, tile, flags);
}
}
//Clear the selected columns:
for(let x = selection.left; x < selection.right; x++) {
for(y = mapRegion.top; y < mapRegion.bottom; y++) {
layerEdit.setTile(x, y, null);
}
}
layerEdit.apply();
}
for (layer of spreadsheetActions.objectLayers) {
//Find any objects after the insertion point and shift them
tileSize = map.tileWidth;
for (obj of layer.objects) {
if (obj.x >= selection.x * tileSize) {
obj.x += selection.width * tileSize;
}
}
}
spreadsheetActions.restoreSelection(map);
});
}),
deleteCols: tiled.registerAction("DeleteColsAt", function(action) {
let map = tiled.activeAsset;
if(!map || !map.isTileMap)
return;
let selection = map.selectedArea.boundingRect; //a reference we can't change...
selection = Qt.rect(selection.x, selection.y, selection.width, selection.height); //...so we copy it
if(selection.width == 0) return;
let mapRegion = spreadsheetActions.getMapBounds(map);
//Don't bother trying to remove columns outside the map
if(selection.x > mapRegion.x + mapRegion.width) return;
if(selection.x + selection.width < mapRegion.x) return;
//Make sure the selection doesn't go outside the map bounds, so we don't delete more columns than are actually selected:
if(selection.right > mapRegion.right) {
selection.width = mapRegion.right - selection.x;
}
if(selection.left < mapRegion.left) {
selection.height = selection.right - mapRegion.x;
selection.x = mapRegion.x;
}
if(selection.width < 1 || selection.width >= map.width) return;
map.macro("Delete Columns", function() {
spreadsheetActions.saveSelection(map);
map.selectedArea.set(Qt.rect(0,0,0,0));
spreadsheetActions.getMapLayers(map);
for(layer of spreadsheetActions.tileLayers) {
let layerEdit = layer.edit();
//Copy all the rows from the end of the selection to the end of the map, starting from the left
for(let x = selection.right; x < mapRegion.right; x++) {
for(y = mapRegion.top; y < mapRegion.bottom; y++) {
let tile = layer.tileAt(x, y);
let flags = layer.flagsAt(x, y);
layerEdit.setTile(x-selection.width, y, tile, flags);
}
}
//On infinite maps, clear the rows beyond the map's new edge
if(map.infinite) {
for(let x = mapRegion.right - selection.width; x < mapRegion.right; x++) {
for(y = mapRegion.top; y < mapRegion.bottom; y++) {
layerEdit.setTile(x, y, null);
}
}
}
layerEdit.apply();
}
for (layer of spreadsheetActions.objectLayers) {
//Find any objects after the insertion point and shift them
tileSize = map.tileWidth;
for (obj of layer.objects) {
if (obj.x >= selection.x * tileSize) {
obj.x -= selection.width * tileSize;
}
}
}
spreadsheetActions.restoreSelection(map);
if(!map.infinite)
map.resize(Qt.size(map.width - selection.width, map.height));
});
})
};
spreadsheetActions.insertRows.text = "Insert Rows at Selection";
spreadsheetActions.insertCols.text = "Insert Columns at Selection";
spreadsheetActions.deleteRows.text = "Delete Selected Rows";
spreadsheetActions.deleteCols.text = "Delete Selected Columns";
tiled.extendMenu("Edit", [
{ action: "InsertRowsAt", before: "Preferences" },
{ action: "InsertColsAt" },
{ action: "DeleteRowsAt" },
{ action: "DeleteColsAt" },
{separator: true}
]);