Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions src/tiled/abstractworldtool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,16 +563,6 @@ void AbstractWorldTool::setSelectionScreenRect(const QRect &rect)
}
}

// Move the camera back by offset, to keep the active map steady after it shifts
void AbstractWorldTool::recenterView(const QPoint &offset)
{
if (offset.isNull())
return;

MapView *view = DocumentManager::instance()->viewForDocument(mapDocument());
view->forceCenterOn(view->viewCenter() - offset);
}

} // namespace Tiled

#include "moc_abstractworldtool.cpp"
2 changes: 0 additions & 2 deletions src/tiled/abstractworldtool.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,6 @@ class AbstractWorldTool : public AbstractTool
void setSelectionScreenRect(const QRect &rect);
int resizeHandleNear(const QPointF &scenePos, MapDocument *&mapDocument) const;

void recenterView(const QPoint &offset);

bool mapCanBeMoved(MapDocument *mapDocument) const;
bool mapCanBeResized(MapDocument *mapDocument) const;
QRect mapRect(MapDocument *mapDocument) const;
Expand Down
2 changes: 1 addition & 1 deletion src/tiled/editablemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ QPointF EditableMap::tileToPixel(qreal x, qreal y) const
void EditableMap::setSize(int width, int height)
{
if (auto doc = mapDocument()) {
push(new ResizeMap(doc, QSize(width, height)));
push(new ResizeMap(doc, QSize(width, height), QPoint()));
} else if (!checkReadOnly()) {
map()->setWidth(width);
map()->setHeight(height);
Expand Down
14 changes: 10 additions & 4 deletions src/tiled/mapdocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,19 @@ void MapDocument::resizeMap(QSize size, QPoint offset, bool removeObjects)
new TransformMapObjects(this, objectsToMove, states, command);
}

new ResizeMap(this, size, command);
new ResizeMap(this, size, offset, command);
new ChangeSelectedArea(this, movedSelection, command);

// A world positions a map by its bounding rect, so move by how much that
// rect shifts. The pixel offset moved isometric maps twice as far as their
// rect actually shifts
const QRect oldBounds = renderer()->boundingRect(QRect(QPoint(), map()->size()));
const QRect newBounds = renderer()->boundingRect(QRect(-offset, size));
const QPoint boundsOffset = newBounds.topLeft() - oldBounds.topLeft();

// Adjust world position if this map is part of any loaded worlds
if (!pixelOffset.isNull()) {
if (!boundsOffset.isNull()) {
const QString &mapName = fileName();
const QPoint offsetPixels = pixelOffset.toPoint();

for (const auto &worldDocument : WorldManager::instance().worlds()) {
auto world = worldDocument->world();
Expand All @@ -466,7 +472,7 @@ void MapDocument::resizeMap(QSize size, QPoint offset, bool removeObjects)
continue; // also skips maps matched via pattern

const QPoint prevPos = world->mapRect(mapName).topLeft();
const QPoint newPos = prevPos - offsetPixels;
const QPoint newPos = prevPos + boundsOffset;
new SetMapPosInLoadedWorld(worldDocument->fileName(), mapName, prevPos, newPos, command);
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/tiled/mapdocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,10 @@ class TILED_EDITOR_EXPORT MapDocument final : public Document
void mapObjectPicked(MapObject *object);

/**
* Emitted when the map size changes.
* Emitted when the map size changes. The screen offset tells by how much
* the contents were shifted during the resize, in screen coordinates.
*/
void mapResized();
void mapResized(QPointF screenOffset);

void layerAdded(Layer *layer);
void layerAboutToBeRemoved(GroupLayer *parentLayer, int index);
Expand Down
47 changes: 47 additions & 0 deletions src/tiled/mapscene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "map.h"
#include "mapobject.h"
#include "maprenderer.h"
#include "mapview.h"
#include "objectgroup.h"
#include "objecttemplate.h"
#include "preferences.h"
Expand Down Expand Up @@ -117,6 +118,8 @@ void MapScene::setMapDocument(MapDocument *mapDocument)
this, [this] { update(); });
connect(mMapDocument, &MapDocument::tilesetReplaced,
this, &MapScene::tilesetReplaced);
connect(mMapDocument, &MapDocument::mapResized,
this, &MapScene::mapResized);
}

refreshScene();
Expand Down Expand Up @@ -285,6 +288,36 @@ QPointF MapScene::parallaxOffset(const Layer &layer) const
(1.0 - parallaxFactor.y()) * viewCenter.y());
}

