Skip to content

Commit 71a8bca

Browse files
committed
Merge move functionality into selection tools via BrushItem
Addresses PR review feedback: instead of a separate Move Tiles tool, the move/duplicate behavior is now built into the selection tools (Rectangular Select, Magic Wand, Select Same Tile). Click and drag inside a selection to move; hold Alt to duplicate. FloatingTileSelectionItem is replaced by extending BrushItem with an optional animated outline mode. The marching ants outline now uses QPainterPath::simplified(), fixing internal lines that appeared with non-rectangular selections. Removed files: - tilemovetool.h/cpp - floatingtileselectionitem.h/cpp
1 parent 00787c9 commit 71a8bca

15 files changed

Lines changed: 522 additions & 759 deletions

docs/manual/editing-tile-layers.rst

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,28 +177,20 @@ While selecting an area, the following modifiers can be used:
177177
- Hold ``Shift`` to constrain the selection to a square.
178178
- Hold ``Ctrl`` to expand the selection from the starting location.
179179

180-
.. _move-tiles-tool:
180+
.. _moving-tiles:
181181

182-
Move Tiles
183-
----------
182+
Moving Tiles
183+
^^^^^^^^^^^^
184184

185-
Shortcut: ``V`` |stock-tool-move|
185+
Click and drag inside a selection to move the selected tiles to a new
186+
location.
186187

187-
The Move Tiles tool allows moving or duplicating selected tiles to a new
188-
location. First select tiles using one of the :ref:`tile-selection-tools`,
189-
then switch to this tool.
190-
191-
- Drag the selection to move tiles to a new location.
192188
- Hold ``Alt`` while dragging to duplicate tiles instead of moving them.
193189
- Press ``Escape`` or right-click to cancel the move operation.
194190

195191
Undo will restore both the tiles and the selection to their original
196192
positions.
197193

198-
.. raw:: html
199-
200-
<div class="new">Since Tiled 1.12</div>
201-
202194
Managing Tile Stamps
203195
--------------------
204196

@@ -218,7 +210,6 @@ using the *Tile Stamps* view.
218210
.. |stock-tool-bucket-fill| image:: ../../src/tiled/resources/images/22/stock-tool-bucket-fill.png
219211
.. |stock-tool-clone| image:: ../../src/tiled/resources/images/22/stock-tool-clone.png
220212
.. |stock-tool-eraser| image:: ../../src/tiled/resources/images/22/stock-tool-eraser.png
221-
.. |stock-tool-move| image:: ../../src/tiled/resources/images/22/stock-tool-move-22.png
222213
.. |stock-tool-rect-select| image:: ../../src/tiled/resources/images/22/stock-tool-rect-select.png
223214
.. |stock-tool-by-color-select| image:: ../../src/tiled/resources/images/22/stock-tool-by-color-select.png
224215
.. |stock-tool-fuzzy-select-22| image:: ../../src/tiled/resources/images/22/stock-tool-fuzzy-select-22.png

src/libtiled/isometricrenderer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ class TILEDSHARED_EXPORT IsometricRenderer final : public MapRenderer
8484
using MapRenderer::pixelToScreenCoords;
8585
QPointF pixelToScreenCoords(qreal x, qreal y) const override;
8686

87+
QPolygonF tileRectToScreenPolygon(const QRect &rect) const;
88+
8789
private:
8890
QTransform transform() const;
8991
QPolygonF pixelRectToScreenPolygon(const QRectF &rect) const;
90-
QPolygonF tileRectToScreenPolygon(const QRect &rect) const;
9192
};
9293

9394
} // namespace Tiled

src/tiled/abstracttileselectiontool.cpp

Lines changed: 261 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@
2323
#include "brushitem.h"
2424
#include "changeselectedarea.h"
2525
#include "mapdocument.h"
26+
#include "mapscene.h"
27+
#include "movetiles.h"
28+
#include "tilelayer.h"
2629

