Skip to content

Commit e4fcb79

Browse files
authored
Adjust world map position when resizing a map with offset (#4285)
When an offset is applied while resizing a map, this offset will now affect the maps position in any loaded worlds the map is in. This affects both manual resizing as well as the "crop to selection" and "autocrop" actions. Implemented using a SetMapPosInLoadedWorld command which has a weak reference (file name) to a loaded world, to avoid issues when a world is later unloaded. It in turn uses SetMapRectCommand to apply the change to the world, which was adjusted to set itself as obsolete when applicable in order to disappear when the resize operation is undone. Closes #4270
1 parent 8473dc8 commit e4fcb79

File tree

6 files changed

+114
-14
lines changed

6 files changed

+114
-14
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* Allow canceling Select Same Tile, Magic Wand and Bucket Fill operations with right-click and Escape
1515
* Allow dragging over multiple tiles with Select Same Tile, Magic Wand and Bucket Fill tools (#4276)
1616
* Don't switch to Edit Polygons tool on double-click with Alt pressed
17+
* Adjust world map position when resizing a map with offset (#4270)
1718
* Added export plugin for Remixed Dungeon (by Mikhael Danilov, #4158)
1819
* Added "World > World Properties" menu action (with dogboydog, #4190)
1920
* Added Delete shortcut to Remove Tiles action by default and avoid ambiguity (#4201)

src/tiled/changeworld.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,51 @@ void SetMapRectCommand::setMapRect(const QRect &rect)
115115
emit mWorldDocument->worldChanged();
116116
}
117117

118+
bool SetMapRectCommand::mergeWith(const QUndoCommand *other)
119+
{
120+
auto o = static_cast<const SetMapRectCommand *>(other);
121+
if (mWorldDocument != o->mWorldDocument || mMapName != o->mMapName)
122+
return false;
123+
124+
mRect = o->mRect;
125+
setObsolete(childCount() == 0 && mRect == mPreviousRect);
126+
return true;
127+
}
128+
129+
130+
SetMapPosInLoadedWorld::SetMapPosInLoadedWorld(const QString &worldFileName,
131+
const QString &mapName,
132+
const QPoint &from,
133+
const QPoint &to,
134+
QUndoCommand *parent)
135+
: QUndoCommand(parent)
136+
, mWorldFileName(worldFileName)
137+
, mMapName(mapName)
138+
, mFrom(from)
139+
, mTo(to)
140+
{}
141+
142+
void SetMapPosInLoadedWorld::setRect(QPoint pos)
143+
{
144+
auto worldDoc = WorldManager::instance().findWorld(mWorldFileName);
145+
if (!worldDoc)
146+
return;
147+
148+
auto world = worldDoc->world();
149+
const int idx = world->mapIndex(mMapName);
150+
if (idx < 0)
151+
return;
152+
153+
// Only apply when the current position matches the expected state, to
154+
// avoid clobbering manual world moves
155+
QRect rect = world->mapRect(mMapName);
156+
const QPoint expectedPos = (pos == mTo) ? mFrom : mTo;
157+
if (rect.topLeft() != expectedPos)
158+
return;
159+
160+
rect.moveTo(pos);
161+
162+
worldDoc->undoStack()->push(new SetMapRectCommand(worldDoc.data(), mMapName, rect));
163+
}
164+
118165
} // namespace Tiled

src/tiled/changeworld.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#pragma once
2323

24+
#include "undocommands.h"
25+
2426
#include <QRect>
2527
#include <QUndoCommand>
2628

@@ -75,6 +77,9 @@ class SetMapRectCommand : public QUndoCommand
7577
void undo() override { setMapRect(mPreviousRect); }
7678
void redo() override { setMapRect(mRect); }
7779

80+
int id() const override { return Cmd_SetMapRect; }
81+
bool mergeWith(const QUndoCommand *other) override;
82+
7883
private:
7984
void setMapRect(const QRect &rect);
8085

@@ -84,4 +89,32 @@ class SetMapRectCommand : public QUndoCommand
8489
QRect mPreviousRect;
8590
};
8691

92+
/**
93+
* Undo command that safely updates a world's map rect if that world is loaded.
94+
*
95+
* This undo command is used as part of resizing a map. It modifies the world
96+
* using SetMapRectCommand, which obsoletes itself when this command changes the
97+
* value back on undo.
98+
*/
99+
class SetMapPosInLoadedWorld : public QUndoCommand
100+
{
101+
public:
102+
SetMapPosInLoadedWorld(const QString &worldFileName,
103+
const QString &mapName,
104+
const QPoint &from,
105+
const QPoint &to,
106+
QUndoCommand *parent = nullptr);
107+
108+
void undo() override { setRect(mFrom); }
109+
void redo() override { setRect(mTo); }
110+
111+
private:
112+
void setRect(QPoint pos);
113+
114+
QString mWorldFileName;
115+
QString mMapName;
116+
QPoint mFrom;
117+
QPoint mTo;
118+
};
119+
87120
} // namespace Tiled

src/tiled/mapdocument.cpp

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "changemapobjectsorder.h"
3232
#include "changeproperties.h"
3333
#include "changeselectedarea.h"
34+
#include "changeworld.h"
3435
#include "containerhelpers.h"
3536
#include "editablemap.h"
3637
#include "editor.h"
@@ -60,6 +61,9 @@
6061
#include "tilelayer.h"
6162
#include "tilesetdocument.h"
6263
#include "transformmapobjects.h"
64+
#include "world.h"
65+
#include "worlddocument.h"
66+
#include "worldmanager.h"
6367

6468
#include <QFileInfo>
6569
#include <QRect>
@@ -395,7 +399,7 @@ void MapDocument::resizeMap(QSize size, QPoint offset, bool removeObjects)
395399
const QPointF pixelOffset = origin - newOrigin;
396400

397401
// Resize the map and each layer
398-
QUndoCommand *command = new QUndoCommand(tr("Resize Map"));
402+
auto command = new QUndoCommand(tr("Resize Map"));
399403

400404
QList<MapObject *> objectsToRemove;
401405
QList<MapObject *> objectsToMove;
@@ -404,12 +408,12 @@ void MapDocument::resizeMap(QSize size, QPoint offset, bool removeObjects)
404408
while (Layer *layer = iterator.next()) {
405409
switch (layer->layerType()) {
406410
case Layer::TileLayerType: {
407-
TileLayer *tileLayer = static_cast<TileLayer*>(layer);
411+
auto tileLayer = static_cast<TileLayer*>(layer);
408412
new ResizeTileLayer(this, tileLayer, size, offset, command);
409413
break;
410414
}
411415
case Layer::ObjectGroupType: {
412-
ObjectGroup *objectGroup = static_cast<ObjectGroup*>(layer);
416+
auto objectGroup = static_cast<ObjectGroup*>(layer);
413417

414418
for (MapObject *o : objectGroup->objects()) {
415419
// Remove objects that will fall outside of the map
@@ -450,6 +454,23 @@ void MapDocument::resizeMap(QSize size, QPoint offset, bool removeObjects)
450454
new ResizeMap(this, size, command);
451455
new ChangeSelectedArea(this, movedSelection, command);
452456

457+
// Adjust world position if this map is part of any loaded worlds
458+
if (!pixelOffset.isNull()) {
459+
const QString &mapName = fileName();
460+
const QPoint offsetPixels = pixelOffset.toPoint();
461+
462+
for (const auto &worldDocument : WorldManager::instance().worlds()) {
463+
auto world = worldDocument->world();
464+
const int mapIdx = world->mapIndex(mapName);
465+
if (mapIdx < 0)
466+
continue; // also skips maps matched via pattern
467+
468+
const QPoint prevPos = world->mapRect(mapName).topLeft();
469+
const QPoint newPos = prevPos - offsetPixels;
470+
new SetMapPosInLoadedWorld(worldDocument->fileName(), mapName, prevPos, newPos, command);
471+
}
472+
}
473+
453474
undoStack()->push(command);
454475

455476
// TODO: Handle layers that don't match the map size correctly
@@ -460,8 +481,7 @@ void MapDocument::autocropMap()
460481
if (!mCurrentLayer || !mCurrentLayer->isTileLayer())
461482
return;
462483

463-
TileLayer *tileLayer = static_cast<TileLayer*>(mCurrentLayer);
464-
484+
auto tileLayer = static_cast<TileLayer*>(mCurrentLayer);
465485
const QRect bounds = tileLayer->region().boundingRect();
466486
if (bounds.isNull())
467487
return;
@@ -1072,12 +1092,12 @@ void MapDocument::paintTileLayers(const Map &map, bool mergeable,
10721092
if (!mMap->infinite() && !target->rect().intersects(source->bounds()))
10731093
continue;
10741094

1075-
PaintTileLayer *paintCommand = new PaintTileLayer(this,
1076-
target,
1077-
source->x(),
1078-
source->y(),
1079-
source,
1080-
paintRegion);
1095+
auto paintCommand = new PaintTileLayer(this,
1096+
target,
1097+
source->x(),
1098+
source->y(),
1099+
source,
1100+
paintRegion);
10811101

10821102
if (missingTilesets && !missingTilesets->isEmpty()) {
10831103
for (const SharedTileset &tileset : std::as_const(*missingTilesets)) {
@@ -1615,7 +1635,7 @@ void MapDocument::checkIssues()
16151635

16161636
LayerIterator it(map());
16171637
for (Layer *layer : map()->objectGroups()) {
1618-
ObjectGroup *objectGroup = static_cast<ObjectGroup*>(layer->asObjectGroup());
1638+
auto objectGroup = static_cast<ObjectGroup*>(layer->asObjectGroup());
16191639
for (MapObject *mapObject : *objectGroup) {
16201640
if (const ObjectTemplate *objectTemplate = mapObject->objectTemplate())
16211641
if (!objectTemplate->object())

src/tiled/undocommands.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ enum UndoCommands {
6868
Cmd_ChangeWangSetName,
6969
Cmd_EraseTiles,
7070
Cmd_PaintTileLayer,
71+
Cmd_SetMapRect,
7172
Cmd_SetProperty,
7273
};
7374

src/tiled/worlddocument.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
#include "document.h"
2525
#include "editableasset.h"
2626

27-
class WorldManager;
28-
2927
namespace Tiled {
3028

3129
class World;

0 commit comments

Comments
 (0)