/**
* When a resize shifted the map contents, move the view along by the same
* amount so the contents appear to stay in place.
*/
void MapScene::mapResized(QPointF screenOffset)
{
if (screenOffset.isNull())
return;

// when the map has its own entry in a world, the resize also moves its
// world position and refreshScene handles the view, so nothing to do here
const QString &fileName = mMapDocument->fileName();
if (auto worldDocument = WorldManager::instance().worldForMap(fileName))
if (worldDocument->world()->mapIndex(fileName) >= 0)
return;

translateViews(screenOffset);
}

/**
* Moves all views on this scene along by the given delta.
*/
void MapScene::translateViews(const QPointF &delta)
{
const auto sceneViews = views();
for (QGraphicsView *view : sceneViews)
if (auto mapView = qobject_cast<MapView*>(view))
mapView->forceCenterOn(mapView->viewCenter() + delta);
}

/**
* Refreshes the map scene.
*/
Expand All @@ -293,6 +326,7 @@ void MapScene::refreshScene()
QHash<MapDocument*, MapItem*> mapItems;

if (!mMapDocument) {
mLastWorldPositionMapFile.clear();
mMapItems.swap(mapItems);
qDeleteAll(mapItems);
updateSceneRect();
Expand All @@ -307,6 +341,17 @@ void MapScene::refreshScene()
const QPoint currentMapPosition = world->mapRect(currentMapFile).topLeft();
auto const contextMaps = world->contextMaps(currentMapFile);

// If the current map moved in its world (by a move or an undo),
// shift the view along so the map visibly moves instead of the
// world around it.
if (mLastWorldPositionMapFile == currentMapFile
&& currentMapPosition != mLastWorldPosition) {
const QPoint delta = currentMapPosition - mLastWorldPosition;
translateViews(-delta);
}
mLastWorldPositionMapFile = currentMapFile;
mLastWorldPosition = currentMapPosition;

for (const WorldMapEntry &mapEntry : contextMaps) {
MapDocumentPtr mapDocument;

Expand All @@ -329,6 +374,8 @@ void MapScene::refreshScene()
}
}
} else {
mLastWorldPositionMapFile.clear();

auto mapItem = takeOrCreateMapItem(mMapDocument->sharedFromThis(), MapItem::Editable);
mapItems.insert(mMapDocument, mapItem);
}
Expand Down
4 changes: 4 additions & 0 deletions src/tiled/mapscene.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ class MapScene : public QGraphicsScene
void refreshScene();

void changeEvent(const ChangeEvent &change);
void mapResized(QPointF screenOffset);
void translateViews(const QPointF &delta);
void repaintTileset(Tileset *tileset);

void tilesetReplaced(int index, Tileset *tileset, Tileset *oldTileset);
Expand All @@ -133,6 +135,8 @@ class MapScene : public QGraphicsScene

MapDocument *mMapDocument = nullptr;
QHash<MapDocument*, MapItem*> mMapItems;
QString mLastWorldPositionMapFile;
QPoint mLastWorldPosition;
AbstractTool *mSelectedTool = nullptr;
DebugDrawItem *mDebugDrawItem = nullptr;
bool mUnderMouse = false;
Expand Down
23 changes: 21 additions & 2 deletions src/tiled/resizemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@

#include "map.h"
#include "mapdocument.h"
#include "maprenderer.h"

#include <QCoreApplication>

