diff --git a/src/tiled/changeworld.cpp b/src/tiled/changeworld.cpp index 2fdff477a3..b14be6564a 100644 --- a/src/tiled/changeworld.cpp +++ b/src/tiled/changeworld.cpp @@ -159,7 +159,15 @@ void SetMapPosInLoadedWorld::setRect(QPoint pos) rect.moveTo(pos); - worldDoc->undoStack()->push(new SetMapRectCommand(worldDoc.data(), mMapName, rect)); + // Avoid merging this automatic world update with a user's manual world move. + class NonMergingSetMapRectCommand final : public SetMapRectCommand + { + public: + using SetMapRectCommand::SetMapRectCommand; + int id() const override { return -1; } + }; + + worldDoc->undoStack()->push(new NonMergingSetMapRectCommand(worldDoc.data(), mMapName, rect)); } } // namespace Tiled diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index 7b869b9e4f..efee9d4ca4 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -587,8 +587,8 @@ DynamicLibrary { "worlddocument.h", "worldmanager.cpp", "worldmanager.h", - "worldmovemaptool.cpp", - "worldmovemaptool.h", + "worldmaptool.cpp", + "worldmaptool.h", "worldpropertiesdialog.cpp", "worldpropertiesdialog.h", "worldpropertiesdialog.ui", diff --git a/src/tiled/mapeditor.cpp b/src/tiled/mapeditor.cpp index 9318cde09a..3c2a838b35 100644 --- a/src/tiled/mapeditor.cpp +++ b/src/tiled/mapeditor.cpp @@ -76,7 +76,7 @@ #include "wangdock.h" #include "zoomable.h" #include "worldmanager.h" -#include "worldmovemaptool.h" +#include "worldmaptool.h" #include #include @@ -205,7 +205,7 @@ MapEditor::MapEditor(QObject *parent) mToolsToolBar->addAction(mToolManager->registerTool(templatesTool)); mToolsToolBar->addAction(mToolManager->registerTool(textObjectsTool)); mToolsToolBar->addSeparator(); - mToolsToolBar->addAction(mToolManager->registerTool(new WorldMoveMapTool(this))); + mToolsToolBar->addAction(mToolManager->registerTool(new WorldMapTool(this))); mToolsToolBar->addAction(mToolManager->registerTool(new LayerOffsetTool(this))); mToolsToolBar->addSeparator(); // todo: hide when there are no tool extensions diff --git a/src/tiled/worldmaptool.cpp b/src/tiled/worldmaptool.cpp new file mode 100644 index 0000000000..87da50f861 --- /dev/null +++ b/src/tiled/worldmaptool.cpp @@ -0,0 +1,613 @@ +/* + * worldmaptool.cpp + * Copyright 2019, Nils Kuebler + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "worldmaptool.h" + +#include "changeworld.h" +#include "documentmanager.h" +#include "layer.h" +#include "map.h" +#include "mapdocument.h" +#include "maprenderer.h" +#include "mapscene.h" +#include "mapview.h" +#include "preferences.h" +#include "selectionrectangle.h" +#include "toolmanager.h" +#include "utils.h" +#include "world.h" +#include "worlddocument.h" +#include "zoomable.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Tiled { + +static const qreal HandleSize = 8.0; + +// Prevents consecutive move commands from merging in the undo stack. +class NonMergingSetMapRectCommand final : public SetMapRectCommand +{ +public: + using SetMapRectCommand::SetMapRectCommand; + int id() const override { return -1; } +}; + +class ResizeHandleItem : public QGraphicsItem +{ +public: + ResizeHandleItem(ResizeAnchor anchor, QGraphicsItem *parent = nullptr) + : QGraphicsItem(parent) + , mAnchor(anchor) + { + setFlags(QGraphicsItem::ItemIgnoresTransformations | + QGraphicsItem::ItemIgnoresParentOpacity); + setAcceptedMouseButtons(Qt::MouseButtons()); + setAcceptHoverEvents(false); + setZValue(10001); + } + + ResizeAnchor anchor() const { return mAnchor; } + + void setUnderMouse(bool underMouse) + { + if (mUnderMouse != underMouse) { + mUnderMouse = underMouse; + update(); + } + } + + QRectF boundingRect() const override + { + const qreal s = Utils::defaultDpiScale(); + const qreal half = (HandleSize * s) / 2.0 + 1.0; + return QRectF(-half, -half, half * 2, half * 2); + } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override + { + const qreal s = Utils::defaultDpiScale(); + const qreal half = (HandleSize * s) / 2.0; + const QRectF rect(-half, -half, HandleSize * s, HandleSize * s); + + painter->setRenderHint(QPainter::Antialiasing, false); + + if (mUnderMouse) { + painter->setPen(QPen(Qt::black, 1.5 * s)); + painter->setBrush(Qt::white); + } else { + painter->setPen(QPen(Qt::white, 1.5 * s)); + painter->setBrush(QColor(200, 40, 40)); + } + + painter->drawRect(rect); + } + +private: + ResizeAnchor mAnchor; + bool mUnderMouse = false; +}; + +WorldMapTool::WorldMapTool(QObject *parent) + : AbstractWorldTool("WorldMapTool", + tr("World Tool"), + QIcon(QLatin1String(":images/22/world-move-tool.png")), + QKeySequence(Qt::Key_N), + parent) +{ + for (int i = 0; i < ResizeAnchorCount; ++i) { + mResizeHandles[i] = new ResizeHandleItem(static_cast(i)); + mResizeHandles[i]->setVisible(false); + } + mResizePreviewRectangle = std::make_unique(); + mResizePreviewRectangle->setVisible(false); +} + +WorldMapTool::~WorldMapTool() +{ + for (auto handle : mResizeHandles) + delete handle; +} + +QUndoStack *WorldMapTool::undoStack() +{ + if (mResizing && mResizingMap) + return mResizingMap->undoStack(); + + return AbstractWorldTool::undoStack(); +} + +void WorldMapTool::activate(MapScene *scene) +{ + AbstractWorldTool::activate(scene); + + scene->addItem(mResizePreviewRectangle.get()); + for (auto handle : mResizeHandles) + scene->addItem(handle); +} + +void WorldMapTool::deactivate(MapScene *scene) +{ + abortResizing(); + abortMoving(); + + scene->removeItem(mResizePreviewRectangle.get()); + for (auto handle : mResizeHandles) + scene->removeItem(handle); + + AbstractWorldTool::deactivate(scene); +} + +void WorldMapTool::keyPressed(QKeyEvent *event) +{ + QPointF moveBy; + + switch (event->key()) { + case Qt::Key_Up: moveBy = QPointF(0, -1); break; + case Qt::Key_Down: moveBy = QPointF(0, 1); break; + case Qt::Key_Left: moveBy = QPointF(-1, 0); break; + case Qt::Key_Right: moveBy = QPointF(1, 0); break; + case Qt::Key_Escape: + abortResizing(); + abortMoving(); + return; + default: + AbstractWorldTool::keyPressed(event); + return; + } + + const Qt::KeyboardModifiers modifiers = event->modifiers(); + if (moveBy.isNull() || (modifiers & Qt::ControlModifier)) { + event->ignore(); + return; + } + MapDocument *document = mapDocument(); + if (!document || !mapCanBeMoved(document) || mDraggingMap) + return; + + const bool moveFast = modifiers & Qt::ShiftModifier; + const auto snapMode = Preferences::instance()->snapMode(); + + if (moveFast) { + moveBy.rx() *= document->map()->tileWidth(); + moveBy.ry() *= document->map()->tileHeight(); + if (snapMode == SnapMode::FineGrid) + moveBy /= Preferences::instance()->gridFine(); + } + + moveMap(document, moveBy.toPoint()); +} + +void WorldMapTool::moveMap(MapDocument *document, QPoint moveBy) +{ + auto worldDocument = worldForMap(document); + if (!worldDocument) + return; + + const auto prevRect = worldDocument->world()->mapRect(document->fileName()); + QPoint offset = QPoint(document->map()->tileWidth() * static_cast(moveBy.x()), + document->map()->tileHeight() * static_cast(moveBy.y())); + QRect rect = document->renderer()->mapBoundingRect(); + rect.moveTo(snapPoint(prevRect.topLeft() + offset, document)); + + auto undoStack = worldDocument->undoStack(); + undoStack->push(new NonMergingSetMapRectCommand(worldDocument, document->fileName(), rect)); + + if (document == mapDocument()) { + DocumentManager *manager = DocumentManager::instance(); + MapView *view = manager->viewForDocument(mapDocument()); + QRectF viewRect { view->viewport()->rect() }; + QRectF sceneViewRect = view->viewportTransform().inverted().mapRect(viewRect); + view->forceCenterOn(sceneViewRect.center() - offset); + } +} + +void WorldMapTool::mouseEntered() +{ +} + +void WorldMapTool::mousePressed(QGraphicsSceneMouseEvent *event) +{ + if (mDraggingMap || mResizing) + return; + + setTargetMap(mapAt(event->scenePos())); + + if (event->button() == Qt::LeftButton && mapCanBeMoved(targetMap())) { + if (ResizeHandleItem *handle = handleAt(event->scenePos())) { + mResizing = true; + mResizeAnchor = handle->anchor(); + mResizeStartScenePos = event->scenePos(); + mResizingMap = targetMap(); + mOriginalWorldRect = mapRect(mResizingMap); + mOriginalTileSize = QSize(mResizingMap->map()->tileWidth(), + mResizingMap->map()->tileHeight()); + mOriginalMapSizeTiles = mResizingMap->map()->tileBoundingRect().size(); + + setTargetMap(nullptr); + mResizePreviewRectangle->setRectangle(mOriginalWorldRect); + mResizePreviewRectangle->setVisible(true); + + hideResizeHandles(); + refreshCursor(); + return; + } + + const QRect rect = mapRect(targetMap()); + if (rect.contains(event->scenePos().toPoint())) { + mDraggingMap = targetMap(); + mDraggingMapItem = mapScene()->mapItem(mDraggingMap); + mDragStartScenePos = event->scenePos(); + mDraggedMapStartPos = mDraggingMapItem->pos(); + mDragOffset = QPoint(0, 0); + updateResizeHandlesForPreview(rect); + for (auto handle : mResizeHandles) + handle->setVisible(true); + refreshCursor(); + return; + } + } + + AbstractWorldTool::mousePressed(event); +} + +void WorldMapTool::mouseMoved(const QPointF &pos, + Qt::KeyboardModifiers modifiers) +{ + if (mResizing) { + const ResizeDelta d = computeResizeDelta(pos); + const int tw = mOriginalTileSize.width(); + const int th = mOriginalTileSize.height(); + + QRect previewRect = mOriginalWorldRect; + previewRect.adjust(-d.dLeftTiles * tw, + -d.dTopTiles * th, + d.dRightTiles * tw, + d.dBottomTiles * th); + + mResizePreviewRectangle->setRectangle(previewRect); + updateResizeHandlesForPreview(previewRect); + + setStatusInfo(tr("Resize to %1 x %2 tiles (offset: %3, %4)") + .arg(d.newWidth).arg(d.newHeight) + .arg(d.dLeftTiles).arg(d.dTopTiles)); + return; + } + + if (!worldForMap(mDraggingMap) || !mDraggingMap) { + AbstractWorldTool::mouseMoved(pos, modifiers); + updateResizeHandles(); + ResizeHandleItem *handle = handleAt(pos); + if (mHoveredResizeHandle != handle) { + if (mHoveredResizeHandle) + mHoveredResizeHandle->setUnderMouse(false); + mHoveredResizeHandle = handle; + if (mHoveredResizeHandle) + mHoveredResizeHandle->setUnderMouse(true); + } + refreshCursor(); + return; + } + + const QRect currentMapRect = mapRect(mDraggingMap); + const QPoint offset = (pos - mDragStartScenePos).toPoint(); + + QPoint newPos = currentMapRect.topLeft() + offset; + if (!(modifiers & Qt::ControlModifier)) + newPos = snapPoint(newPos, mDraggingMap); + + mDragOffset = newPos - currentMapRect.topLeft(); + + mDraggingMapItem->setPos(mDraggedMapStartPos + mDragOffset); + updateSelectionRectangle(); + updateResizeHandlesForPreview(mapRect(mDraggingMap)); + + setStatusInfo(tr("Move map to %1, %2 (offset: %3, %4)") + .arg(newPos.x()) + .arg(newPos.y()) + .arg(mDragOffset.x()) + .arg(mDragOffset.y())); +} + +void WorldMapTool::mouseReleased(QGraphicsSceneMouseEvent *event) +{ + if (mResizing) { + if (event->button() == Qt::LeftButton) { + const ResizeDelta d = computeResizeDelta(event->scenePos()); + const QSize newSize(d.newWidth, d.newHeight); + const QPoint offset(d.dLeftTiles, d.dTopTiles); + + if (mResizingMap && newSize != mOriginalMapSizeTiles) + mResizingMap->resizeMap(newSize, offset, false); + + mResizing = false; + mResizingMap = nullptr; + mResizePreviewRectangle->setVisible(false); + setTargetMap(mapAt(event->scenePos())); + updateResizeHandles(); + refreshCursor(); + setStatusInfo(QString()); + return; + } + + if (event->button() == Qt::RightButton) { + abortResizing(); + return; + } + } + + if (!mDraggingMap) + return; + + if (event->button() == Qt::LeftButton) { + DocumentManager *manager = DocumentManager::instance(); + MapView *view = manager->viewForDocument(mapDocument()); + const QRectF viewRect { view->viewport()->rect() }; + const QRectF sceneViewRect = view->viewportTransform().inverted().mapRect(viewRect); + + auto draggedMap = std::exchange(mDraggingMap, nullptr); + mDraggingMapItem = nullptr; + + if (!mDragOffset.isNull()) { + if (auto worldDocument = worldForMap(draggedMap)) { + QRect rect = draggedMap->renderer()->mapBoundingRect(); + + auto world = worldDocument->world(); + rect.moveTo(world->mapRect(draggedMap->fileName()).topLeft()); + rect.translate(mDragOffset); + + auto undoStack = worldDocument->undoStack(); + undoStack->push(new NonMergingSetMapRectCommand(worldDocument, draggedMap->fileName(), rect)); + + if (draggedMap == mapDocument()) + view->forceCenterOn(sceneViewRect.center() - mDragOffset); + } + } else { + manager->switchToDocumentAndHandleSimiliarTileset(draggedMap, + sceneViewRect.center() - mDraggedMapStartPos, + view->zoomable()->scale()); + } + + setTargetMap(mapAt(event->scenePos())); + updateResizeHandles(); + refreshCursor(); + setStatusInfo(QString()); + return; + } + + if (event->button() == Qt::RightButton) + abortMoving(); +} + +void WorldMapTool::languageChanged() +{ + setName(tr("World Tool")); + AbstractWorldTool::languageChanged(); +} + +void WorldMapTool::refreshCursor() +{ + Qt::CursorShape cursorShape = Qt::ArrowCursor; + + if (mResizing) { + switch (mResizeAnchor) { + case TopLeftAnchor: + case BottomRightAnchor: + cursorShape = Qt::SizeFDiagCursor; break; + case TopRightAnchor: + case BottomLeftAnchor: + cursorShape = Qt::SizeBDiagCursor; break; + case TopAnchor: + case BottomAnchor: + cursorShape = Qt::SizeVerCursor; break; + case LeftAnchor: + case RightAnchor: + cursorShape = Qt::SizeHorCursor; break; + default: + break; + } + } else if (mDraggingMap) { + cursorShape = Qt::SizeAllCursor; + } else if (mHoveredResizeHandle) { + switch (mHoveredResizeHandle->anchor()) { + case TopLeftAnchor: + case BottomRightAnchor: + cursorShape = Qt::SizeFDiagCursor; break; + case TopRightAnchor: + case BottomLeftAnchor: + cursorShape = Qt::SizeBDiagCursor; break; + case TopAnchor: + case BottomAnchor: + cursorShape = Qt::SizeVerCursor; break; + case LeftAnchor: + case RightAnchor: + cursorShape = Qt::SizeHorCursor; break; + default: + break; + } + } + + if (cursor().shape() != cursorShape) + setCursor(cursorShape); +} + +void WorldMapTool::abortMoving() +{ + if (!mDraggingMap) + return; + + MapDocument *draggedMap = mDraggingMap; + mDraggingMapItem->setPos(mDraggedMapStartPos); + mDraggingMapItem = nullptr; + mDraggingMap = nullptr; + updateSelectionRectangle(); + updateResizeHandlesForPreview(mapRect(draggedMap)); + + refreshCursor(); + setStatusInfo(QString()); +} + +void WorldMapTool::abortResizing() +{ + if (!mResizing) + return; + + mResizing = false; + mResizingMap = nullptr; + mResizePreviewRectangle->setVisible(false); + setTargetMap(mapAt(mResizeStartScenePos)); + updateResizeHandles(); + refreshCursor(); + setStatusInfo(QString()); +} + +ResizeHandleItem *WorldMapTool::handleAt(const QPointF &scenePos) const +{ + if (!mapScene()) + return nullptr; + const auto views = mapScene()->views(); + if (views.isEmpty()) + return nullptr; + + QGraphicsItem *item = mapScene()->itemAt(scenePos, views.first()->transform()); + return dynamic_cast(item); +} + +void WorldMapTool::updateResizeHandles() +{ + if (mResizing || mDraggingMap) { + hideResizeHandles(); + return; + } + + MapDocument *target = targetMap(); + if (!target || !mapCanBeMoved(target)) { + hideResizeHandles(); + return; + } + + const QRect rect = mapRect(target); + updateResizeHandlesForPreview(rect); + + for (auto handle : mResizeHandles) + handle->setVisible(true); +} + +void WorldMapTool::updateResizeHandlesForPreview(const QRect &rect) +{ + const QPointF topLeft = rect.topLeft(); + const QPointF topRight = QPointF(rect.right() + 1, rect.top()); + const QPointF bottomLeft = QPointF(rect.left(), rect.bottom() + 1); + const QPointF bottomRight = QPointF(rect.right() + 1, rect.bottom() + 1); + + mResizeHandles[TopLeftAnchor]->setPos(topLeft); + mResizeHandles[TopRightAnchor]->setPos(topRight); + mResizeHandles[BottomLeftAnchor]->setPos(bottomLeft); + mResizeHandles[BottomRightAnchor]->setPos(bottomRight); + mResizeHandles[TopAnchor]->setPos((topLeft + topRight) / 2.0); + mResizeHandles[LeftAnchor]->setPos((topLeft + bottomLeft) / 2.0); + mResizeHandles[RightAnchor]->setPos((topRight + bottomRight) / 2.0); + mResizeHandles[BottomAnchor]->setPos((bottomLeft + bottomRight) / 2.0); +} + +void WorldMapTool::hideResizeHandles() +{ + if (mHoveredResizeHandle) { + mHoveredResizeHandle->setUnderMouse(false); + mHoveredResizeHandle = nullptr; + } + for (auto handle : mResizeHandles) + handle->setVisible(false); +} + +WorldMapTool::ResizeDelta WorldMapTool::computeResizeDelta(const QPointF ¤tScenePos) const +{ + ResizeDelta d; + const int tw = mOriginalTileSize.width(); + const int th = mOriginalTileSize.height(); + if (tw <= 0 || th <= 0) { + d.newWidth = qMax(1, mOriginalMapSizeTiles.width()); + d.newHeight = qMax(1, mOriginalMapSizeTiles.height()); + return d; + } + + const QPointF rawDelta = currentScenePos - mResizeStartScenePos; + const int snappedDx = static_cast(std::round(rawDelta.x() / tw)) * tw; + const int snappedDy = static_cast(std::round(rawDelta.y() / th)) * th; + + int dLeft = 0, dTop = 0, dRight = 0, dBottom = 0; + switch (mResizeAnchor) { + case TopLeftAnchor: dLeft = -snappedDx; dTop = -snappedDy; break; + case TopAnchor: dTop = -snappedDy; break; + case TopRightAnchor: dRight = snappedDx; dTop = -snappedDy; break; + case LeftAnchor: dLeft = -snappedDx; break; + case RightAnchor: dRight = snappedDx; break; + case BottomLeftAnchor: dLeft = -snappedDx; dBottom = snappedDy; break; + case BottomAnchor: dBottom = snappedDy; break; + case BottomRightAnchor: dRight = snappedDx; dBottom = snappedDy; break; + default: break; + } + + d.dLeftTiles = dLeft / tw; + d.dTopTiles = dTop / th; + d.dRightTiles = dRight / tw; + d.dBottomTiles = dBottom / th; + d.newWidth = mOriginalMapSizeTiles.width() + d.dLeftTiles + d.dRightTiles; + d.newHeight = mOriginalMapSizeTiles.height() + d.dTopTiles + d.dBottomTiles; + + if (d.newWidth < 1) { + const int excess = 1 - d.newWidth; + switch (mResizeAnchor) { + case TopLeftAnchor: case LeftAnchor: case BottomLeftAnchor: + d.dLeftTiles -= excess; break; + default: + d.dRightTiles -= excess; break; + } + d.newWidth = mOriginalMapSizeTiles.width() + d.dLeftTiles + d.dRightTiles; + } + + if (d.newHeight < 1) { + const int excess = 1 - d.newHeight; + switch (mResizeAnchor) { + case TopLeftAnchor: case TopAnchor: case TopRightAnchor: + d.dTopTiles -= excess; break; + default: + d.dBottomTiles -= excess; break; + } + d.newHeight = mOriginalMapSizeTiles.height() + d.dTopTiles + d.dBottomTiles; + } + + return d; +} + +} // namespace Tiled + +#include "moc_worldmaptool.cpp" + diff --git a/src/tiled/worldmovemaptool.h b/src/tiled/worldmaptool.h similarity index 50% rename from src/tiled/worldmovemaptool.h rename to src/tiled/worldmaptool.h index 05c444b472..3376f779dd 100644 --- a/src/tiled/worldmovemaptool.h +++ b/src/tiled/worldmaptool.h @@ -1,5 +1,5 @@ /* - * worldmovemaptool.h + * worldmaptool.h * Copyright 2019, Nils Kuebler * * This file is part of Tiled. @@ -22,17 +22,42 @@ #include "abstractworldtool.h" +#include +#include + namespace Tiled { class MapItem; +class SelectionRectangle; + +enum ResizeAnchor { + TopLeftAnchor, + TopRightAnchor, + BottomLeftAnchor, + BottomRightAnchor, + + TopAnchor, + LeftAnchor, + RightAnchor, + BottomAnchor, + + ResizeAnchorCount = 8 +}; + +class ResizeHandleItem; -class WorldMoveMapTool : public AbstractWorldTool +class WorldMapTool : public AbstractWorldTool { Q_OBJECT public: - explicit WorldMoveMapTool(QObject *parent = nullptr); - ~WorldMoveMapTool() override; + explicit WorldMapTool(QObject *parent = nullptr); + ~WorldMapTool() override; + + QUndoStack *undoStack() override; + + void activate(MapScene *scene) override; + void deactivate(MapScene *scene) override; void keyPressed(QKeyEvent *event) override; void mouseEntered() override; @@ -45,16 +70,45 @@ class WorldMoveMapTool : public AbstractWorldTool protected: void abortMoving(); + void abortResizing(); void refreshCursor(); void moveMap(MapDocument *document, QPoint moveBy); - // drag state + struct ResizeDelta { + int dLeftTiles = 0; + int dTopTiles = 0; + int dRightTiles = 0; + int dBottomTiles = 0; + int newWidth = 0; + int newHeight = 0; + }; + + ResizeHandleItem *handleAt(const QPointF &scenePos) const; + void updateResizeHandles(); + void updateResizeHandlesForPreview(const QRect &previewRect); + void hideResizeHandles(); + ResizeDelta computeResizeDelta(const QPointF ¤tScenePos) const; + + // for drag MapDocument *mDraggingMap = nullptr; MapItem *mDraggingMapItem = nullptr; QPointF mDragStartScenePos; QPointF mDraggedMapStartPos; QPoint mDragOffset; + + // for resize + ResizeHandleItem *mResizeHandles[ResizeAnchorCount]; + ResizeHandleItem *mHoveredResizeHandle = nullptr; + bool mResizing = false; + ResizeAnchor mResizeAnchor; + QPointF mResizeStartScenePos; + QRect mOriginalWorldRect; + QSize mOriginalTileSize; + QSize mOriginalMapSizeTiles; + MapDocument *mResizingMap = nullptr; + std::unique_ptr mResizePreviewRectangle; }; } // namespace Tiled + diff --git a/src/tiled/worldmovemaptool.cpp b/src/tiled/worldmovemaptool.cpp deleted file mode 100644 index 7529940c1f..0000000000 --- a/src/tiled/worldmovemaptool.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* - * worldmovemaptool.cpp - * Copyright 2019, Nils Kuebler - * - * This file is part of Tiled. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ - -#include "worldmovemaptool.h" - -#include "changeworld.h" -#include "documentmanager.h" -#include "layer.h" -#include "map.h" -#include "mapdocument.h" -#include "maprenderer.h" -#include "mapscene.h" -#include "mapview.h" -#include "preferences.h" -#include "toolmanager.h" -#include "utils.h" -#include "world.h" -#include "worlddocument.h" -#include "zoomable.h" - -#include -#include -#include -#include -#include -#include - -#include - -using namespace Tiled; - -namespace Tiled { - -WorldMoveMapTool::WorldMoveMapTool(QObject *parent) - : AbstractWorldTool("WorldMoveMapTool", - tr("World Tool"), - QIcon(QLatin1String(":images/22/world-move-tool.png")), - QKeySequence(Qt::Key_N), - parent) -{ -} - -WorldMoveMapTool::~WorldMoveMapTool() -{ -} - -void WorldMoveMapTool::keyPressed(QKeyEvent *event) -{ - QPointF moveBy; - - switch (event->key()) { - case Qt::Key_Up: moveBy = QPointF(0, -1); break; - case Qt::Key_Down: moveBy = QPointF(0, 1); break; - case Qt::Key_Left: moveBy = QPointF(-1, 0); break; - case Qt::Key_Right: moveBy = QPointF(1, 0); break; - case Qt::Key_Escape: - abortMoving(); - return; - default: - AbstractWorldTool::keyPressed(event); - return; - } - - const Qt::KeyboardModifiers modifiers = event->modifiers(); - if (moveBy.isNull() || (modifiers & Qt::ControlModifier)) { - event->ignore(); - return; - } - MapDocument *document = mapDocument(); - if (!document || !mapCanBeMoved(document) || mDraggingMap) - return; - - const bool moveFast = modifiers & Qt::ShiftModifier; - const auto snapMode = Preferences::instance()->snapMode(); - - if (moveFast) { - // TODO: This only makes sense for orthogonal maps - moveBy.rx() *= document->map()->tileWidth(); - moveBy.ry() *= document->map()->tileHeight(); - if (snapMode == SnapMode::FineGrid) - moveBy /= Preferences::instance()->gridFine(); - } - - moveMap(document, moveBy.toPoint()); -} - -void WorldMoveMapTool::moveMap(MapDocument *document, QPoint moveBy) -{ - auto worldDocument = worldForMap(document); - if (!worldDocument) - return; - - const auto prevRect = worldDocument->world()->mapRect(document->fileName()); - QPoint offset = QPoint(document->map()->tileWidth() * static_cast(moveBy.x()), - document->map()->tileHeight() * static_cast(moveBy.y())); - QRect rect = document->renderer()->mapBoundingRect(); - rect.moveTo(snapPoint(prevRect.topLeft() + offset, document)); - - auto undoStack = worldDocument->undoStack(); - undoStack->push(new SetMapRectCommand(worldDocument, document->fileName(), rect)); - - if (document == mapDocument()) { - // undo camera movement - DocumentManager *manager = DocumentManager::instance(); - MapView *view = manager->viewForDocument(mapDocument()); - QRectF viewRect { view->viewport()->rect() }; - QRectF sceneViewRect = view->viewportTransform().inverted().mapRect(viewRect); - view->forceCenterOn(sceneViewRect.center() - offset); - } -} - -void WorldMoveMapTool::mouseEntered() -{ -} - -void WorldMoveMapTool::mousePressed(QGraphicsSceneMouseEvent *event) -{ - if (mDraggingMap) - return; - - if (event->button() == Qt::LeftButton && mapCanBeMoved(targetMap())) { - // initiate drag action - mDraggingMap = targetMap(); - mDraggingMapItem = mapScene()->mapItem(mDraggingMap); - mDragStartScenePos = event->scenePos(); - mDraggedMapStartPos = mDraggingMapItem->pos(); - mDragOffset = QPoint(0, 0); - refreshCursor(); - return; - } - - AbstractWorldTool::mousePressed(event); -} - -void WorldMoveMapTool::mouseMoved(const QPointF &pos, - Qt::KeyboardModifiers modifiers) -{ - if (!worldForMap(mDraggingMap) || !mDraggingMap) { - AbstractWorldTool::mouseMoved(pos, modifiers); - return; - } - - // calculate new drag offset - const QRect currentMapRect = mapRect(mDraggingMap); - const QPoint offset = (pos - mDragStartScenePos).toPoint(); - - QPoint newPos = currentMapRect.topLeft() + offset; - if (!(modifiers & Qt::ControlModifier)) - newPos = snapPoint(newPos, mDraggingMap); - - mDragOffset = newPos - currentMapRect.topLeft(); - - // update preview - mDraggingMapItem->setPos(mDraggedMapStartPos + mDragOffset); - updateSelectionRectangle(); - - setStatusInfo(tr("Move map to %1, %2 (offset: %3, %4)") - .arg(newPos.x()) - .arg(newPos.y()) - .arg(mDragOffset.x()) - .arg(mDragOffset.y())); -} - -void WorldMoveMapTool::mouseReleased(QGraphicsSceneMouseEvent *event) -{ - if (!mDraggingMap) - return; - - if (event->button() == Qt::LeftButton) { - DocumentManager *manager = DocumentManager::instance(); - MapView *view = manager->viewForDocument(mapDocument()); - const QRectF viewRect { view->viewport()->rect() }; - const QRectF sceneViewRect = view->viewportTransform().inverted().mapRect(viewRect); - - auto draggedMap = std::exchange(mDraggingMap, nullptr); - mDraggingMapItem = nullptr; - - if (!mDragOffset.isNull()) { - if (auto worldDocument = worldForMap(draggedMap)) { - QRect rect = draggedMap->renderer()->mapBoundingRect(); - - auto world = worldDocument->world(); - rect.moveTo(world->mapRect(draggedMap->fileName()).topLeft()); - rect.translate(mDragOffset); - - auto undoStack = worldDocument->undoStack(); - undoStack->push(new SetMapRectCommand(worldDocument, draggedMap->fileName(), rect)); - - if (draggedMap == mapDocument()) { - // undo camera movement - view->forceCenterOn(sceneViewRect.center() - mDragOffset); - } - } - } else { - // switch to the document - manager->switchToDocumentAndHandleSimiliarTileset(draggedMap, - sceneViewRect.center() - mDraggedMapStartPos, - view->zoomable()->scale()); - } - - refreshCursor(); - setStatusInfo(QString()); - return; - } - - if (event->button() == Qt::RightButton) - abortMoving(); -} - -void WorldMoveMapTool::languageChanged() -{ - setName(tr("World Tool")); - - AbstractWorldTool::languageChanged(); -} - -void WorldMoveMapTool::refreshCursor() -{ - Qt::CursorShape cursorShape = Qt::ArrowCursor; - - if (mDraggingMap) - cursorShape = Qt::SizeAllCursor; - - if (cursor().shape() != cursorShape) - setCursor(cursorShape); -} - -void WorldMoveMapTool::abortMoving() -{ - if (!mDraggingMap) - return; - - mDraggingMapItem->setPos(mDraggedMapStartPos); - mDraggingMapItem = nullptr; - mDraggingMap = nullptr; - updateSelectionRectangle(); - - refreshCursor(); - setStatusInfo(QString()); -} - -} // namespace Tiled - -#include "moc_worldmovemaptool.cpp"