diff --git a/.gitignore b/.gitignore
index d85e383626..c97f7e9a56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ default/
# clangd cache
.cache/
+compile_commands.json
# Mac stuff
*.DS_Store
diff --git a/NEWS.md b/NEWS.md
index 1af048c280..8d37f4433e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -4,6 +4,8 @@
* Allow changing the values of number inputs using expressions (with dogboydog, #4234)
* Added support for SVG 1.2 / CSS blending modes to layers (#3932)
* Added button to toggle Terrain Brush to full tile mode (by Finlay Pearson, #3407)
+* Added square selection and expand-from-center to Rectangular Select tool (#4201)
+* Added status bar info for various Stamp Brush modes (#3092)
* Added export plugin for Remixed Dungeon (by Mikhael Danilov, #4158)
* Added "World > World Properties" menu action (with dogboydog, #4190)
* Scripting: Added API for custom property types (with dogboydog, #3971)
diff --git a/docs/manual/editing-tile-layers.rst b/docs/manual/editing-tile-layers.rst
index 812d54e0c7..eb019d91e0 100644
--- a/docs/manual/editing-tile-layers.rst
+++ b/docs/manual/editing-tile-layers.rst
@@ -156,17 +156,27 @@ There are various tile selection tools that all work in similar fashion:
- |stock-tool-by-color-select| **Select Same Tile** allows selection of
same-tiles across the entire layer (shortcut: ``S``)
-By default, each of these tools replaces the currently selected area.
-The following modifiers can be used to change this behavior:
+By default, each of these tools replaces the currently selected area. The
+following modifiers can be used to change the selection mode before starting
+the selection:
-- Holding ``Shift`` expands the current selection with the new area
-- Holding ``Ctrl`` subtracts the new area from the current selection
-- Holding ``Ctrl`` and ``Shift`` selects the intersection of the new
+- Hold ``Shift`` to expand the current selection with the new area
+- Hold ``Ctrl`` to subtract the new area from the current selection
+- Hold ``Ctrl`` and ``Shift`` to select the intersection of the new
area with the current selection
You can also lock into one of these modes (Add, Subtract or Intersect)
by clicking on one of the tool buttons in the Tool Options toolbar.
+.. raw:: html
+
+
Since Tiled 1.12
+
+While selecting an area, the following modifiers can be used:
+
+- Hold ``Shift`` to constrain the selection to a square.
+- Hold ``Ctrl`` to expand the selection from the starting location.
+
Managing Tile Stamps
--------------------
diff --git a/src/tiled/stampbrush.cpp b/src/tiled/stampbrush.cpp
index 8b6f8add12..bfdd6ad500 100644
--- a/src/tiled/stampbrush.cpp
+++ b/src/tiled/stampbrush.cpp
@@ -65,9 +65,7 @@ StampBrush::StampBrush(QObject *parent)
[this] { emit stampChanged(mStamp.rotated(RotateRight)); });
}
-StampBrush::~StampBrush()
-{
-}
+StampBrush::~StampBrush() = default;
void StampBrush::activate(MapScene *scene)
{
@@ -114,6 +112,45 @@ void StampBrush::tilePositionChanged(QPoint pos)
mPrevTilePosition = pos;
}
+void StampBrush::updateStatusInfo()
+{
+ if (!isBrushVisible()) {
+ AbstractTileTool::updateStatusInfo();
+ return;
+ }
+
+ const QPoint pos = tilePosition();
+ QString actionText;
+
+ if (mBrushState == BrushState::Capture) {
+ const QRect area = mCaptureStampHelper.capturedArea(pos);
+ actionText = tr("Capture area (%3 x %4)")
+ .arg(area.width())
+ .arg(area.height());
+ } else if (mBrushState == BrushState::StartSet) {
+ if (mBrushBehavior == BrushBehavior::Line) {
+ const int length = std::max(std::abs(pos.x() - mStampReference.x()),
+ std::abs(pos.y() - mStampReference.y())) + 1;
+ actionText = tr("Draw line (length: %3)").arg(length);
+ } else if (mBrushBehavior == BrushBehavior::Circle) {
+ const int rx = std::abs(pos.x() - mStampReference.x());
+ const int ry = std::abs(pos.y() - mStampReference.y());
+ const int w = rx * 2 + 1;
+ const int h = ry * 2 + 1;
+ actionText = tr("Draw ellipse (size: %3 x %4)").arg(w).arg(h);
+ }
+ }
+
+ if (!actionText.isEmpty()) {
+ setStatusInfo(tr("%1, %2 - %3")
+ .arg(pos.x()).arg(pos.y())
+ .arg(actionText));
+ return;
+ }
+
+ AbstractTileTool::updateStatusInfo();
+}
+
void StampBrush::mousePressed(QGraphicsSceneMouseEvent *event)
{
if (brushItem()->isVisible()) {
@@ -135,6 +172,7 @@ void StampBrush::mousePressed(QGraphicsSceneMouseEvent *event)
case BrushBehavior::Circle:
mStampReference = tilePosition();
mBrushState = BrushState::StartSet;
+ updateStatusInfo();
break;
}
break;
@@ -142,9 +180,10 @@ void StampBrush::mousePressed(QGraphicsSceneMouseEvent *event)
break;
}
return;
- } else if (event->button() == Qt::RightButton &&
- !(event->modifiers() & Qt::ControlModifier))
- {
+ }
+
+ if (event->button() == Qt::RightButton &&
+ !(event->modifiers() & Qt::ControlModifier)) {
beginCapture();
return;
}
@@ -161,6 +200,7 @@ void StampBrush::mouseReleased(QGraphicsSceneMouseEvent *event)
if (mStampReference != tilePosition()) {
doPaint();
mBrushState = BrushState::Free;
+ updateStatusInfo();
}
}
break;
@@ -210,6 +250,7 @@ void StampBrush::updateBrushBehavior()
mBrushBehavior = brushBehavior;
mBrushState = brushState;
updatePreview();
+ updateStatusInfo();
}
}
@@ -333,6 +374,8 @@ void StampBrush::endCapture()
emit stampChanged(stamp);
else
updatePreview();
+
+ updateStatusInfo(); // restore default status info
}
/**
diff --git a/src/tiled/stampbrush.h b/src/tiled/stampbrush.h
index 67b2cfc3e9..a1f7723044 100644
--- a/src/tiled/stampbrush.h
+++ b/src/tiled/stampbrush.h
@@ -90,6 +90,8 @@ public slots:
protected:
void tilePositionChanged(QPoint tilePos) override;
+ void updateStatusInfo() override;
+
void mapDocumentChanged(MapDocument *oldDocument,
MapDocument *newDocument) override;
diff --git a/src/tiled/tileselectiontool.cpp b/src/tiled/tileselectiontool.cpp
index 257f67394a..0125a4bad9 100644
--- a/src/tiled/tileselectiontool.cpp
+++ b/src/tiled/tileselectiontool.cpp
@@ -22,10 +22,8 @@
#include "brushitem.h"
#include "changeselectedarea.h"
-#include "map.h"
#include "mapdocument.h"
#include "mapscene.h"
-#include "tilelayer.h"
#include
@@ -38,8 +36,6 @@ TileSelectionTool::TileSelectionTool(QObject *parent)
":images/22/stock-tool-rect-select.png")),
QKeySequence(Qt::Key_R),
parent)
- , mMouseDown(false)
- , mSelecting(false)
{
setTilePositionMethod(OnTiles);
}
@@ -86,6 +82,8 @@ void TileSelectionTool::mousePressed(QGraphicsSceneMouseEvent *event)
if (button == Qt::LeftButton) {
mMouseDown = true;
+ mForceSquare = false;
+ mExpandFromCenter = false;
mMouseScreenStart = event->screenPos();
mSelectionStart = tilePosition();
brushItem()->setTileRegion(QRegion());
@@ -99,7 +97,9 @@ void TileSelectionTool::mousePressed(QGraphicsSceneMouseEvent *event)
mMouseDown = false; // Avoid restarting select on move
brushItem()->setTileRegion(QRegion());
return;
- } else if (event->modifiers() == Qt::NoModifier) {
+ }
+
+ if (event->modifiers() == Qt::NoModifier) {
clearSelection();
return;
}
@@ -142,6 +142,38 @@ void TileSelectionTool::mouseReleased(QGraphicsSceneMouseEvent *event)
mMouseDown = false;
}
+void TileSelectionTool::modifiersChanged(Qt::KeyboardModifiers modifiers)
+{
+ if (mMouseDown) {
+ // When the mouse is down, we no longer change the selection mode. Instead:
+ //
+ // * The Shift modifier can be used to force a 1:1 aspect ratio
+ // * The Control modifier can be used to expand from the center
+ //
+ // Only a change in modifier state has an effect because we don't want
+ // modifiers that were already held when starting the selection to
+ // affect these options, since at that point they were used to set the
+ // selection mode.
+
+ const bool shift = modifiers & Qt::ShiftModifier;
+ const bool ctrl = modifiers & Qt::ControlModifier;
+
+ if (shift != (mLastModifiers & Qt::ShiftModifier))
+ mForceSquare = shift;
+
+ if (ctrl != (mLastModifiers & Qt::ControlModifier))
+ mExpandFromCenter = ctrl;
+
+ tilePositionChanged(tilePosition());
+ updateStatusInfo();
+
+ } else {
+ AbstractTileSelectionTool::modifiersChanged(modifiers);
+ }
+
+ mLastModifiers = modifiers;
+}
+
void TileSelectionTool::languageChanged()
{
setName(tr("Rectangular Select"));
@@ -151,15 +183,29 @@ void TileSelectionTool::languageChanged()
QRect TileSelectionTool::selectedArea() const
{
+ QPoint startPos = mSelectionStart;
+ QPoint endPos = tilePosition();
+ QPoint delta = endPos - startPos;
+
+ if (mForceSquare) {
+ const int size = qMax(qAbs(delta.x()), qAbs(delta.y()));
+ delta.setX((delta.x() < 0) ? -size : size);
+ delta.setY((delta.y() < 0) ? -size : size);
+ endPos = startPos + delta;
+ }
+
+ if (mExpandFromCenter)
+ startPos -= delta;
+
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- QRect area = QRect(mSelectionStart, tilePosition()).normalized();
+ QRect area = QRect(startPos, endPos).normalized();
if (area.width() == 0)
area.adjust(-1, 0, 1, 0);
if (area.height() == 0)
area.adjust(0, -1, 0, 1);
return area;
#else
- return QRect::span(mSelectionStart, tilePosition());
+ return QRect::span(startPos, endPos);
#endif
}
diff --git a/src/tiled/tileselectiontool.h b/src/tiled/tileselectiontool.h
index e65ec0e378..0103c9692b 100644
--- a/src/tiled/tileselectiontool.h
+++ b/src/tiled/tileselectiontool.h
@@ -35,6 +35,8 @@ class TileSelectionTool : public AbstractTileSelectionTool
void mousePressed(QGraphicsSceneMouseEvent *event) override;
void mouseReleased(QGraphicsSceneMouseEvent *event) override;
+ void modifiersChanged(Qt::KeyboardModifiers modifiers) override;
+
void languageChanged() override;
protected:
@@ -49,8 +51,11 @@ class TileSelectionTool : public AbstractTileSelectionTool
QPoint mMouseScreenStart;
QPoint mSelectionStart;
- bool mMouseDown;
- bool mSelecting;
+ bool mMouseDown = false;
+ bool mSelecting = false;
+ bool mForceSquare = false;
+ bool mExpandFromCenter = false;
+ Qt::KeyboardModifiers mLastModifiers = Qt::NoModifier;
};
} // namespace Tiled