namespace Tiled {

ResizeMap::ResizeMap(MapDocument *mapDocument,
QSize size,
QPoint offset,
QUndoCommand *parent)
: QUndoCommand(QCoreApplication::translate("Undo Commands",
"Resize Map"),
parent)
, mMapDocument(mapDocument)
, mSize(size)
, mOffset(offset)
{
}

Expand All @@ -51,12 +54,28 @@ void ResizeMap::redo()
void ResizeMap::swapSize()
{
Map *map = mMapDocument->map();
QSize oldSize(map->width(), map->height());
const MapRenderer *renderer = mMapDocument->renderer();

const QSize oldSize = map->size();

// Measure the bounding rect rather than the contents, since on hexagonal
// and staggered maps the contents jump half a tile whenever the stagger
// parity changes. Taken before the resize, because the isometric origin
// follows the map height.
const QRect oldBounds = renderer->boundingRect(QRect(QPoint(), oldSize));
const QRect newBounds = renderer->boundingRect(QRect(-mOffset, mSize));
const QPoint boundsOffset = newBounds.topLeft() - oldBounds.topLeft();

map->setWidth(mSize.width());
map->setHeight(mSize.height());
mSize = oldSize;

emit mMapDocument->mapResized();
// The view goes the other way, so the map stays where it was on screen.
emit mMapDocument->mapResized(-boundsOffset);

// Shift the other way when this command is applied again, so that undo
// and redo each restore the previous state.
mOffset = -mOffset;
}

} // namespace Tiled
3 changes: 3 additions & 0 deletions src/tiled/resizemap.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#pragma once

#include <QPoint>
#include <QSize>
#include <QUndoCommand>

Expand All @@ -36,6 +37,7 @@ class ResizeMap : public QUndoCommand
public:
ResizeMap(MapDocument *mapDocument,
QSize size,
QPoint offset,
QUndoCommand *parent = nullptr);

void undo() override;
Expand All @@ -46,6 +48,7 @@ class ResizeMap : public QUndoCommand

MapDocument *mMapDocument;
QSize mSize;
QPoint mOffset;
};

} // namespace Tiled
79 changes: 34 additions & 45 deletions src/tiled/worldmovemaptool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,13 @@ void WorldMoveMapTool::moveMap(MapDocument *document, QPoint moveBy)

auto undoStack = worldDocument->undoStack();
undoStack->push(new SetMapRectCommand(worldDocument, document->fileName(), rect));

if (document == mapDocument()) {
// undo camera movement, by the actual snapped offset
recenterView(rect.topLeft() - prevRect.topLeft());
}
}