2730
#include <QAction>
2831
#include <QActionGroup>
2932
#include <QApplication>
33+
#include <QGraphicsView>
3034
#include <QKeyEvent>
3135
#include <QToolBar>
36+
#include <QUndoStack>
3237

3338
using namespace Tiled;
3439

@@ -81,34 +86,68 @@ AbstractTileSelectionTool::AbstractTileSelectionTool(Id id,
8186
AbstractTileSelectionTool::languageChanged();
8287
}
8388

89+
AbstractTileSelectionTool::~AbstractTileSelectionTool()
90+
{
91+
}
92+
93+
void AbstractTileSelectionTool::deactivate(MapScene *scene)
94+
{
95+
if (mMoveState == MovingTiles)
96+
cancelMove();
97+
98+
AbstractTileTool::deactivate(scene);
99+
}
100+
84101
void AbstractTileSelectionTool::mousePressed(QGraphicsSceneMouseEvent *event)
85102
{
86103
const auto button = event->button();
87104
const auto modifiers = event->modifiers();
88105

89106
if (button == Qt::LeftButton) {
107+
if (tryStartMove(event))
108+
return;
109+
90110
mMouseDown = true;
91111
return;
92112
}
93113

94-
if (button == Qt::RightButton && modifiers == Qt::NoModifier) {
95-
// Right mouse button cancels selection
96-
if (mMouseDown) {
97-
mMouseDown = false;
98-
setSelectionPreview(QRegion());
114+
if (button == Qt::RightButton) {
115+
if (mMoveState == MovingTiles) {
116+
cancelMove();
99117
return;
100118
}
101119

102-
// Right mouse button clears selection
103-
changeSelectedArea(QRegion());
104-
return;
120+
if (modifiers == Qt::NoModifier) {
121+
if (mMouseDown) {
122+
mMouseDown = false;
123+
setSelectionPreview(QRegion());
124+
return;
125+
}
126+
127+
changeSelectedArea(QRegion());
128+
return;
129+
}
105130
}
106131

107132
AbstractTileTool::mousePressed(event);
108133
}
109134

110135
void AbstractTileSelectionTool::mouseReleased(QGraphicsSceneMouseEvent *event)
111136
{
137+
if (event->button() == Qt::LeftButton) {
138+
switch (mMoveState) {
139+
case PickingUp:
140+
mMoveState = NoMove;
141+
updateMoveCursor();
142+
return;
143+
case MovingTiles:
144+
commitMove();
145+
return;
146+
case NoMove:
147+
break;
148+
}
149+
}
150+
112151
if (event->button() == Qt::LeftButton && mMouseDown) {
113152
mMouseDown = false;
114153

@@ -119,8 +158,33 @@ void AbstractTileSelectionTool::mouseReleased(QGraphicsSceneMouseEvent *event)
119158
}
120159
}
121160

161+
void AbstractTileSelectionTool::mouseMoved(const QPointF &pos,
162+
Qt::KeyboardModifiers modifiers)
163+
{
164+
AbstractTileTool::mouseMoved(pos, modifiers);
165+
166+
if (mMoveState == PickingUp) {
167+
QPointF screenPos = mapScene()->views().first()->mapFromScene(pos);
168+
QPointF delta = screenPos - mMoveScreenStart;
169+
if (delta.manhattanLength() >= MoveDragThreshold) {
170+
pickUpSelection();
171+
}
172+
} else if (mMoveState == MovingTiles) {
173+
updateFloatingPosition();
174+
}
175+
}
176+
122177
void AbstractTileSelectionTool::modifiersChanged(Qt::KeyboardModifiers modifiers)
123178
{
179+
if (mMoveState == MovingTiles) {
180+
bool wasDuplicate = mDuplicateMode;
181+
mDuplicateMode = modifiers & Qt::AltModifier;
182+
if (wasDuplicate != mDuplicateMode)
183+
updateStatusInfo();
184+
updateMoveCursor();
185+
return;
186+
}
187+
124188
if (modifiers == Qt::ControlModifier)
125189
mSelectionMode = Subtract;
126190
else if (modifiers == Qt::ShiftModifier)
@@ -141,8 +205,12 @@ void AbstractTileSelectionTool::modifiersChanged(Qt::KeyboardModifiers modifiers
141205
void AbstractTileSelectionTool::keyPressed(QKeyEvent *event)
142206
{
143207
if (event->key() == Qt::Key_Escape) {
208+
if (mMoveState == MovingTiles) {
209+
cancelMove();
210+
return;
211+
}
212+
144213
if (mMouseDown) {
145-
// Cancel the ongoing selection
146214
mMouseDown = false;
147215
setSelectionPreview(QRegion());
148216
return;
@@ -212,4 +280,188 @@ void AbstractTileSelectionTool::updateBrushVisibility()
212280
brushItem()->setVisible(isBrushVisible());
213281
}
214282

283+
void AbstractTileSelectionTool::mapDocumentChanged(MapDocument *oldDocument,
284+
MapDocument *newDocument)
285+
{
286+
if (mMoveState == MovingTiles)
287+
cancelMove();
288+
289+
if (oldDocument) {
290+
disconnect(oldDocument, &MapDocument::selectedAreaChanged,
291+
this, &AbstractTileSelectionTool::onMoveSelectionChanged);
292+
}
293+
294+
if (newDocument) {
295+
connect(newDocument, &MapDocument::selectedAreaChanged,
296+
this, &AbstractTileSelectionTool::onMoveSelectionChanged);
297+
}
298+
299+
AbstractTileTool::mapDocumentChanged(oldDocument, newDocument);
300+
}
301+
302+
bool AbstractTileSelectionTool::tryStartMove(QGraphicsSceneMouseEvent *event)
303+
{
304+
if (mMoveState != NoMove)
305+
return true;
306+
307+
const auto modifiers = event->modifiers();
308+
309+
// Shift/Ctrl are selection mode modifiers -- don't intercept
310+
if (modifiers & (Qt::ShiftModifier | Qt::ControlModifier))
311+
return false;
312+
313+
if (!hasActiveSelection())
314+
return false;
315+
316+
if (!mapDocument()->selectedArea().contains(tilePosition()))
317+
return false;
318+
319+
mMoveScreenStart = event->screenPos();
320+
mPickupTilePos = tilePosition();
321+
mDuplicateMode = modifiers & Qt::AltModifier;
322+
mMoveState = PickingUp;
323+
updateMoveCursor();
324+
return true;
325+
}
326+
327+
void AbstractTileSelectionTool::pickUpSelection()
328+
{
329+
if (!mapDocument())
330+
return;
331+
332+
TileLayer *layer = currentTileLayer();
333+
if (!layer)
334+
return;
335+
336+
mOriginalSelection = mapDocument()->selectedArea();
337+
if (mOriginalSelection.isEmpty()) {
338+
mMoveState = NoMove;
339+
updateMoveCursor();
340+
return;
341+
}
342+
343+
mFloatingTiles = SharedTileLayer::create();
344+
mFloatingTiles->setCells(0, 0, layer, mOriginalSelection);
345+
346+
brushItem()->setTileLayer(mFloatingTiles, mOriginalSelection);
347+
brushItem()->setAnimatedOutline(true);
348+
brushItem()->setTileOffset(QPoint(0, 0));
349+
350+
mUndoIndexBeforeMove = mapDocument()->undoStack()->index();
351+
connect(mapDocument()->undoStack(), &QUndoStack::indexChanged,
352+
this, &AbstractTileSelectionTool::onUndoIndexChanged);
353+
354+
mCurrentTilePos = mPickupTilePos;
355+
mMoveState = MovingTiles;
356+
updateMoveCursor();
357+
}
358+
359+
void AbstractTileSelectionTool::updateFloatingPosition()
360+
{
361+
QPoint offset = tilePosition() - mPickupTilePos;
362+
brushItem()->setTileOffset(offset);
363+
mCurrentTilePos = tilePosition();
364+
}
365+
366+
void AbstractTileSelectionTool::commitMove()
367+
{
368+
if (!mapDocument() || mOriginalSelection.isEmpty()) {
369+
cancelMove();
370+
return;
371+
}
372+
373+
TileLayer *layer = currentTileLayer();
374+
if (!layer) {
375+
cancelMove();
376+
return;
377+
}
378+
379+
disconnect(mapDocument()->undoStack(), &QUndoStack::indexChanged,
380+
this, &AbstractTileSelectionTool::onUndoIndexChanged);
381+
382+
QPoint offset = mCurrentTilePos - mPickupTilePos;
383+
384+
if (offset != QPoint(0, 0) || mDuplicateMode) {
385+
QUndoStack *undoStack = mapDocument()->undoStack();
386+
387+
undoStack->beginMacro(mDuplicateMode ? tr("Duplicate Tiles") : tr("Move Tiles"));
388+
389+
undoStack->push(new MoveTiles(
390+
mapDocument(),
391+
layer,
392+
mOriginalSelection,
393+
offset,
394+
mDuplicateMode
395+
));
396+
397+
QRegion newSelection = mOriginalSelection.translated(offset);
398+
undoStack->push(new ChangeSelectedArea(mapDocument(), newSelection));
399+
400+
undoStack->endMacro();
401+
}
402+
403+
brushItem()->setAnimatedOutline(false);
404+
brushItem()->setTileOffset(QPoint(0, 0));
405+
brushItem()->setTileLayer(SharedTileLayer());
406+
mFloatingTiles.reset();
407+
mOriginalSelection = QRegion();
408+
409+
mMoveState = NoMove;
410+
updateMoveCursor();
411+
}
412+
413+
void AbstractTileSelectionTool::cancelMove()
414+
{
415+
if (mapDocument()) {
416+
disconnect(mapDocument()->undoStack(), &QUndoStack::indexChanged,
417+
this, &AbstractTileSelectionTool::onUndoIndexChanged);
418+
}
419+
420+
brushItem()->setAnimatedOutline(false);
421+
brushItem()->setTileOffset(QPoint(0, 0));
422+
brushItem()->setTileLayer(SharedTileLayer());
423+
mFloatingTiles.reset();
424+
mOriginalSelection = QRegion();
425+
426+
mMoveState = NoMove;
427+
updateMoveCursor();
428+
}
429+
430+
bool AbstractTileSelectionTool::hasActiveSelection() const
431+
{
432+
return mapDocument() && !mapDocument()->selectedArea().isEmpty();
433+
}
434+
435+
void AbstractTileSelectionTool::updateMoveCursor()
436+
{
437+
switch (mMoveState) {
438+
case NoMove:
439+
setCursor(QCursor());
440+
break;
441+
case PickingUp:
442+
setCursor(Qt::ClosedHandCursor);
443+
break;
444+
case MovingTiles:
445+
setCursor(mDuplicateMode ? Qt::DragCopyCursor : Qt::SizeAllCursor);
446+
break;
447+
}
448+
}
449+
450+
void AbstractTileSelectionTool::onUndoIndexChanged()
451+
{
452+
if (mMoveState == MovingTiles && mapDocument()) {
453+
int currentIndex = mapDocument()->undoStack()->index();
454+
if (currentIndex < mUndoIndexBeforeMove) {
455+
cancelMove();
456+
}
457+
}
458+
}
459+
460+
void AbstractTileSelectionTool::onMoveSelectionChanged()
461+
{
462+
if (mMoveState == MovingTiles) {
463+
cancelMove();
464+
}
465+
}
466+
215467
#include "moc_abstracttileselectiontool.cpp"

0 commit comments

Comments
 (0)