Skip to content

Commit

Permalink
Scripting: Added ability to set the size of a map
Browse files Browse the repository at this point in the history
Also refactored the EditableAsset and EditableMap a little, moving the
undo stack back into Document and the 'resize' method back into
MapDocument. This is to keep the "Editable" wrappers as small as
possible since they are only meant to be used from the script.

Issue #949
  • Loading branch information
bjorn committed Oct 2, 2019
1 parent 4dde642 commit 5ec163d
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 135 deletions.
44 changes: 36 additions & 8 deletions docs/reference/scripting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,9 @@ Properties
.. csv-table::
:widths: 1, 2

**width** : int |ro|, Width of the map in tiles (only relevant for non-infinite maps). Use :ref:`resize <script-map-resize>` to change it.
**height** : int |ro|, Height of the map in tiles (only relevant for non-infinite maps). Use :ref:`resize <script-map-resize>` to change it.
**size** : size |ro|, Size of the map in tiles (only relevant for non-infinite maps). Use :ref:`resize <script-map-resize>` to change it.
**width** : int, Width of the map in tiles (only relevant for non-infinite maps).
**height** : int, Height of the map in tiles (only relevant for non-infinite maps).
**size** : :ref:`script-size` |ro|, Size of the map in tiles (only relevant for non-infinite maps).
**tileWidth** : int, Tile width (used by tile layers).
**tileHeight**: int, Tile height (used by tile layers).
**infinite** : bool, Whether this map is infinite.
Expand Down Expand Up @@ -642,6 +642,13 @@ Functions
new TileMap()
Constructs a new map.

.. _script-map-setSize:

TileMap.setSize(width : int, height : int) : void
Sets the size of the map in tiles. This does not affect the contents of the map.

See also :ref:`resize <script-map-resize>`.

.. _script-map-layerAt:

TileMap.layerAt(index : int) : :ref:`script-layer`
Expand Down Expand Up @@ -714,10 +721,16 @@ TileMap.merge(map : :ref:`script-map` [, canJoin : bool = false]) : void
the undo stack when possible. Useful for reducing the amount of undo
commands.

*This operation can currently only be applied to maps loaded from a file.*

.. _script-map-resize:

TileMap.resize(size : size [, offset : :ref:`script-point` [, removeObjects : bool = false]]) : void
Resizes the map to the given size, optionally applying an offset (in tiles)
TileMap.resize(size : :ref:`script-size` [, offset : :ref:`script-point` [, removeObjects : bool = false]]) : void
Resizes the map to the given size, optionally applying an offset (in tiles).

*This operation can currently only be applied to maps loaded from a file.*

See also :ref:`setSize <script-map-setSize>`.

.. _script-layer:

Expand Down Expand Up @@ -759,7 +772,7 @@ Properties

**width** : int |ro|, Width of the layer in tiles (only relevant for non-infinite maps).
**height** : int |ro|, Height of the layer in tiles (only relevant for non-infinite maps).
**size** : size |ro|, Size of the layer in tiles (has ``width`` and ``height`` members) (only relevant for non-infinite maps).
**size** : :ref:`script-size` |ro|, Size of the layer in tiles (has ``width`` and ``height`` members) (only relevant for non-infinite maps).

Functions
~~~~~~~~~
Expand Down Expand Up @@ -1310,12 +1323,12 @@ A cell on a :ref:`script-tilelayer`.
.. csv-table::
:widths: 1, 2

**tileId** : int, The local tile ID of the tile, or -1 if the cell is empty.
**tileId** : int, "The local tile ID of the tile, or -1 if the cell is empty."
**empty** : bool, Whether the cell is empty.
**flippedHorizontally** : bool, Whether the tile is flipped horizontally.
**flippedVertically** : bool, Whether the tile is flipped vertically.
**flippedAntiDiagonally** : bool, Whether the tile is flipped anti-diagonally.
**rotatedHexagonal120** : bool, "Whether the tile is rotated by 120 degrees (for hexagonal maps, the anti-diagonal flip is interpreted as a 60-degree rotation)".
**rotatedHexagonal120** : bool, "Whether the tile is rotated by 120 degrees (for hexagonal maps, the anti-diagonal flip is interpreted as a 60-degree rotation)."

.. _script-frames:

Expand Down Expand Up @@ -1375,6 +1388,21 @@ point
**x** : number, X coordinate of the point.
**y** : number, Y coordinate of the point.

.. _script-size:

size
~~~~

``Qt.size(width, height)`` can be used to create a size object.

**Properties**:

.. csv-table::
:widths: 1, 2

**width** : number, Width.
**height** : number, Height.

.. _script-polygon:

Polygon
Expand Down
14 changes: 4 additions & 10 deletions src/tiled/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ Document::Document(DocumentType type, const QString &fileName,
, mType(type)
, mFileName(fileName)
, mCanonicalFilePath(QFileInfo(mFileName).canonicalFilePath())
, mUndoStack(new QUndoStack(this))
{
if (!mCanonicalFilePath.isEmpty())
sDocumentInstances.insert(mCanonicalFilePath, this);

connect(mUndoStack, &QUndoStack::cleanChanged, this, &Document::modifiedChanged);
}

Document::~Document()
Expand All @@ -52,15 +55,6 @@ Document::~Document()
}
}

/**
* Returns the undo stack of this document. Should be used to push any commands
* on that modify the document.
*/
QUndoStack *Document::undoStack()
{
return editable()->undoStack();
}

void Document::setFileName(const QString &fileName)
{
if (mFileName == fileName)
Expand Down Expand Up @@ -104,7 +98,7 @@ void Document::checkFilePathProperties(const Object *object) const
*/
bool Document::isModified() const
{
return mEditable && mEditable->isModified();
return !undoStack()->isClean();
}

void Document::setCurrentObject(Object *object)
Expand Down
16 changes: 14 additions & 2 deletions src/tiled/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Document : public QObject,
Q_OBJECT

Q_PROPERTY(QString fileName READ fileName NOTIFY fileNameChanged)
Q_PROPERTY(bool modified READ isModified)
Q_PROPERTY(bool modified READ isModified NOTIFY modifiedChanged)

public:
enum DocumentType {
Expand Down Expand Up @@ -91,7 +91,7 @@ class Document : public QObject,

QDateTime lastSaved() const { return mLastSaved; }

QUndoStack *undoStack();
QUndoStack *undoStack() const;
bool isModified() const;

Q_INVOKABLE virtual Tiled::EditableAsset *editable() = 0;
Expand Down Expand Up @@ -127,6 +127,7 @@ class Document : public QObject,

void fileNameChanged(const QString &fileName,
const QString &oldFileName);
void modifiedChanged();

void currentObjectChanged(Object *object);

Expand Down Expand Up @@ -159,6 +160,8 @@ class Document : public QObject,
QString mFileName;
QString mCanonicalFilePath;

QUndoStack * const mUndoStack;

bool mChangedOnDisk = false;
bool mIgnoreBrokenLinks = false;

Expand All @@ -176,6 +179,15 @@ inline QString Document::canonicalFilePath() const
return mCanonicalFilePath;
}

/**
* Returns the undo stack of this document. Should be used to push any commands
* on that modify the document.
*/
inline QUndoStack *Document::undoStack() const
{
return mUndoStack;
}

inline bool Document::ignoreBrokenLinks() const
{
return mIgnoreBrokenLinks;
Expand Down
2 changes: 1 addition & 1 deletion src/tiled/documentmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ void DocumentManager::addDocument(const DocumentPtr &document)
mTabBar->setTabToolTip(documentIndex, document->fileName());

connect(documentPtr, &Document::fileNameChanged, this, &DocumentManager::fileNameChanged);
connect(document->editable(), &EditableAsset::modifiedChanged, this, [=] { updateDocumentTab(documentPtr); });
connect(documentPtr, &Document::modifiedChanged, this, [=] { updateDocumentTab(documentPtr); });
connect(documentPtr, &Document::saved, this, &DocumentManager::onDocumentSaved);

if (auto *mapDocument = qobject_cast<MapDocument*>(documentPtr)) {
Expand Down
11 changes: 9 additions & 2 deletions src/tiled/editableasset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ namespace Tiled {
EditableAsset::EditableAsset(Document *document, Object *object, QObject *parent)
: EditableObject(this, object, parent)
, mDocument(document)
, mUndoStack(new QUndoStack(this))
{
connect(mUndoStack, &QUndoStack::cleanChanged, this, &EditableAsset::modifiedChanged);
if (document) {
connect(document, &Document::modifiedChanged,
this, &EditableAsset::modifiedChanged);
}
}

QString EditableAsset::fileName() const
Expand All @@ -54,6 +56,11 @@ bool EditableAsset::isTileset() const
return qobject_cast<const EditableTileset*>(this) != nullptr;
}

QUndoStack *EditableAsset::undoStack() const
{
return document() ? document()->undoStack() : nullptr;
}

/**
* Returns whether the asset has unsaved changes.
*/
Expand Down
6 changes: 0 additions & 6 deletions src/tiled/editableasset.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,9 @@ public slots:

private:
Document * const mDocument;
QUndoStack * const mUndoStack;
};


inline QUndoStack *EditableAsset::undoStack() const
{
return mUndoStack;
}

inline Document *EditableAsset::document() const
{
return mDocument;
Expand Down
117 changes: 14 additions & 103 deletions src/tiled/editablemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
#include "editablemap.h"

#include "addremovelayer.h"
#include "addremovemapobject.h"
#include "addremovetileset.h"
#include "changeevents.h"
#include "changelayer.h"
#include "changemapproperty.h"
#include "changeselectedarea.h"
#include "editablegrouplayer.h"
Expand All @@ -36,20 +34,17 @@
#include "editableselectedarea.h"
#include "editabletilelayer.h"
#include "grouplayer.h"
#include "movemapobject.h"
#include "imagelayer.h"
#include "mapobject.h"
#include "maprenderer.h"
#include "objectgroup.h"
#include "replacetileset.h"
#include "resizemap.h"
#include "resizetilelayer.h"
#include "scriptmanager.h"
#include "tilelayer.h"
#include "tileset.h"
#include "tilesetdocument.h"

#include <imagelayer.h>
#include <mapobject.h>
#include <maprenderer.h>
#include <objectgroup.h>
#include <tilelayer.h>

#include <QUndoStack>

#include "qtcompat_p.h"
Expand Down Expand Up @@ -329,41 +324,6 @@ void EditableMap::merge(EditableMap *editableMap, bool canJoin)
mapDocument()->paintTileLayers(map, canJoin, &missingTilesets);
}

/**
* Custom intersects check necessary because QRectF::intersects wants a
* non-empty area of overlap, but we should also consider overlap with empty
* area as intersection.
*
* Results for rectangles with negative size are undefined.
*/
static bool intersects(const QRectF &a, const QRectF &b)
{
return a.right() >= b.left() &&
a.bottom() >= b.top() &&
a.left() <= b.right() &&
a.top() <= b.bottom();
}

static bool visibleIn(const QRectF &area, MapObject *object,
const MapRenderer &renderer)
{
QRectF boundingRect = renderer.boundingRect(object);

if (object->rotation() != 0) {
// Rotate around object position
QPointF pos = renderer.pixelToScreenCoords(object->position());
boundingRect.translate(-pos);

QTransform transform;
transform.rotate(object->rotation());
boundingRect = transform.mapRect(boundingRect);

boundingRect.translate(pos);
}

return intersects(area, boundingRect);
}

/**
* Resize this map to the given \a size, while at the same time shifting
* the contents by \a offset. If \a removeObjects is true then all objects
Expand All @@ -382,66 +342,17 @@ void EditableMap::resize(QSize size, QPoint offset, bool removeObjects)
return;
}

const QRegion movedSelection = mapDocument()->selectedArea().translated(offset);
const QRect newArea = QRect(-offset, size);
const QRectF visibleArea = renderer()->boundingRect(newArea);

const QPointF origin = renderer()->tileToPixelCoords(QPointF());
const QPointF newOrigin = renderer()->tileToPixelCoords(-offset);
const QPointF pixelOffset = origin - newOrigin;

// Resize the map and each layer
QUndoCommand *command = new QUndoCommand(tr("Resize Map"));

QList<MapObject *> objectsToRemove;
mapDocument()->resizeMap(size, offset, removeObjects);
}

LayerIterator iterator(map());
while (Layer *layer = iterator.next()) {
switch (layer->layerType()) {
case Layer::TileLayerType: {
TileLayer *tileLayer = static_cast<TileLayer*>(layer);
new ResizeTileLayer(mapDocument(), tileLayer, size, offset, command);
break;
}
case Layer::ObjectGroupType: {
ObjectGroup *objectGroup = static_cast<ObjectGroup*>(layer);

for (MapObject *o : objectGroup->objects()) {
if (removeObjects && !visibleIn(visibleArea, o, *renderer())) {
// Remove objects that will fall outside of the map
objectsToRemove.append(o);
} else {
QPointF oldPos = o->position();
QPointF newPos = oldPos + pixelOffset;
new MoveMapObject(mapDocument(), o, newPos, oldPos, command);
}
}
break;
}
case Layer::ImageLayerType: {
// Adjust image layer by changing its offset
auto imageLayer = static_cast<ImageLayer*>(layer);
new SetLayerOffset(mapDocument(), layer,
imageLayer->offset() + pixelOffset,
command);
break;
}
case Layer::GroupLayerType: {
// Recursion handled by LayerIterator
break;
}
}
void EditableMap::setSize(int width, int height)
{
if (auto doc = mapDocument()) {
push(new ResizeMap(doc, QSize(width, height)));
} else if (!isReadOnly()) {
map()->setWidth(width);
map()->setHeight(height);
}

if (!objectsToRemove.isEmpty())
new RemoveMapObjects(mapDocument(), objectsToRemove, command);

new ResizeMap(mapDocument(), size, command);
new ChangeSelectedArea(mapDocument(), movedSelection, command);

push(command);

// TODO: Handle layers that don't match the map size correctly
}

void EditableMap::setTileWidth(int value)
Expand Down
Loading

0 comments on commit 5ec163d

Please sign in to comment.