void WorldMoveMapTool::updateResizingMap(const QPointF &pos,
Qt::KeyboardModifiers modifiers)
{
const Map *map = mResizingMap->map();
const int tileWidth = map->tileWidth();
const int tileHeight = map->tileHeight();
const MapRenderer *renderer = mResizingMap->renderer();
const QSize step = snapSize(mResizingMap);
const HandleEdges edges = handleEdges[mResizeHandle];

Expand All @@ -165,37 +159,48 @@ void WorldMoveMapTool::updateResizingMap(const QPointF &pos,
const QPoint delta = (pos - mDragStartScenePos).toPoint();
const bool snapToGrid = !(modifiers & Qt::ControlModifier);

const auto snap = [&](int value, int gridStep) {
return (snapToGrid && gridStep > 0) ? qRound(qreal(value) / gridStep) * gridStep
: value;
// snap the drag rather than the resulting edge, since the bounds of a
// staggered or hexagonal map don't line up with the grid and the edge
// would otherwise jump as soon as a handle is grabbed
const auto snap = [&](int amount, int gridStep) {
return (snapToGrid && gridStep > 0) ? qRound(qreal(amount) / gridStep) * gridStep
: amount;
};

if (edges.left)
left = snap(left + delta.x(), step.width());
left += snap(delta.x(), step.width());
if (edges.right)
right = snap(right + delta.x(), step.width());
right += snap(delta.x(), step.width());
if (edges.top)
top = snap(top + delta.y(), step.height());
top += snap(delta.y(), step.height());
if (edges.bottom)
bottom = snap(bottom + delta.y(), step.height());

// a map is always a whole number of tiles, so round to the nearest tile
const int newWidth = qMax(1, qRound(qreal(right - left) / tileWidth));
const int newHeight = qMax(1, qRound(qreal(bottom - top) / tileHeight));
bottom += snap(delta.y(), step.height());

// ask the renderer what one more column or row adds on screen, since that
// is only the tile size for orthogonal maps
const QSize mapSize = map->size();
const QRect mapRect = renderer->boundingRect(QRect(QPoint(), mapSize));
const QRect oneMoreColumn = renderer->boundingRect(QRect(QPoint(), mapSize + QSize(1, 0)));
const QRect oneMoreRow = renderer->boundingRect(QRect(QPoint(), mapSize + QSize(0, 1)));
const int columnPixels = qMax(1, oneMoreColumn.width() - mapRect.width());
const int rowPixels = qMax(1, oneMoreRow.height() - mapRect.height());

// count from the size we started at, so grabbing a handle without dragging
// leaves the map as it is
const int widthDragged = right - left - mResizeStartWorldRect.width();
const int heightDragged = bottom - top - mResizeStartWorldRect.height();
const int newWidth = qMax(1, mapSize.width() + qRound(qreal(widthDragged) / columnPixels));
const int newHeight = qMax(1, mapSize.height() + qRound(qreal(heightDragged) / rowPixels));

// the content only shifts when the left or top edge is the one being moved
mResizeOffset = QPoint(edges.left ? newWidth - map->width() : 0,
edges.top ? newHeight - map->height() : 0);
mResizeOffset = QPoint(edges.left ? newWidth - mapSize.width() : 0,
edges.top ? newHeight - mapSize.height() : 0);
mResizeNewSize = QSize(newWidth, newHeight);

// preview the result, matching what resizeMap() will produce, using the
// renderer so the size is correct for isometric and other orientations
const MapRenderer *renderer = mResizingMap->renderer();
const QPointF pixelOffset = renderer->tileToPixelCoords(QPointF())
- renderer->tileToPixelCoords(-mResizeOffset);
const QPoint topLeft = mResizeStartWorldRect.topLeft() - pixelOffset.toPoint();
const QSize previewSize = renderer->boundingRect(QRect(QPoint(), mResizeNewSize)).size();
setSelectionScreenRect(QRect(topLeft, previewSize).translated(mResizeSceneOffset));
// preview the result, positioned the way resizeMap() will position it
const QRect newBounds = renderer->boundingRect(QRect(-mResizeOffset, mResizeNewSize));
const QPoint topLeft = mResizeStartWorldRect.topLeft() + newBounds.topLeft() - mapRect.topLeft();
setSelectionScreenRect(QRect(topLeft, newBounds.size()).translated(mResizeSceneOffset));

setStatusInfo(tr("Resize map to %1 x %2").arg(newWidth).arg(newHeight));
}
Expand Down Expand Up @@ -326,20 +331,9 @@ void WorldMoveMapTool::finishResizing()
auto resizedMap = std::exchange(mResizingMap, nullptr);
mResizeHandle = -1;

if (mResizeNewSize != resizedMap->map()->size() || !mResizeOffset.isNull()) {
if (mResizeNewSize != resizedMap->map()->size() || !mResizeOffset.isNull())
resizedMap->resizeMap(mResizeNewSize, mResizeOffset, false);

// keep the view steady by compensating for the content shift, which
// matches the pixelOffset applied by MapDocument::resizeMap (a map
// in a world has its position adjusted by exactly this amount)
if (resizedMap == mapDocument()) {
const MapRenderer *renderer = resizedMap->renderer();
const QPointF pixelOffset = renderer->tileToPixelCoords(QPointF())
- renderer->tileToPixelCoords(-mResizeOffset);
recenterView(-pixelOffset.toPoint());
}
}

updateSelectionRectangle();
refreshCursor();
setStatusInfo(QString());
Expand All @@ -363,11 +357,6 @@ void WorldMoveMapTool::finishMoving()

auto undoStack = worldDocument->undoStack();
undoStack->push(new SetMapRectCommand(worldDocument, draggedMap->fileName(), rect));

if (draggedMap == mapDocument()) {
// undo camera movement
view->forceCenterOn(view->viewCenter() - mDragOffset);
}
}
} else {
// switch to the document
Expand Down
Loading