From 9e174f4ea03fb235be373a47768677f40cca73f2 Mon Sep 17 00:00:00 2001 From: Benjamin Trotter Date: Tue, 4 Jul 2017 12:37:27 -0600 Subject: [PATCH] Initial work on support for Wang Tiles (#1582) --- src/libtiled/libtiled-src.pri | 6 +- src/libtiled/libtiled.qbs | 2 + src/libtiled/mapreader.cpp | 51 +++++ src/libtiled/mapwriter.cpp | 37 +++- src/libtiled/object.h | 2 + src/libtiled/tileset.cpp | 40 ++++ src/libtiled/tileset.h | 25 +++ src/libtiled/wangset.cpp | 304 ++++++++++++++++++++++++++++++ src/libtiled/wangset.h | 266 ++++++++++++++++++++++++++ src/tiled/addremovewangset.cpp | 74 ++++++++ src/tiled/addremovewangset.h | 71 +++++++ src/tiled/changewangsetdata.cpp | 102 ++++++++++ src/tiled/changewangsetdata.h | 92 +++++++++ src/tiled/mapdocument.h | 1 + src/tiled/propertybrowser.cpp | 71 ++++++- src/tiled/propertybrowser.h | 5 + src/tiled/renamewangset.cpp | 54 ++++++ src/tiled/renamewangset.h | 53 ++++++ src/tiled/tiled.pro | 14 ++ src/tiled/tiled.qbs | 14 ++ src/tiled/tilesetdocument.cpp | 12 ++ src/tiled/tilesetdocument.h | 6 + src/tiled/tileseteditor.cpp | 86 ++++++++- src/tiled/tileseteditor.h | 10 + src/tiled/tilesetview.cpp | 26 +++ src/tiled/tilesetview.h | 10 + src/tiled/tilesetwangsetmodel.cpp | 186 ++++++++++++++++++ src/tiled/tilesetwangsetmodel.h | 92 +++++++++ src/tiled/wangdock.cpp | 294 +++++++++++++++++++++++++++++ src/tiled/wangdock.h | 94 +++++++++ src/tiled/wangsetmodel.cpp | 292 ++++++++++++++++++++++++++++ src/tiled/wangsetmodel.h | 85 +++++++++ src/tiled/wangsetview.cpp | 128 +++++++++++++ src/tiled/wangsetview.h | 62 ++++++ 34 files changed, 2661 insertions(+), 6 deletions(-) create mode 100644 src/libtiled/wangset.cpp create mode 100644 src/libtiled/wangset.h create mode 100644 src/tiled/addremovewangset.cpp create mode 100644 src/tiled/addremovewangset.h create mode 100644 src/tiled/changewangsetdata.cpp create mode 100644 src/tiled/changewangsetdata.h create mode 100644 src/tiled/renamewangset.cpp create mode 100644 src/tiled/renamewangset.h create mode 100644 src/tiled/tilesetwangsetmodel.cpp create mode 100644 src/tiled/tilesetwangsetmodel.h create mode 100644 src/tiled/wangdock.cpp create mode 100644 src/tiled/wangdock.h create mode 100644 src/tiled/wangsetmodel.cpp create mode 100644 src/tiled/wangsetmodel.h create mode 100644 src/tiled/wangsetview.cpp create mode 100644 src/tiled/wangsetview.h diff --git a/src/libtiled/libtiled-src.pri b/src/libtiled/libtiled-src.pri index 82c92b928a..7cf9179163 100644 --- a/src/libtiled/libtiled-src.pri +++ b/src/libtiled/libtiled-src.pri @@ -29,7 +29,8 @@ SOURCES += $$PWD/compression.cpp \ $$PWD/tileset.cpp \ $$PWD/tilesetformat.cpp \ $$PWD/tilesetmanager.cpp \ - $$PWD/varianttomapconverter.cpp + $$PWD/varianttomapconverter.cpp \ + $$PWD/wangset.cpp HEADERS += $$PWD/compression.h \ $$PWD/filesystemwatcher.h \ $$PWD/gidmapper.h \ @@ -65,4 +66,5 @@ HEADERS += $$PWD/compression.h \ $$PWD/tileset.h \ $$PWD/tilesetformat.h \ $$PWD/tilesetmanager.h \ - $$PWD/varianttomapconverter.h + $$PWD/varianttomapconverter.h \ + $$PWD/wangset.h diff --git a/src/libtiled/libtiled.qbs b/src/libtiled/libtiled.qbs index 305b85fe97..b02602609e 100644 --- a/src/libtiled/libtiled.qbs +++ b/src/libtiled/libtiled.qbs @@ -99,6 +99,8 @@ DynamicLibrary { "tilesetmanager.h", "varianttomapconverter.cpp", "varianttomapconverter.h", + "wangset.cpp", + "wangset.h", ] Group { diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 4e50bc9404..bbd2a23f5d 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -41,6 +41,7 @@ #include "tilelayer.h" #include "tilesetmanager.h" #include "terrain.h" +#include "wangset.h" #include #include @@ -84,6 +85,7 @@ class MapReaderPrivate void readTilesetGrid(Tileset &tileset); void readTilesetImage(Tileset &tileset); void readTilesetTerrainTypes(Tileset &tileset); + void readTilesetWangSets(Tileset &tileset); ImageReference readImage(); Layer *tryReadLayer(); @@ -353,6 +355,8 @@ SharedTileset MapReaderPrivate::readTileset() } } else if (xml.name() == QLatin1String("terraintypes")) { readTilesetTerrainTypes(*tileset); + } else if (xml.name() == QLatin1String("wangsets")) { + readTilesetWangSets(*tileset); } else { readUnknownElement(); } @@ -564,6 +568,53 @@ void MapReaderPrivate::readTilesetTerrainTypes(Tileset &tileset) } } +void MapReaderPrivate::readTilesetWangSets(Tileset &tileset) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("wangsets")); + + while (xml.readNextStartElement()) { + if (xml.name() == QLatin1String("wangset")) { + const QXmlStreamAttributes atts = xml.attributes(); + QString name = atts.value(QLatin1String("name")).toString(); + int edges = atts.value(QLatin1String("edges")).toInt(); + int corners = atts.value(QLatin1String("corners")).toInt(); + int tile = atts.value(QLatin1String("tile")).toInt(); + + WangSet *wangSet = new WangSet(&tileset, edges, corners, name, tile); + + tileset.addWangSet(wangSet); + + while (xml.readNextStartElement()) { + if (xml.name() == QLatin1String("properties")) { + wangSet->mergeProperties(readProperties()); + } else if (xml.name() == QLatin1String("wangtile")) { + const QXmlStreamAttributes tileAtts = xml.attributes(); + int tileId = tileAtts.value(QLatin1String("tileid")).toInt(); + unsigned wangId = tileAtts.value(QLatin1String("wangid")).toUInt(); + bool fH = tileAtts.value(QLatin1String("hflip")).toInt(); + bool fV = tileAtts.value(QLatin1String("vflip")).toInt(); + bool fA = tileAtts.value(QLatin1String("dflip")).toInt(); + + Tile *tile = tileset.findOrCreateTile(tileId); + + WangTile wangTile(tile, wangId); + wangTile.setFlippedHorizontally(fH); + wangTile.setFlippedVertically(fV); + wangTile.setFlippedAntiDiagonally(fA); + + wangSet->addWangTile(wangTile); + + xml.skipCurrentElement(); + } else { + readUnknownElement(); + } + } + } else { + readUnknownElement(); + } + } +} + static void readLayerAttributes(Layer &layer, const QXmlStreamAttributes &atts) { diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index b7c7327d3e..4a68d9275d 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -42,6 +42,7 @@ #include "tilelayer.h" #include "tileset.h" #include "terrain.h" +#include "wangset.h" #include #include @@ -421,11 +422,45 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset, } w.writeEndElement(); // } - w.writeEndElement(); // } } + // Write the wangsets + if (tileset.wangSetCount() > 0) { + w.writeStartElement(QLatin1String("wangsets")); + for (const WangSet *ws : tileset.wangSets()) { + w.writeStartElement(QLatin1String("wangset")); + + w.writeAttribute(QLatin1String("name"), ws->name()); + w.writeAttribute(QLatin1String("edges"), QString::number(ws->edgeColors())); + w.writeAttribute(QLatin1String("corners"), QString::number(ws->cornerColors())); + w.writeAttribute(QLatin1String("tile"), QString::number(ws->imageTileId())); + + for (const WangTile &wangTile : ws->wangTiles()) { + w.writeStartElement(QLatin1String("wangtile")); + w.writeAttribute(QLatin1String("tileid"), QString::number(wangTile.tile()->id())); + w.writeAttribute(QLatin1String("wangid"), QString::number(wangTile.wangId())); + + if (wangTile.flippedHorizontally()) + w.writeAttribute(QLatin1String("hflip"), QString::number(1)); + + if (wangTile.flippedVertically()) + w.writeAttribute(QLatin1String("vflip"), QString::number(1)); + + if (wangTile.flippedAntiDiagonally()) + w.writeAttribute(QLatin1String("dflip"), QString::number(1)); + + w.writeEndElement(); // + } + + writeProperties(w, ws->properties()); + + w.writeEndElement(); // + } + w.writeEndElement(); // + } + w.writeEndElement(); } diff --git a/src/libtiled/object.h b/src/libtiled/object.h index c92b2fadb0..4d1f7b6d9a 100644 --- a/src/libtiled/object.h +++ b/src/libtiled/object.h @@ -44,6 +44,7 @@ class TILEDSHARED_EXPORT Object MapObjectType, MapType, TerrainType, + WangSetType, TilesetType, TileType }; @@ -135,6 +136,7 @@ inline bool Object::isPartOfTileset() const case Object::TilesetType: case Object::TileType: case Object::TerrainType: + case Object::WangSetType: return true; default: return false; diff --git a/src/libtiled/tileset.cpp b/src/libtiled/tileset.cpp index 25a06732d7..02957eedfd 100644 --- a/src/libtiled/tileset.cpp +++ b/src/libtiled/tileset.cpp @@ -32,6 +32,7 @@ #include "terrain.h" #include "tile.h" #include "tilesetformat.h" +#include "wangset.h" #include @@ -62,6 +63,7 @@ Tileset::~Tileset() { qDeleteAll(mTiles); qDeleteAll(mTerrainTypes); + qDeleteAll(mWangSets); } void Tileset::setFormat(TilesetFormat *format) @@ -505,6 +507,35 @@ void Tileset::recalculateTerrainDistances() } while (bNewConnections); } +void Tileset::addWangSet(WangSet *wangSet) +{ + Q_ASSERT(wangSet->tileset() == this); + + mWangSets.append(wangSet); +} + +/** + * @brief Tileset::insertWangSet Adds a wangSet. + * @param wangSet A pointer to the wangset to add. + */ +void Tileset::insertWangSet(int index, WangSet *wangSet) +{ + Q_ASSERT(wangSet->tileset() == this); + + mWangSets.insert(index, wangSet); +} + +/** + * @brief Tileset::takeWangSetAt Removes the wangset at a given index + * And returns it to the caller. + * @param index Index to take at. + * @return + */ +WangSet *Tileset::takeWangSetAt(int index) +{ + return mWangSets.takeAt(index); +} + /** * Adds a new tile to the end of the tileset. */ @@ -617,6 +648,7 @@ void Tileset::swap(Tileset &other) std::swap(mTiles, other.mTiles); std::swap(mNextTileId, other.mNextTileId); std::swap(mTerrainTypes, other.mTerrainTypes); + std::swap(mWangSets, other.mWangSets); std::swap(mTerrainDistancesDirty, other.mTerrainDistancesDirty); std::swap(mLoaded, other.mLoaded); std::swap(mBackgroundColor, other.mBackgroundColor); @@ -629,11 +661,15 @@ void Tileset::swap(Tileset &other) tile->mTileset = this; for (auto terrain : mTerrainTypes) terrain->mTileset = this; + for (auto wangSet : mWangSets) + wangSet->setTileset(this); for (auto tile : other.mTiles) tile->mTileset = &other; for (auto terrain : other.mTerrainTypes) terrain->mTileset = &other; + for (auto wangSet : other.mWangSets) + wangSet->setTileset(&other); } SharedTileset Tileset::clone() const @@ -669,6 +705,10 @@ SharedTileset Tileset::clone() const for (Terrain *terrain : mTerrainTypes) c->mTerrainTypes.append(terrain->clone(c.data())); + c->mWangSets.reserve(mWangSets.size()); + for (WangSet *wangSet : mWangSets) + c->mWangSets.append(wangSet->clone(c.data())); + return c; } diff --git a/src/libtiled/tileset.h b/src/libtiled/tileset.h index ebb03c27df..a20ec834c2 100644 --- a/src/libtiled/tileset.h +++ b/src/libtiled/tileset.h @@ -49,6 +49,7 @@ class Tile; class Tileset; class TilesetFormat; class Terrain; +class WangSet; typedef QSharedPointer SharedTileset; @@ -184,6 +185,14 @@ class TILEDSHARED_EXPORT Tileset : public Object int terrainTransitionPenalty(int terrainType0, int terrainType1) const; + const QList &wangSets() const; + int wangSetCount() const; + WangSet *wangSet(int index) const; + + void addWangSet(WangSet *wangSet); + void insertWangSet(int index, WangSet *wangSet); + WangSet *takeWangSetAt(int index); + Tile *addTile(const QPixmap &image, const QString &source = QString()); void addTiles(const QList &tiles); void removeTiles(const QList &tiles); @@ -246,6 +255,7 @@ class TILEDSHARED_EXPORT Tileset : public Object QMap mTiles; int mNextTileId; QList mTerrainTypes; + QList mWangSets; bool mTerrainDistancesDirty; bool mLoaded; QColor mBackgroundColor; @@ -553,6 +563,21 @@ inline Terrain *Tileset::terrain(int index) const return index >= 0 ? mTerrainTypes[index] : nullptr; } +inline const QList &Tileset::wangSets() const +{ + return mWangSets; +} + +inline int Tileset::wangSetCount() const +{ + return mWangSets.size(); +} + +inline WangSet *Tileset::wangSet(int index) const +{ + return index >= 0 ? mWangSets[index] : nullptr; +} + /** * Sets the next id to be used for tiles in this tileset. */ diff --git a/src/libtiled/wangset.cpp b/src/libtiled/wangset.cpp new file mode 100644 index 0000000000..71092fd7fd --- /dev/null +++ b/src/libtiled/wangset.cpp @@ -0,0 +1,304 @@ +/* + * wangset.cpp + * Copyright 2017, Benjamin Trotter + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tilelayer.h" +#include "wangset.h" + +#include + +using namespace Tiled; + +unsigned cellToTileInfo(const Cell &cell) +{ + return cell.tileId() + | (cell.flippedHorizontally() << 29) + | (cell.flippedVertically() << 28) + | (cell.flippedAntiDiagonally() << 27); +} + +unsigned wangTileToTileInfo(const WangTile &wangTile) +{ + return wangTile.tile()->id() + | (wangTile.flippedHorizontally() << 29) + | (wangTile.flippedVertically() << 28) + | (wangTile.flippedAntiDiagonally() << 27); +} + +void WangTile::translate(int map[]) +{ + int mask = (mFlippedHorizontally << 2) + | (mFlippedVertically << 1) + | (mFlippedAntiDiagonally); + + mask = map[mask]; + + mFlippedHorizontally = mask & 4; + mFlippedVertically = mask & 2; + mFlippedAntiDiagonally = mask & 1; +} + +void WangTile::rotateRight() +{ + int map[] = {5, 4, 1, 0, 7, 6, 3, 2}; + + translate(map); +} + +void WangTile::rotateLeft() +{ + int map[] = {3, 2, 7, 6, 1, 0, 5, 4}; + + translate(map); +} + +void WangTile::flipHorizontally() +{ + int map[] = {4, 3, 6, 1, 0, 7, 2, 5}; + + translate(map); +} + +void WangTile::flipVertically() +{ + int map[] = {2, 5, 0, 7, 6, 1, 4, 3}; + + translate(map); +} + +Cell WangTile::makeCell() const +{ + if (!mTile) + return Cell(); + + Cell cell(mTile); + cell.setFlippedHorizontally(mFlippedHorizontally); + cell.setFlippedVertically(mFlippedVertically); + cell.setFlippedAntiDiagonally(mFlippedAntiDiagonally); + + return cell; +} + +WangSet::WangSet(Tileset *tileset, + int edgeColors, + int cornerColors, + QString name, + int imageTileId): + Object(Object::WangSetType), + mTileset(tileset), + mName(std::move(name)), + mImageTileId(imageTileId), + mEdgeColors(edgeColors), + mCornerColors(cornerColors) +{ +} + +void WangSet::addTile(Tile *tile, WangId wangId) +{ + Q_ASSERT(tile->tileset() == mTileset); + Q_ASSERT(wangIdIsValid(wangId)); + + mWangIdToWangTile.insert(wangId, WangTile(tile, wangId)); + mTileInfoToWangId.insert(tile->id(), wangId); +} + +void WangSet::addCell(const Cell &cell, WangId wangId) +{ + Q_ASSERT(cell.tileset() == mTileset); + Q_ASSERT(wangIdIsValid(wangId)); + + mWangIdToWangTile.insert(wangId, WangTile(cell, wangId)); + mTileInfoToWangId.insert(cellToTileInfo(cell), wangId); +} + +void WangSet::addWangTile(const WangTile &wangTile) +{ + Q_ASSERT(wangTile.tile()->tileset() == mTileset); + Q_ASSERT(wangIdIsValid(wangTile.wangId())); + + mWangIdToWangTile.insert(wangTile.wangId(), wangTile); + mTileInfoToWangId.insert(wangTileToTileInfo(wangTile), wangTile.wangId()); +} + +WangTile WangSet::findMatchingWangTile(WangId wangId) const +{ + auto potentials = findMatchingWangTiles(wangId); + + if (potentials.length() > 0) + return potentials[qrand() % potentials.length()]; + else + return WangTile(); +} + +struct WangWildCard +{ + int index, colorCount; +}; + +QList WangSet::findMatchingWangTiles(WangId wangId) const +{ + QList list; + + //Stores the space of a wild card, followed by how many colors that space can have. + QVector wildCards; + + if (mEdgeColors > 0) { + for (int i = 0; i < 4; ++i) { + if (!wangId.edgeColor(i)) { + WangWildCard w; + w.index = i * 8; + w.colorCount = mEdgeColors; + + wildCards.append(w); + } + } + } + + if (mCornerColors > 0) { + for (int i = 0; i < 4; ++i) { + if (!wangId.cornerColor(i)) { + WangWildCard w; + w.index = i * 8 + 4; + w.colorCount = mCornerColors; + + wildCards.append(w); + } + } + } + + if (wildCards.isEmpty()) { + list.append(mWangIdToWangTile.values(wangId)); + } else { + QStack stack; + + stack += wildCards; + + int max = wildCards.size(); + + while (!stack.isEmpty()) { + if (stack.size() == max) { + int idVariation = 0; + + for (int i = 0; i < max; ++i) + idVariation |= stack[i].colorCount << stack[i].index; + + list.append(mWangIdToWangTile.values(idVariation | wangId)); + + WangWildCard top = stack.pop(); + top.colorCount -= 1; + if (top.colorCount > 0) + stack.push(top); + } else { + WangWildCard top = stack.pop(); + top.colorCount -= 1; + if (top.colorCount > 0) { + stack.push(top); + + for (int i = stack.size(); i < max; ++i) + stack.push(wildCards[i]); + } + } + } + } + + return list; +} + +Cell WangSet::findMatchingCell(WangId wangId) const +{ + WangTile wangTile = findMatchingWangTile(wangId); + + return wangTile.makeCell(); +} + +WangId WangSet::wangIdFromSurrounding(WangId surroundingWangIds[]) const +{ + unsigned id = 0; + + if (mEdgeColors > 0) { + for (int i = 0; i < 4; ++i) + id |= (surroundingWangIds[i*2].edgeColor((2 + i) % 4)) << (i*8); + } + + if (mCornerColors > 0) { + for (int i = 0; i < 4; ++i) { + int color = surroundingWangIds[i*2 + 1].cornerColor((2 + i) % 4); + + if (!color) + color = surroundingWangIds[i*2].cornerColor((1 + i) % 4); + + if (!color) + color = surroundingWangIds[(i*2 + 2) % 8].cornerColor((3 + i) % 4); + + id |= color << (4 + i*8); + } + } + + return id; +} + +WangId WangSet::wangIdFromSurrounding(const Cell surroundingCells[]) const +{ + WangId wangIds[8]; + + for (int i = 0; i < 8; ++i) + wangIds[i] = wangIdOfCell(surroundingCells[i]); + + WangId wangId = wangIdFromSurrounding(wangIds); + + return wangId; +} + +WangId WangSet::wangIdOfTile(const Tile *tile) const +{ + return mTileInfoToWangId.value(tile->id()); +} + +WangId WangSet::wangIdOfCell(const Cell &cell) const +{ + return mTileInfoToWangId.value(cellToTileInfo(cell)); +} + +bool WangSet::wangIdIsValid(WangId wangId) const +{ + for (int i = 0; i < 4; ++i) { + if (wangId.edgeColor(i) > mEdgeColors + || wangId.cornerColor(i) > mCornerColors) + return false; + } + + return true; +} + +WangSet *WangSet::clone(Tileset *tileset) const +{ + WangSet *c = new WangSet(tileset, mEdgeColors, mCornerColors, mName, mImageTileId); + + c->mWangIdToWangTile = mWangIdToWangTile; + c->mTileInfoToWangId = mTileInfoToWangId; + + return c; +} diff --git a/src/libtiled/wangset.h b/src/libtiled/wangset.h new file mode 100644 index 0000000000..df9907fb37 --- /dev/null +++ b/src/libtiled/wangset.h @@ -0,0 +1,266 @@ +/* + * wangset.h + * Copyright 2017, Benjamin Trotter + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "tile.h" +#include "tileset.h" +#include "tilelayer.h" + +#include +#include +#include +#include + +namespace Tiled { + +class WangId +{ +public: + + WangId() : mId(0) {} + WangId(unsigned id) : mId(id) {} + + operator unsigned() const { return mId; } + inline void setId(unsigned id) { mId = id; } + + /* These return the color of the edge/corner of the wangId. + * 0 being the top right corner, or top edge + * */ + int edgeColor(int index) const; + int cornerColor(int index) const; + + /* Rotates the wang Id clockwise by (90 * rotations) degrees. + * Meaning with one rotation, the top edge becomes the right edge, + * and the top right corner, becomes the top bottom. + * */ + void rotate(int rotations); + + /* Flips the wang Id horizontally + * */ + void flipHorizontally(); + + /* Flips the wang Id vertically + * */ + void flipVertically(); + +private: + unsigned mId; +}; + +inline int WangId::edgeColor(int index) const +{ + int shift = (index * 8); + + int color = (mId >> shift) & 0xf; + + return color; +} + +inline int WangId::cornerColor(int index) const +{ + int shift = (index * 8) + 4; + + int color = (mId >> shift) & 0xf; + + return color; +} + +inline void WangId::rotate(int rotations) +{ + if (rotations < 0) + rotations = 4 + (rotations % 4); + else + rotations %= 4; + + unsigned rotated = mId << rotations*8; + rotated = rotated | (mId >> ((4 - rotations) * 8)); + + mId = rotated; +} + +//Class for holding info about rotation and flipping. +class TILEDSHARED_EXPORT WangTile +{ +public: + WangTile(): + mTile(nullptr), + mWangId(0), + mFlippedHorizontally(false), + mFlippedVertically(false), + mFlippedAntiDiagonally(false) + {} + + WangTile(Tile *tile, + WangId wangId): + mTile(tile), + mWangId(wangId), + mFlippedHorizontally(false), + mFlippedVertically(false), + mFlippedAntiDiagonally(false) + {} + + WangTile(const Cell &cell, WangId wangId): + mTile(cell.tile()), + mWangId(wangId), + mFlippedHorizontally(cell.flippedHorizontally()), + mFlippedVertically(cell.flippedVertically()), + mFlippedAntiDiagonally(cell.flippedAntiDiagonally()) + {} + + Tile *tile() const { return mTile; } + + WangId wangId() const { return mWangId; } + + bool flippedHorizontally() const { return mFlippedHorizontally; } + bool flippedVertically() const { return mFlippedVertically; } + bool flippedAntiDiagonally() const { return mFlippedAntiDiagonally; } + + void setFlippedHorizontally(bool b) { mFlippedHorizontally = b; } + void setFlippedVertically(bool b) { mFlippedVertically = b; } + void setFlippedAntiDiagonally(bool b) { mFlippedAntiDiagonally = b; } + + void rotateRight(); + void rotateLeft(); + void flipHorizontally(); + void flipVertically(); + + Cell makeCell() const; + +private: + //performs a translation (either flipping or rotating) based on a one to one + //map of size 8 (from 0 - 7) + void translate(int map[]); + + Tile *mTile; + WangId mWangId; + bool mFlippedHorizontally; + bool mFlippedVertically; + bool mFlippedAntiDiagonally; +}; + +/** + * Represents a wang set. + */ +class TILEDSHARED_EXPORT WangSet : public Object +{ +public: + WangSet(Tileset *tileset, + int edgeColors, + int cornerColors, + QString name, + int imageTileId); + + Tileset *tileset() const { return mTileset; } + void setTileset(Tileset *tileset) { mTileset = tileset; } + + QString name() const { return mName; } + void setName(const QString &name) { mName = name; } + + int imageTileId() const { return mImageTileId; } + void setImageTileId(int imageTileId) { mImageTileId = imageTileId; } + + Tile *imageTile() const { return mTileset->findTile(mImageTileId); } + + int edgeColors() const { return mEdgeColors; } + int cornerColors() const { return mCornerColors; } + void setEdgeColors(int n) { mEdgeColors = n; } + void setCornerColors(int n) { mCornerColors = n; } + + /* Adds a tile to the wang set with a given wangId + * */ + void addTile(Tile *tile, WangId wangId); + + void addCell(const Cell &cell, WangId wangId); + + void addWangTile(const WangTile &wangTile); + + /* Finds a tile whos WangId matches with the one provided, + * where zeros in the id are treated as wild cards, and can be + * any color. + * */ + WangTile findMatchingWangTile(WangId wangId) const; + + /* Finds all the tiles which match the given wangId, + * where zeros in the id are treated as wild cards, and can be + * any color. + * */ + QList findMatchingWangTiles(WangId wangId) const; + + /* Finds a wangTile mathcing the provided wangId, and creates, + * and returns a Cell matching the WangTile. + * */ + Cell findMatchingCell(WangId wangId) const; + + QList wangTiles() const { return mWangIdToWangTile.values(); } + + /* Returns a wangId matching that of the provided surrounding wangIds. + * This is based off a provided array, {a, b, c, d, e, f, g, h}, + * which corrisponds to h|a|b + * g|X|c + * f|e|d + * */ + WangId wangIdFromSurrounding(WangId surroundingWangIds[]) const; + + /* Returns a wangId matching that of the provided surrounding tiles. + * This is based off a provided array, {a, b, c, d, e, f, g, h}, + * which corrisponds to h|a|b + * g|X|c + * f|e|d + * */ + WangId wangIdFromSurrounding(const Cell surroundingCells[]) const; + + /* Returns the wangId of a given Tile. + * */ + WangId wangIdOfTile(const Tile *tile) const; + + WangId wangIdOfCell(const Cell &cell) const; + + /* Returns whether or not the given wangId is valid in the contex of the current wangSet + * */ + bool wangIdIsValid(WangId wangId) const; + + /* Returns a clone of this wangset + * */ + WangSet *clone(Tileset *tileset) const; + +private: + Tileset *mTileset; + QString mName; + int mImageTileId; + int mEdgeColors; + int mCornerColors; + QMultiHash mWangIdToWangTile; + + //Tile info being the tileId, with the last three bits (32, 31, 30) + //being info on flip (horizontal, vertical, and antidiagonal) + QHash mTileInfoToWangId; +}; + +} // namespace Tiled + +Q_DECLARE_METATYPE(Tiled::WangSet*) diff --git a/src/tiled/addremovewangset.cpp b/src/tiled/addremovewangset.cpp new file mode 100644 index 0000000000..d9a190a789 --- /dev/null +++ b/src/tiled/addremovewangset.cpp @@ -0,0 +1,74 @@ +/* + * addremovewangset.cpp + * Copyright 2017, Benjamin Trotter + * + * 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 "addremovewangset.h" + +#include "wangset.h" +#include "tileset.h" +#include "tilesetdocument.h" +#include "tilesetwangsetmodel.h" + +#include + +using namespace Tiled; +using namespace Internal; + +AddRemoveWangSet::AddRemoveWangSet(TilesetDocument *tilesetDocument, + int index, + WangSet *wangSet) + : mTilesetDocument(tilesetDocument) + , mIndex(index) + , mWangSet(wangSet) +{ +} + +AddRemoveWangSet::~AddRemoveWangSet() +{ + delete mWangSet; +} + +void AddRemoveWangSet::removeWangSet() +{ + Q_ASSERT(!mWangSet); + mWangSet = mTilesetDocument->wangSetModel()->takeWangSetAt(mIndex); +} + +void AddRemoveWangSet::addWangSet() +{ + Q_ASSERT(mWangSet); + mTilesetDocument->wangSetModel()->insertWangSet(mIndex, mWangSet); + mWangSet = nullptr; +} + +AddWangSet::AddWangSet(TilesetDocument *tilesetDocument, WangSet *wangSet) + : AddRemoveWangSet(tilesetDocument, + wangSet->tileset()->wangSetCount(), + wangSet) +{ + setText(QCoreApplication::translate("Undo Commands", "Add Wang Set")); +} + +RemoveWangSet::RemoveWangSet(TilesetDocument *tilesetDocument, WangSet *wangset) + : AddRemoveWangSet(tilesetDocument, + tilesetDocument->wangSetModel()->index(wangset).row(), + nullptr) +{ + setText(QCoreApplication::translate("Undo Commands", "Remove Wang Set")); +} diff --git a/src/tiled/addremovewangset.h b/src/tiled/addremovewangset.h new file mode 100644 index 0000000000..e8bca08591 --- /dev/null +++ b/src/tiled/addremovewangset.h @@ -0,0 +1,71 @@ +/* + * addremovewangset.h + * Copyright 2017, Benjamin Trotter + * + * 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 . + */ + +#pragma once + +#include + +namespace Tiled { + +class WangSet; +class Tileset; + +namespace Internal { + +class TilesetDocument; + +class AddRemoveWangSet : public QUndoCommand +{ +public: + AddRemoveWangSet(TilesetDocument *tilesetDocument, + int index, + WangSet *wangSet); + ~AddRemoveWangSet(); + +protected: + void addWangSet(); + void removeWangSet(); + +private: + TilesetDocument *mTilesetDocument; + int mIndex; + WangSet *mWangSet; +}; + +class AddWangSet : public AddRemoveWangSet +{ +public: + AddWangSet(TilesetDocument *tilesetDocument, WangSet *wangSet); + + void undo() override { removeWangSet(); } + void redo() override { addWangSet(); } +}; + +class RemoveWangSet : public AddRemoveWangSet +{ +public: + RemoveWangSet(TilesetDocument *tilesetDocument, WangSet *wangset); + + void undo() override { addWangSet(); } + void redo() override { removeWangSet(); } +}; + +} // namespace internal +} // namespace tiled diff --git a/src/tiled/changewangsetdata.cpp b/src/tiled/changewangsetdata.cpp new file mode 100644 index 0000000000..1a7e167e02 --- /dev/null +++ b/src/tiled/changewangsetdata.cpp @@ -0,0 +1,102 @@ +/* + * changewangsetdata.cpp + * Copyright 2017, Benjamin Trotter + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "changewangsetdata.h" + +#include "wangset.h" +#include "tileset.h" +#include "tilesetdocument.h" +#include "tilesetwangsetmodel.h" + +#include + +using namespace Tiled; +using namespace Internal; + +ChangeWangSetEdges::ChangeWangSetEdges(TilesetDocument *tilesetDocument, + int index, + int newValue) + : QUndoCommand(QCoreApplication::translate("Undo Commands", + "Change Wang Set edge count")) + , mWangSetModel(tilesetDocument->wangSetModel()) + , mIndex(index) + , mOldValue(tilesetDocument->tileset()->wangSet(index)->edgeColors()) + , mNewValue(newValue) +{ +} + +void ChangeWangSetEdges::undo() +{ + mWangSetModel->setWangSetEdges(mIndex, mOldValue); +} + +void ChangeWangSetEdges::redo() +{ + mWangSetModel->setWangSetEdges(mIndex, mNewValue); +} + +ChangeWangSetCorners::ChangeWangSetCorners(TilesetDocument *tilesetDocument, + int index, + int newValue) + : QUndoCommand(QCoreApplication::translate("Undo Commands", + "Change Wang Set corner count")) + , mWangSetModel(tilesetDocument->wangSetModel()) + , mIndex(index) + , mOldValue(tilesetDocument->tileset()->wangSet(index)->cornerColors()) + , mNewValue(newValue) +{ +} + +void ChangeWangSetCorners::undo() +{ + mWangSetModel->setWangSetCorners(mIndex, mOldValue); +} + +void ChangeWangSetCorners::redo() +{ + mWangSetModel->setWangSetCorners(mIndex, mNewValue); +} + +SetWangSetImage::SetWangSetImage(TilesetDocument *tilesetDocument, int index, int tileId) + : QUndoCommand(QCoreApplication::translate("Undo Commands", + "Set Wang Set Image")) + , mWangSetModel(tilesetDocument->wangSetModel()) + , mIndex(index) + , mOldImageTileId(tilesetDocument->tileset()->wangSet(index)->imageTileId()) + , mNewImageTileId(tileId) +{ +} + +void SetWangSetImage::undo() +{ + mWangSetModel->setWangSetImage(mIndex, mOldImageTileId); +} + +void SetWangSetImage::redo() +{ + mWangSetModel->setWangSetImage(mIndex, mNewImageTileId); +} diff --git a/src/tiled/changewangsetdata.h b/src/tiled/changewangsetdata.h new file mode 100644 index 0000000000..ce3f70d2e5 --- /dev/null +++ b/src/tiled/changewangsetdata.h @@ -0,0 +1,92 @@ +/* + * changewangsetdata.h + * Copyright 2017, Benjamin Trotter + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace Tiled { + +class Tileset; + +namespace Internal { + +class TilesetDocument; +class TilesetWangSetModel; + +class ChangeWangSetEdges : public QUndoCommand +{ +public: + ChangeWangSetEdges(TilesetDocument *TilesetDocument, + int index, + int newValue); + + void undo() override; + void redo() override; + +private: + TilesetWangSetModel *mWangSetModel; + int mIndex; + int mOldValue; + int mNewValue; +}; + +class ChangeWangSetCorners : public QUndoCommand +{ +public: + ChangeWangSetCorners(TilesetDocument *TilesetDocument, + int index, + int newValue); + + void undo() override; + void redo() override; + +private: + TilesetWangSetModel *mWangSetModel; + int mIndex; + int mOldValue; + int mNewValue; +}; + +class SetWangSetImage : public QUndoCommand +{ +public: + SetWangSetImage(TilesetDocument *tilesetDocument, + int index, + int tileId); + + void undo() override; + void redo() override; +private: + TilesetWangSetModel *mWangSetModel; + int mIndex; + int mOldImageTileId; + int mNewImageTileId; +}; + +} // namespace Internal +} // namespace Tiled diff --git a/src/tiled/mapdocument.h b/src/tiled/mapdocument.h index c2586453a0..eec251b565 100644 --- a/src/tiled/mapdocument.h +++ b/src/tiled/mapdocument.h @@ -45,6 +45,7 @@ class MapRenderer; class MapFormat; class Terrain; class Tile; +class WangSet; namespace Internal { diff --git a/src/tiled/propertybrowser.cpp b/src/tiled/propertybrowser.cpp index 9c84fc0a51..6449b97d53 100644 --- a/src/tiled/propertybrowser.cpp +++ b/src/tiled/propertybrowser.cpp @@ -30,6 +30,7 @@ #include "changetile.h" #include "changetileimagesource.h" #include "changetileprobability.h" +#include "changewangsetdata.h" #include "flipmapobjects.h" #include "imagelayer.h" #include "map.h" @@ -42,6 +43,7 @@ #include "resizemapobject.h" #include "renamelayer.h" #include "renameterrain.h" +#include "renamewangset.h" #include "rotatemapobject.h" #include "terrain.h" #include "tile.h" @@ -51,9 +53,11 @@ #include "tilesetformat.h" #include "tilesetmanager.h" #include "tilesetterrainmodel.h" +#include "tilesetwangsetmodel.h" #include "utils.h" #include "varianteditorfactory.h" #include "variantpropertymanager.h" +#include "wangset.h" #include @@ -145,8 +149,10 @@ void PropertyBrowser::setDocument(Document *document) if (mDocument) { mDocument->disconnect(this); - if (mTilesetDocument) + if (mTilesetDocument) { mTilesetDocument->terrainModel()->disconnect(this); + mTilesetDocument->wangSetModel()->disconnect(this); + } } mDocument = document; @@ -194,6 +200,10 @@ void PropertyBrowser::setDocument(Document *document) TilesetTerrainModel *terrainModel = tilesetDocument->terrainModel(); connect(terrainModel, &TilesetTerrainModel::terrainChanged, this, &PropertyBrowser::terrainChanged); + + TilesetWangSetModel *wangSetModel = tilesetDocument->wangSetModel(); + connect(wangSetModel, &TilesetWangSetModel::wangSetChanged, + this, &PropertyBrowser::wangSetChanged); } if (document) { @@ -324,6 +334,12 @@ void PropertyBrowser::terrainChanged(Tileset *tileset, int index) updateProperties(); } +void PropertyBrowser::wangSetChanged(Tileset *tileset, int index) +{ + if (mObject == tileset->wangSet(index)) + updateProperties(); +} + static QVariant predefinedPropertyValue(Object *object, const QString &name) { QString objectType; @@ -349,6 +365,7 @@ static QVariant predefinedPropertyValue(Object *object, const QString &name) case Object::MapType: case Object::TerrainType: case Object::TilesetType: + case Object::WangSetType: break; } @@ -532,6 +549,7 @@ void PropertyBrowser::valueChanged(QtProperty *property, const QVariant &val) case Object::TilesetType: applyTilesetValue(id, val); break; case Object::TileType: applyTileValue(id, val); break; case Object::TerrainType: applyTerrainValue(id, val); break; + case Object::WangSetType: applyWangSetValue(id, val); break; } } @@ -832,6 +850,23 @@ void PropertyBrowser::addTerrainProperties() addProperty(groupProperty); } +void PropertyBrowser::addWangSetProperties() +{ + QtProperty *groupProperty = mGroupManager->addProperty(tr("WangSet")); + QtVariantProperty *nameProperty = addProperty(NameProperty, QVariant::String, tr("Name"), groupProperty); + QtVariantProperty *edgeProperty = addProperty(EdgeCountProperty, QVariant::Int, tr("Edge Count"), groupProperty); + QtVariantProperty *cornerProperty = addProperty(CornerCountProperty, QVariant::Int, tr("Corner Count"), groupProperty); + + edgeProperty->setAttribute(QLatin1String("minimum"), 0); + cornerProperty->setAttribute(QLatin1String("minimum"), 0); + + nameProperty->setEnabled(mTilesetDocument); + edgeProperty->setEnabled(mTilesetDocument); + cornerProperty->setEnabled(mTilesetDocument); + + addProperty(groupProperty); +} + void PropertyBrowser::applyMapValue(PropertyId id, const QVariant &val) { QUndoCommand *command = nullptr; @@ -1210,6 +1245,32 @@ void PropertyBrowser::applyTerrainValue(PropertyId id, const QVariant &val) } } +void PropertyBrowser::applyWangSetValue(PropertyId id, const QVariant &val) +{ + Q_ASSERT(mTilesetDocument); + + WangSet *wangSet = static_cast(mObject); + + switch (id) { + case NameProperty: + mDocument->undoStack()->push(new RenameWangSet(mTilesetDocument, + mTilesetDocument->tileset()->wangSets().indexOf(wangSet), + val.toString())); + break; + case EdgeCountProperty: + mDocument->undoStack()->push(new ChangeWangSetEdges(mTilesetDocument, + mTilesetDocument->tileset()->wangSets().indexOf(wangSet), + val.toInt())); + break; + case CornerCountProperty: + mDocument->undoStack()->push(new ChangeWangSetCorners(mTilesetDocument, + mTilesetDocument->tileset()->wangSets().indexOf(wangSet), + val.toInt())); + default: + break; + } +} + /** * @warning This function does not add the property to the view. */ @@ -1337,6 +1398,7 @@ void PropertyBrowser::addProperties() case Object::TilesetType: addTilesetProperties(); break; case Object::TileType: addTileProperties(); break; case Object::TerrainType: addTerrainProperties(); break; + case Object::WangSetType: addWangSetProperties(); break; } // Make sure the color and font properties are collapsed, to save space @@ -1505,6 +1567,12 @@ void PropertyBrowser::updateProperties() mIdToProperty[NameProperty]->setValue(terrain->name()); break; } + case Object::WangSetType: { + const WangSet *wangSet = static_cast(mObject); + mIdToProperty[NameProperty]->setValue(wangSet->name()); + mIdToProperty[EdgeCountProperty]->setValue(wangSet->edgeColors()); + mIdToProperty[CornerCountProperty]->setValue(wangSet->cornerColors()); + } } mUpdating = false; @@ -1564,6 +1632,7 @@ void PropertyBrowser::updateCustomProperties() case Object::MapType: case Object::TerrainType: case Object::TilesetType: + case Object::WangSetType: break; } diff --git a/src/tiled/propertybrowser.h b/src/tiled/propertybrowser.h index d38245a2dc..38fc85ae7b 100644 --- a/src/tiled/propertybrowser.h +++ b/src/tiled/propertybrowser.h @@ -97,6 +97,7 @@ private slots: void tileChanged(Tile *tile); void tileTypeChanged(Tile *tile); void terrainChanged(Tileset *tileset, int index); + void wangSetChanged(Tileset *tileset, int index); void propertyAdded(Object *object, const QString &name); void propertyRemoved(Object *object, const QString &name); @@ -152,6 +153,8 @@ private slots: TileProbabilityProperty, ColumnCountProperty, IdProperty, + EdgeCountProperty, + CornerCountProperty, CustomProperty }; @@ -165,6 +168,7 @@ private slots: void addTilesetProperties(); void addTileProperties(); void addTerrainProperties(); + void addWangSetProperties(); void applyMapValue(PropertyId id, const QVariant &val); void applyMapObjectValue(PropertyId id, const QVariant &val); @@ -177,6 +181,7 @@ private slots: void applyTilesetValue(PropertyId id, const QVariant &val); void applyTileValue(PropertyId id, const QVariant &val); void applyTerrainValue(PropertyId id, const QVariant &val); + void applyWangSetValue(PropertyId id, const QVariant &val); QtVariantProperty *createProperty(PropertyId id, int type, diff --git a/src/tiled/renamewangset.cpp b/src/tiled/renamewangset.cpp new file mode 100644 index 0000000000..4c3fc786da --- /dev/null +++ b/src/tiled/renamewangset.cpp @@ -0,0 +1,54 @@ +/* + * renamewangset.h + * Copyright 2017, Benjamin Trotte + * + * 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 "renamewangset.h" + +#include "wangset.h" +#include "tileset.h" +#include "tilesetdocument.h" +#include "tilesetwangsetmodel.h" + +#include + +using namespace Tiled; +using namespace Internal; + +RenameWangSet::RenameWangSet(TilesetDocument *tilesetDocument, + int index, + const QString &newName) + : QUndoCommand(QCoreApplication::translate("Undo Commands", + "Change Wang Set Name")) + , mWangSetModel(tilesetDocument->wangSetModel()) + , mTileset(tilesetDocument->tileset().data()) + , mIndex(index) + , mOldName(mTileset->wangSet(index)->name()) + , mNewName(newName) +{ +} + +void RenameWangSet::undo() +{ + mWangSetModel->setWangSetName(mIndex, mOldName); +} + +void RenameWangSet::redo() +{ + mWangSetModel->setWangSetName(mIndex, mNewName); +} diff --git a/src/tiled/renamewangset.h b/src/tiled/renamewangset.h new file mode 100644 index 0000000000..cffb08c620 --- /dev/null +++ b/src/tiled/renamewangset.h @@ -0,0 +1,53 @@ +/* + * renamewangset.h + * Copyright 2017, Benjamin Trotte + * + * 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 . + */ + +#pragma once + +#include + +namespace Tiled { + +class Tileset; + +namespace Internal { + +class TilesetDocument; +class TilesetWangSetModel; + +class RenameWangSet : public QUndoCommand +{ +public: + RenameWangSet(TilesetDocument *tilesetDocument, + int index, + const QString &newName); + + void undo() override; + void redo() override; + +private: + TilesetWangSetModel *mWangSetModel; + Tileset *mTileset; + int mIndex; + QString mOldName; + QString mNewName; +}; + +} // namespace Internal +} // namespace Tiled diff --git a/src/tiled/tiled.pro b/src/tiled/tiled.pro index d5caa888be..347a94c466 100644 --- a/src/tiled/tiled.pro +++ b/src/tiled/tiled.pro @@ -84,6 +84,7 @@ SOURCES += aboutdialog.cpp \ addremoveterrain.cpp \ addremovetiles.cpp \ addremovetileset.cpp \ + addremovewangset.cpp \ adjusttileindexes.cpp \ automapper.cpp \ automapperwrapper.cpp \ @@ -109,6 +110,7 @@ SOURCES += aboutdialog.cpp \ changetileobjectgroup.cpp \ changetileprobability.cpp \ changetileterrain.cpp \ + changewangsetdata.cpp \ clickablelabel.cpp \ clipboardmanager.cpp \ colorbutton.cpp \ @@ -190,6 +192,7 @@ SOURCES += aboutdialog.cpp \ raiselowerhelper.cpp \ renamelayer.cpp \ renameterrain.cpp \ + renamewangset.cpp \ reparentlayers.cpp \ replacetileset.cpp \ resizedialog.cpp \ @@ -230,6 +233,7 @@ SOURCES += aboutdialog.cpp \ tilesetmodel.cpp \ tilesetparametersedit.cpp \ tilesetterrainmodel.cpp \ + tilesetwangsetmodel.cpp \ tilesetview.cpp \ tilestamp.cpp \ tilestampmanager.cpp \ @@ -242,6 +246,9 @@ SOURCES += aboutdialog.cpp \ utils.cpp \ varianteditorfactory.cpp \ variantpropertymanager.cpp \ + wangsetview.cpp \ + wangsetmodel.cpp \ + wangdock.cpp \ zoomable.cpp HEADERS += aboutdialog.h \ @@ -255,6 +262,7 @@ HEADERS += aboutdialog.h \ addremoveterrain.h \ addremovetileset.h \ addremovetiles.h \ + addremovewangset.h \ adjusttileindexes.h \ automapper.h \ automapperwrapper.h \ @@ -280,6 +288,7 @@ HEADERS += aboutdialog.h \ changetileobjectgroup.h \ changetileprobability.h \ changetileterrain.h \ + changewangsetdata.h \ clickablelabel.h \ clipboardmanager.h \ colorbutton.h \ @@ -364,6 +373,7 @@ HEADERS += aboutdialog.h \ rangeset.h \ renamelayer.h \ renameterrain.h \ + renamewangset.h \ reparentlayers.h \ replacetileset.h \ resizedialog.h \ @@ -405,6 +415,7 @@ HEADERS += aboutdialog.h \ tilesetmodel.h \ tilesetparametersedit.h \ tilesetterrainmodel.h \ + tilesetwangsetmodel.h \ tilesetview.h \ tilestamp.h \ tilestampmanager.h \ @@ -418,6 +429,9 @@ HEADERS += aboutdialog.h \ utils.h \ varianteditorfactory.h \ variantpropertymanager.h \ + wangsetview.h \ + wangsetmodel.h \ + wangdock.h \ zoomable.h FORMS += aboutdialog.ui \ diff --git a/src/tiled/tiled.qbs b/src/tiled/tiled.qbs index 26bd7598a6..ad50be6c13 100644 --- a/src/tiled/tiled.qbs +++ b/src/tiled/tiled.qbs @@ -80,6 +80,8 @@ QtGuiApplication { "addremovetileset.cpp", "addremovetileset.h", "addremovetiles.h", + "addremovewangset.cpp", + "addremovewangset.h", "adjusttileindexes.cpp", "adjusttileindexes.h", "automapper.cpp", @@ -130,6 +132,8 @@ QtGuiApplication { "changetileprobability.h", "changetileterrain.cpp", "changetileterrain.h", + "changewangsetdata.cpp", + "changewangsetdata.h", "clipboardmanager.cpp", "clipboardmanager.h", "colorbutton.cpp", @@ -305,6 +309,8 @@ QtGuiApplication { "renamelayer.h", "renameterrain.cpp", "renameterrain.h", + "renamewangset.cpp", + "renamewangset.h", "reparentlayers.cpp", "reparentlayers.h", "replacetileset.cpp", @@ -391,6 +397,8 @@ QtGuiApplication { "tilesetterrainmodel.h", "tilesetview.cpp", "tilesetview.h", + "tilesetwangsetmodel.cpp", + "tilesetwangsetmodel.h", "tilestamp.cpp", "tilestamp.h", "tilestampmanager.cpp", @@ -414,6 +422,12 @@ QtGuiApplication { "varianteditorfactory.h", "variantpropertymanager.cpp", "variantpropertymanager.h", + "wangdock.cpp", + "wangdock.h", + "wangsetmodel.cpp", + "wangsetmodel.h", + "wangsetview.cpp", + "wangsetview.h", "zoomable.cpp", "zoomable.h", ] diff --git a/src/tiled/tilesetdocument.cpp b/src/tiled/tilesetdocument.cpp index b8dede473f..32b1b4ff24 100644 --- a/src/tiled/tilesetdocument.cpp +++ b/src/tiled/tilesetdocument.cpp @@ -23,10 +23,12 @@ #include "mapdocument.h" #include "map.h" #include "terrain.h" +#include "wangset.h" #include "tile.h" #include "tilesetformat.h" #include "tilesetmanager.h" #include "tilesetterrainmodel.h" +#include "tilesetwangsetmodel.h" #include #include @@ -58,6 +60,7 @@ TilesetDocument::TilesetDocument(const SharedTileset &tileset, const QString &fi : Document(TilesetDocumentType, fileName) , mTileset(tileset) , mTerrainModel(new TilesetTerrainModel(this, this)) + , mWangSetModel(new TilesetWangSetModel(this, this)) { mCurrentObject = tileset.data(); @@ -76,6 +79,9 @@ TilesetDocument::TilesetDocument(const SharedTileset &tileset, const QString &fi connect(mTerrainModel, &TilesetTerrainModel::terrainRemoved, this, &TilesetDocument::onTerrainRemoved); + connect(mWangSetModel, &TilesetWangSetModel::wangSetRemoved, + this, &TilesetDocument::onWangSetRemoved); + TilesetManager *tilesetManager = TilesetManager::instance(); tilesetManager->addReference(tileset); } @@ -335,5 +341,11 @@ void TilesetDocument::onTerrainRemoved(Terrain *terrain) setCurrentObject(nullptr); } +void TilesetDocument::onWangSetRemoved(WangSet *wangSet) +{ + if (wangSet == mCurrentObject) + setCurrentObject(nullptr); +} + } // namespace Internal } // namespace Tiled diff --git a/src/tiled/tilesetdocument.h b/src/tiled/tilesetdocument.h index 87293527d3..d41d1f394c 100644 --- a/src/tiled/tilesetdocument.h +++ b/src/tiled/tilesetdocument.h @@ -33,6 +33,7 @@ namespace Internal { class MapDocument; class TilesetTerrainModel; +class TilesetWangSetModel; /** * Represents an editable tileset. @@ -85,6 +86,7 @@ class TilesetDocument : public Document QList currentObjects() const override; TilesetTerrainModel *terrainModel() const { return mTerrainModel; } + TilesetWangSetModel *wangSetModel() const { return mWangSetModel; } void setTileType(Tile *tile, const QString &type); void setTileImage(Tile *tile, const QPixmap &image, const QString &source); @@ -111,6 +113,8 @@ class TilesetDocument : public Document */ void tileTerrainChanged(const QList &tiles); + void tileWangSetChanged(const QList &tiles); + /** * Emitted when the terrain probability of a tile changed. */ @@ -138,12 +142,14 @@ private slots: void onPropertiesChanged(Object *object); void onTerrainRemoved(Terrain *terrain); + void onWangSetRemoved(WangSet *wangSet); private: SharedTileset mTileset; QList mMapDocuments; TilesetTerrainModel *mTerrainModel; + TilesetWangSetModel *mWangSetModel; QList mSelectedTiles; }; diff --git a/src/tiled/tileseteditor.cpp b/src/tiled/tileseteditor.cpp index 68440877cc..32021601ed 100644 --- a/src/tiled/tileseteditor.cpp +++ b/src/tiled/tileseteditor.cpp @@ -23,7 +23,9 @@ #include "addremovemapobject.h" #include "addremoveterrain.h" #include "addremovetiles.h" +#include "addremovewangset.h" #include "changetileterrain.h" +#include "changewangsetdata.h" #include "erasetiles.h" #include "maintoolbar.h" #include "mapdocument.h" @@ -44,6 +46,8 @@ #include "tilesetview.h" #include "undodock.h" #include "utils.h" +#include "wangdock.h" +#include "wangset.h" #include "zoomable.h" #include @@ -166,6 +170,7 @@ TilesetEditor::TilesetEditor(QObject *parent) , mUndoDock(new UndoDock(mMainWindow)) , mTerrainDock(new TerrainDock(mMainWindow)) , mTileCollisionDock(new TileCollisionDock(mMainWindow)) + , mWangDock(new WangDock(mMainWindow)) , mZoomComboBox(new QComboBox) , mTileAnimationEditor(new TileAnimationEditor(mMainWindow)) , mCurrentTilesetDocument(nullptr) @@ -173,6 +178,7 @@ TilesetEditor::TilesetEditor(QObject *parent) { mTerrainDock->setVisible(false); mTileCollisionDock->setVisible(false); + mWangDock->setVisible(false); #if QT_VERSION >= 0x050600 mMainWindow->setDockOptions(mMainWindow->dockOptions() | QMainWindow::GroupedDragging); @@ -184,11 +190,13 @@ TilesetEditor::TilesetEditor(QObject *parent) mMainWindow->addDockWidget(Qt::LeftDockWidgetArea, mUndoDock); mMainWindow->addDockWidget(Qt::RightDockWidgetArea, mTerrainDock); mMainWindow->addDockWidget(Qt::RightDockWidgetArea, mTileCollisionDock); + mMainWindow->addDockWidget(Qt::RightDockWidgetArea, mWangDock); mUndoDock->setVisible(false); QAction *editTerrain = mTerrainDock->toggleViewAction(); QAction *editCollision = mTileCollisionDock->toggleViewAction(); + QAction *editWang = mWangDock->toggleViewAction(); mAddTiles->setIcon(QIcon(QLatin1String(":images/16x16/add.png"))); mRemoveTiles->setIcon(QIcon(QLatin1String(":images/16x16/remove.png"))); @@ -196,6 +204,8 @@ TilesetEditor::TilesetEditor(QObject *parent) editTerrain->setIconVisibleInMenu(false); editCollision->setIcon(QIcon(QLatin1String(":images/48x48/tile-collision-editor.png"))); editCollision->setIconVisibleInMenu(false); + editWang->setIcon(QIcon(QLatin1String(":images/24x24/terrain.png"))); + editWang->setIconVisibleInMenu(false); Utils::setThemeIcon(mAddTiles, "add"); Utils::setThemeIcon(mRemoveTiles, "remove"); @@ -207,6 +217,7 @@ TilesetEditor::TilesetEditor(QObject *parent) mTilesetToolBar->addSeparator(); mTilesetToolBar->addAction(editTerrain); mTilesetToolBar->addAction(editCollision); + mTilesetToolBar->addAction(editWang); mMainWindow->statusBar()->addPermanentWidget(mZoomComboBox); @@ -219,11 +230,16 @@ TilesetEditor::TilesetEditor(QObject *parent) connect(editTerrain, &QAction::toggled, this, &TilesetEditor::setEditTerrain); connect(editCollision, &QAction::toggled, this, &TilesetEditor::setEditCollision); + connect(editWang, &QAction::toggled, this, &TilesetEditor::setEditWang); connect(mTerrainDock, &TerrainDock::currentTerrainChanged, this, &TilesetEditor::currentTerrainChanged); connect(mTerrainDock, &TerrainDock::addTerrainTypeRequested, this, &TilesetEditor::addTerrainType); connect(mTerrainDock, &TerrainDock::removeTerrainTypeRequested, this, &TilesetEditor::removeTerrainType); + connect(mWangDock, &WangDock::currentWangSetChanged, this, &TilesetEditor::currentWangSetChanged); + connect(mWangDock, &WangDock::addWangSetRequested, this, &TilesetEditor::addWangSet); + connect(mWangDock, &WangDock::removeWangSetRequested, this, &TilesetEditor::removeWangSet); + connect(this, &TilesetEditor::currentTileChanged, mTileAnimationEditor, &TileAnimationEditor::setTile); connect(this, &TilesetEditor::currentTileChanged, @@ -296,6 +312,8 @@ void TilesetEditor::addDocument(Document *document) connect(view, &TilesetView::createNewTerrain, this, &TilesetEditor::addTerrainType); connect(view, &TilesetView::terrainImageSelected, this, &TilesetEditor::setTerrainImage); + connect(view, &TilesetView::wangSetImageSelected, this, &TilesetEditor::setWangSetImage); + QItemSelectionModel *s = view->selectionModel(); connect(s, &QItemSelectionModel::selectionChanged, this, &TilesetEditor::selectionChanged); connect(s, &QItemSelectionModel::currentChanged, this, &TilesetEditor::currentChanged); @@ -344,6 +362,7 @@ void TilesetEditor::setCurrentDocument(Document *document) mWidgetStack->setCurrentWidget(tilesetView); tilesetView->setEditTerrain(mTerrainDock->isVisible()); + tilesetView->setEditWangSet(mWangDock->isVisible()); tilesetView->zoomable()->setComboBox(mZoomComboBox); } @@ -352,6 +371,7 @@ void TilesetEditor::setCurrentDocument(Document *document) mTileAnimationEditor->setTilesetDocument(tilesetDocument); mTileCollisionDock->setTilesetDocument(tilesetDocument); mTerrainDock->setDocument(document); + mWangDock->setDocument(document); mCurrentTilesetDocument = tilesetDocument; @@ -387,7 +407,8 @@ QList TilesetEditor::dockWidgets() const mPropertiesDock, mUndoDock, mTerrainDock, - mTileCollisionDock + mTileCollisionDock, + mWangDock }; } @@ -737,8 +758,10 @@ void TilesetEditor::setEditTerrain(bool editTerrain) if (TilesetView *view = currentTilesetView()) view->setEditTerrain(editTerrain); - if (editTerrain) + if (editTerrain) { mTileCollisionDock->setVisible(false); + mWangDock->setVisible(false); + } } void TilesetEditor::currentTerrainChanged(const Terrain *terrain) @@ -760,11 +783,23 @@ void TilesetEditor::setEditCollision(bool editCollision) if (editCollision) { mPropertiesDock->setDocument(mTileCollisionDock->dummyMapDocument()); mTerrainDock->setVisible(false); + mWangDock->setVisible(false); } else { mPropertiesDock->setDocument(mCurrentTilesetDocument); } } +void TilesetEditor::setEditWang(bool editWang) +{ + if (TilesetView *view = currentTilesetView()) + view->setEditWangSet(editWang); + + if (editWang) { + mTerrainDock->setVisible(false); + mTileCollisionDock->setVisible(false); + } +} + void TilesetEditor::addTerrainType() { Tileset *tileset = currentTileset(); @@ -826,6 +861,42 @@ void TilesetEditor::removeTerrainType() undoStack->endMacro(); } +void TilesetEditor::currentWangSetChanged(const WangSet *wangSet) +{ + TilesetView *view = currentTilesetView(); + if (!view) + return; + + if (wangSet) + view->setWangSet(wangSet); +} + +void TilesetEditor::addWangSet() +{ + Tileset *tileset = currentTileset(); + if (!tileset) + return; + + //2 and 0 are default values for number of edges and corners TODO define this some where better? + WangSet *wangSet = new WangSet(tileset, 2, 0, QString(), -1); + wangSet->setName(tr("New Wang Set")); + + mCurrentTilesetDocument->undoStack()->push(new AddWangSet(mCurrentTilesetDocument, + wangSet)); + + mWangDock->editWangSetName(wangSet); +} + +void TilesetEditor::removeWangSet() +{ + WangSet *wangSet = mWangDock->currentWangSet(); + if (!wangSet) + return; + + mCurrentTilesetDocument->undoStack()->push(new RemoveWangSet(mCurrentTilesetDocument, + wangSet)); +} + void TilesetEditor::setTerrainImage(Tile *tile) { Terrain *terrain = mTerrainDock->currentTerrain(); @@ -837,6 +908,17 @@ void TilesetEditor::setTerrainImage(Tile *tile) tile->id())); } +void TilesetEditor::setWangSetImage(Tile *tile) +{ + WangSet *wangSet = mWangDock->currentWangSet(); + if (!wangSet) + return; + + mCurrentTilesetDocument->undoStack()->push(new SetWangSetImage(mCurrentTilesetDocument, + mCurrentTilesetDocument->tileset()->wangSets().indexOf(wangSet), + tile->id())); +} + void TilesetEditor::updateAddRemoveActions() { bool isCollection = false; diff --git a/src/tiled/tileseteditor.h b/src/tiled/tileseteditor.h index 424e9ac43e..72d934ef75 100644 --- a/src/tiled/tileseteditor.h +++ b/src/tiled/tileseteditor.h @@ -36,6 +36,7 @@ namespace Tiled { class Terrain; class Tile; class Tileset; +class WangSet; namespace Internal { @@ -47,6 +48,7 @@ class TilesetDocument; class TilesetEditorWindow; class TilesetView; class UndoDock; +class WangDock; class Zoomable; class TilesetEditor : public Editor @@ -106,12 +108,19 @@ private slots: void setEditCollision(bool editCollision); + void setEditWang(bool editWang); + void updateAddRemoveActions(); void addTerrainType(); void removeTerrainType(); void setTerrainImage(Tile *tile); + void currentWangSetChanged(const WangSet *wangSet); + void addWangSet(); + void removeWangSet(); + void setWangSetImage(Tile *tile); + private: void setCurrentTile(Tile *tile); @@ -129,6 +138,7 @@ private slots: UndoDock *mUndoDock; TerrainDock *mTerrainDock; TileCollisionDock *mTileCollisionDock; + WangDock *mWangDock; QComboBox *mZoomComboBox; TileAnimationEditor *mTileAnimationEditor; diff --git a/src/tiled/tilesetview.cpp b/src/tiled/tilesetview.cpp index 1883ffc273..412989111d 100644 --- a/src/tiled/tilesetview.cpp +++ b/src/tiled/tilesetview.cpp @@ -30,6 +30,7 @@ #include "tilesetdocument.h" #include "tilesetmodel.h" #include "utils.h" +#include "wangset.h" #include "zoomable.h" #include @@ -540,6 +541,11 @@ void TilesetView::setEditTerrain(bool enabled) viewport()->update(); } +void TilesetView::setEditWangSet(bool enabled) +{ + mEditWangSet = enabled; +} + /** * The id of the terrain currently being specified. Returns -1 when no terrain * is set (used for erasing terrain info). @@ -559,6 +565,11 @@ void TilesetView::setTerrain(const Terrain *terrain) viewport()->update(); } +void TilesetView::setWangSet(const WangSet *wangSet) +{ + mWangSet = wangSet; +} + QIcon TilesetView::imageMissingIcon() const { return QIcon::fromTheme(QLatin1String("image-missing"), mImageMissingIcon); @@ -707,6 +718,15 @@ void TilesetView::contextMenuEvent(QContextMenuEvent *event) QAction *setImage = menu.addAction(tr("Set Terrain Image")); connect(setImage, SIGNAL(triggered()), SLOT(selectTerrainImage())); } + } else if (mEditWangSet) { + selectionModel()->setCurrentIndex(index, + QItemSelectionModel::SelectCurrent | + QItemSelectionModel::Clear); + + if (mWangSet) { + QAction *setImage = menu.addAction(tr("Set WangSet Image")); + connect(setImage, SIGNAL(triggered()), SLOT(selectWangSetImage())); + } } else if (mTilesetDocument) { QAction *tileProperties = menu.addAction(propIcon, tr("Tile &Properties...")); @@ -763,6 +783,12 @@ void TilesetView::selectTerrainImage() emit terrainImageSelected(tile); } +void TilesetView::selectWangSetImage() +{ + if (Tile *tile = currentTile()) + emit wangSetImageSelected(tile); +} + void TilesetView::editTileProperties() { Q_ASSERT(mTilesetDocument); diff --git a/src/tiled/tilesetview.h b/src/tiled/tilesetview.h index c302c31754..e2ded6253d 100644 --- a/src/tiled/tilesetview.h +++ b/src/tiled/tilesetview.h @@ -27,6 +27,7 @@ namespace Tiled { class Terrain; +class WangSet; namespace Internal { @@ -91,6 +92,9 @@ class TilesetView : public QTableView */ void setEditTerrain(bool enabled); + bool isEditWangSet() const { return mEditWangSet; } + void setEditWangSet(bool enabled); + /** * Sets whether terrain editing is in "erase" mode. * \sa setEditTerrain @@ -105,6 +109,8 @@ class TilesetView : public QTableView */ void setTerrain(const Terrain *terrain); + void setWangSet(const WangSet *wangSet); + QModelIndex hoveredIndex() const { return mHoveredIndex; } int hoveredCorner() const { return mHoveredCorner; } @@ -115,6 +121,7 @@ class TilesetView : public QTableView signals: void createNewTerrain(Tile *tile); void terrainImageSelected(Tile *tile); + void wangSetImageSelected(Tile *tile); void swapTilesRequested(Tile *tileA, Tile *tileB); void changeSelectedMapObjectsTileRequested(Tile *tile); @@ -131,6 +138,7 @@ class TilesetView : public QTableView private slots: void addTerrainType(); void selectTerrainImage(); + void selectWangSetImage(); void editTileProperties(); void swapTiles(); void changeSelectedMapObjectsTile(); @@ -150,8 +158,10 @@ private slots: bool mMarkAnimatedTiles; bool mEditTerrain; + bool mEditWangSet; bool mEraseTerrain; const Terrain *mTerrain; + const WangSet *mWangSet; QModelIndex mHoveredIndex; int mHoveredCorner; bool mTerrainChanged; diff --git a/src/tiled/tilesetwangsetmodel.cpp b/src/tiled/tilesetwangsetmodel.cpp new file mode 100644 index 0000000000..def294d88e --- /dev/null +++ b/src/tiled/tilesetwangsetmodel.cpp @@ -0,0 +1,186 @@ +/* + * tilesetwangsetmodel.cpp + * Copyright 2017, Benjamin Trotter + * + * 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 "tilesetwangsetmodel.h" + +#include "tilesetdocument.h" +#include "renamewangset.h" +#include "wangset.h" +#include "tileset.h" +#include "tile.h" + +using namespace Tiled; +using namespace Internal; + +TilesetWangSetModel::TilesetWangSetModel(TilesetDocument *mapDocument, + QObject *parent): + QAbstractListModel(parent), + mTilesetDocument(mapDocument) +{ +} + +TilesetWangSetModel::~TilesetWangSetModel() +{ +} + +QModelIndex TilesetWangSetModel::index(WangSet *wangSet) +{ + Tileset *tileset = wangSet->tileset(); + int row = tileset->wangSets().indexOf(wangSet); + + return index(row, 0); +} + +int TilesetWangSetModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return mTilesetDocument->tileset()->wangSetCount(); + + return 0; +} + +int TilesetWangSetModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + + +QVariant TilesetWangSetModel::data(const QModelIndex &index, int role) const +{ + if (WangSet *wangSet = wangSetAt(index)) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return wangSet->name(); + case Qt::DecorationRole: + if (Tile *imageTile = wangSet->imageTile()) + return imageTile->image(); + break; + case WangSetRole: + return QVariant::fromValue(wangSet); + } + } + + return QVariant(); +} + +bool TilesetWangSetModel::setData(const QModelIndex &index, + const QVariant &value, + int role) +{ + if (role == Qt::EditRole) { + const QString newName = value.toString(); + WangSet *wangSet = wangSetAt(index); + if (wangSet->name() != newName) { + RenameWangSet *rename = new RenameWangSet(mTilesetDocument, + mTilesetDocument->tileset()->wangSets().indexOf(wangSet), + newName); + mTilesetDocument->undoStack()->push(rename); + } + return true; + } + + return false; +} + +Qt::ItemFlags TilesetWangSetModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags rc = QAbstractItemModel::flags(index); + if (index.isValid()) // can edit wangSet names + rc |= Qt::ItemIsEditable; + return rc; +} + +WangSet *TilesetWangSetModel::wangSetAt(const QModelIndex &index) const +{ + if (index.isValid()) + return mTilesetDocument->tileset()->wangSet(index.row()); + + return nullptr; +} + +void TilesetWangSetModel::insertWangSet(int index, WangSet *wangSet) +{ + Tileset *tileset = mTilesetDocument->tileset().data(); + int row = tileset->wangSetCount(); + + emit wangSetAboutToBeAdded(tileset); + + beginInsertRows(QModelIndex(), row, row); + tileset->insertWangSet(index, wangSet); + endInsertRows(); + + emit wangSetAdded(tileset); +} + +WangSet *TilesetWangSetModel::takeWangSetAt(int index) +{ + Tileset *tileset = mTilesetDocument->tileset().data(); + + emit wangSetAboutToBeRemoved(tileset->wangSet(index)); + + beginRemoveRows(QModelIndex(), index, index); + WangSet *wangSet = tileset->takeWangSetAt(index); + endRemoveRows(); + + emit wangSetRemoved(wangSet); + + return wangSet; +} + +void TilesetWangSetModel::setWangSetName(int index, const QString &name) +{ + Tileset *tileset = mTilesetDocument->tileset().data(); + WangSet *wangSet = tileset->wangSet(index); + wangSet->setName(name); + emitWangSetChange(wangSet); +} + +void TilesetWangSetModel::setWangSetEdges(int index, int value) +{ + Tileset *tileset = mTilesetDocument->tileset().data(); + WangSet *wangSet = tileset->wangSet(index); + wangSet->setEdgeColors(value); + emitWangSetChange(wangSet); +} + +void TilesetWangSetModel::setWangSetCorners(int index, int value) +{ + Tileset *tileset = mTilesetDocument->tileset().data(); + WangSet *wangSet = tileset->wangSet(index); + wangSet->setCornerColors(value); + emitWangSetChange(wangSet); +} + +void TilesetWangSetModel::setWangSetImage(int index, int tileId) +{ + Tileset *tileset = mTilesetDocument->tileset().data(); + WangSet *wangSet = tileset->wangSet(index); + wangSet->setImageTileId(tileId); + emitWangSetChange(wangSet); +} + +void TilesetWangSetModel::emitWangSetChange(WangSet *wangSet) +{ + const QModelIndex index = TilesetWangSetModel::index(wangSet); + emit dataChanged(index, index); + emit wangSetChanged(wangSet->tileset(), index.row()); +} diff --git a/src/tiled/tilesetwangsetmodel.h b/src/tiled/tilesetwangsetmodel.h new file mode 100644 index 0000000000..d32ea80c92 --- /dev/null +++ b/src/tiled/tilesetwangsetmodel.h @@ -0,0 +1,92 @@ +/* + * tilesetwangsetmodel.h + * Copyright 2017, Benjamin Trotter + * + * 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 . + */ + +#pragma once + +#include + +namespace Tiled { + +class Tileset; +class WangSet; + +namespace Internal { + +class TilesetDocument; + +class TilesetWangSetModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum UserRoles { + WangSetRole = Qt::UserRole + }; + + explicit TilesetWangSetModel(TilesetDocument *mapDocument, + QObject *parent = nullptr); + ~TilesetWangSetModel(); + + using QAbstractListModel::index; + QModelIndex index(WangSet *wangSet); + + /** + * Returns the number of rows. For the root, this is the number of wangSets + * in the tileset. + */ + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + WangSet *wangSetAt(const QModelIndex &index) const; + + void insertWangSet(int index, WangSet *wangSet); + WangSet *takeWangSetAt(int index); + void setWangSetName(int index, const QString &name); + void setWangSetEdges(int index, int value); + void setWangSetCorners(int index, int value); + void setWangSetImage(int index, int tileId); + +signals: + void wangSetAboutToBeAdded(Tileset *tileset); + void wangSetAdded(Tileset *tileset); + void wangSetAboutToBeRemoved(WangSet *wangSet); + void wangSetRemoved(WangSet *wangSet); + + /** + * Emitted when either the name or the image of a terrain changed. + */ + void wangSetChanged(Tileset *tileset, int index); + +private: + void emitWangSetChange(WangSet *wangSet); + + TilesetDocument *mTilesetDocument; +}; + +} // namespace Internal +} // namespace Tiled diff --git a/src/tiled/wangdock.cpp b/src/tiled/wangdock.cpp new file mode 100644 index 0000000000..f7f2c34774 --- /dev/null +++ b/src/tiled/wangdock.cpp @@ -0,0 +1,294 @@ +/* + * wangdock.cpp + * Copyright 2017, Benjamin Trotter + * + * 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 "wangset.h" +#include "wangdock.h" +#include "wangsetview.h" +#include "wangsetmodel.h" +#include "documentmanager.h" +#include "map.h" +#include "mapdocument.h" +#include "tilesetdocument.h" +#include "tilesetdocumentsmodel.h" +#include "tilesetwangsetmodel.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Tiled; +using namespace Tiled::Internal; + +namespace Tiled { +namespace Internal { + +static WangSet *firstWangSet(MapDocument *MapDocument) +{ + for (const SharedTileset &tileset : MapDocument->map()->tilesets()) + if (tileset->wangSetCount() > 0) + return tileset->wangSet(0); + + return nullptr; +} + +static WangSet *firstWangSet(TilesetDocument *tilesetDocument) +{ + Tileset *tileset = tilesetDocument->tileset().data(); + if (tileset->wangSetCount() > 0) + return tileset->wangSet(0); + + return nullptr; +} + +class WangSetFilterModel : public QSortFilterProxyModel +{ +public: + WangSetFilterModel(QObject *parent = nullptr) + : QSortFilterProxyModel(parent) + { + } + + void setEnabled(bool enabled) { mEnabled = enabled; } + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + if (!mEnabled) + return true; + if (sourceParent.isValid()) + return true; + + const QAbstractItemModel *model = sourceModel(); + const QModelIndex index = model->index(sourceRow, 0, sourceParent); + return index.isValid() && model->hasChildren(index); + } + + bool mEnabled; +}; + +} // namespace Internal +} // namespace Tiled + +WangDock::WangDock(QWidget *parent) + : QDockWidget(parent) + , mToolBar(new QToolBar(this)) + , mAddWangSet(new QAction(this)) + , mRemoveWangSet(new QAction(this)) + , mDocument(nullptr) + , mCurrentWangSet(nullptr) + , mTilesetDocumentFilterModel(new TilesetDocumentsFilterModel(this)) + , mWangSetModel(new WangSetModel(mTilesetDocumentFilterModel, this)) + , mProxyModel(new WangSetFilterModel(this)) + , mInitializing(false) +{ + setObjectName(QLatin1String("WangSetDock")); + + QWidget *w = new QWidget(this); + + mWangSetView = new WangSetView(w); + mWangSetView->setModel(mProxyModel); + connect(mWangSetView->selectionModel(), &QItemSelectionModel::currentRowChanged, + this, &WangDock::refreshCurrentWangSet); + connect(mWangSetView, SIGNAL(pressed(QModelIndex)), + SLOT(indexPressed(QModelIndex))); + + connect(mProxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(expandRows(QModelIndex,int,int))); + + mAddWangSet->setIcon(QIcon(QStringLiteral(":/images/22x22/add.png"))); + mRemoveWangSet->setIcon(QIcon(QStringLiteral(":/images/22x22/remove.png"))); + + Utils::setThemeIcon(mAddWangSet, "add"); + Utils::setThemeIcon(mRemoveWangSet, "remove"); + + mToolBar->setFloatable(false); + mToolBar->setMovable(false); + mToolBar->setIconSize(Utils::smallIconSize()); + + mToolBar->addAction(mAddWangSet); + mToolBar->addAction(mRemoveWangSet); + + QHBoxLayout *horizontal = new QHBoxLayout; + horizontal->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding)); + horizontal->addWidget(mToolBar); + + QVBoxLayout *vertical = new QVBoxLayout(w); + vertical->setMargin(0); + vertical->addWidget(mWangSetView); + vertical->addLayout(horizontal); + + connect(mAddWangSet, &QAction::triggered, + this, &WangDock::addWangSetRequested); + connect(mRemoveWangSet, &QAction::triggered, + this, &WangDock::removeWangSetRequested); + + setWidget(w); + retranslateUi(); +} + +WangDock::~WangDock() +{ +} + +void WangDock::setDocument(Document *document) +{ + if (mDocument == document) + return; + + if (auto tilesetDocument = qobject_cast(mDocument)) {\ + tilesetDocument->disconnect(this); + } + + mDocument = document; + mInitializing = true; + + if (auto mapDocument = qobject_cast(document)) { + mTilesetDocumentFilterModel->setMapDocument(mapDocument); + + mProxyModel->setEnabled(true); + mProxyModel->setSourceModel(mWangSetModel); + mWangSetView->expandAll(); + + setCurrentWangSet((firstWangSet(mapDocument))); + + mToolBar->setVisible(false); + + } else if (auto tilesetDocument = qobject_cast(document)) { + TilesetWangSetModel *wangSetModel = tilesetDocument->wangSetModel(); + + mWangSetView->setTilesetDocument(tilesetDocument); + mProxyModel->setEnabled(false); + mProxyModel->setSourceModel(wangSetModel); + + setCurrentWangSet(firstWangSet(tilesetDocument)); + + mToolBar->setVisible(true); + + /* + * Removing a wangset usually changes the selected terrain without the + * selection changing rows, so we can't rely on the currentRowChanged + * signal. + */ + connect(wangSetModel, &TilesetWangSetModel::wangSetRemoved, + this, &WangDock::refreshCurrentWangSet); + + } else { + mProxyModel->setSourceModel(nullptr); + setCurrentWangSet(nullptr); + mToolBar->setVisible(false); + } + + mInitializing = false; +} + +void WangDock::editWangSetName(WangSet *wangSet) +{ + const QModelIndex index = wangSetIndex(wangSet); + QItemSelectionModel *selectionModel = mWangSetView->selectionModel(); + + selectionModel->setCurrentIndex(index, + QItemSelectionModel::ClearAndSelect | + QItemSelectionModel::Rows); + + mWangSetView->edit(index); +} + +void WangDock::changeEvent(QEvent *event) +{ + QDockWidget::changeEvent(event); + switch (event->type()) { + case QEvent::LanguageChange: + retranslateUi(); + break; + default: + break; + } +} + +void WangDock::refreshCurrentWangSet() +{ + QItemSelectionModel *selectionModel = mWangSetView->selectionModel(); + WangSet *wangSet = mWangSetView->wangSetAt(selectionModel->currentIndex()); + setCurrentWangSet(wangSet); +} + +void WangDock::indexPressed(const QModelIndex &index) +{ + if (WangSet *wangSet = mWangSetView->wangSetAt(index)) + mDocument->setCurrentObject(wangSet); +} + +void WangDock::expandRows(const QModelIndex &parent, int first, int last) +{ + if (parent.isValid()) + return; + + for (int row = first; row <= last; ++row) + mWangSetView->expand(mProxyModel->index(row, 0, parent)); +} + +void WangDock::setCurrentWangSet(WangSet *wangSet) +{ + if (mCurrentWangSet == wangSet) + return; + + mCurrentWangSet = wangSet; + + if (wangSet) { + mWangSetView->setCurrentIndex(wangSetIndex(wangSet)); + } else { + mWangSetView->selectionModel()->clearCurrentIndex(); + mWangSetView->selectionModel()->clearSelection(); + mCurrentWangSet = nullptr; + } + + if (wangSet && !mInitializing) + mDocument->setCurrentObject(wangSet); + + mRemoveWangSet->setEnabled(wangSet); + + emit currentWangSetChanged(mCurrentWangSet); +} + +void WangDock::retranslateUi() +{ + setWindowTitle(tr("Wang Sets")); + + mAddWangSet->setText(tr("Add Wang Set")); + mRemoveWangSet->setText(tr("Remove Wang Set")); +} + +QModelIndex WangDock::wangSetIndex(WangSet *wangSet) const +{ + QModelIndex sourceIndex; + + if (mDocument->type() == Document::MapDocumentType) + sourceIndex = mWangSetModel->index(wangSet); + else if (auto tilesetDocument = qobject_cast(mDocument)) + sourceIndex = tilesetDocument->wangSetModel()->index(wangSet); + + return mProxyModel->mapFromSource(sourceIndex); +} diff --git a/src/tiled/wangdock.h b/src/tiled/wangdock.h new file mode 100644 index 0000000000..b60fd89c18 --- /dev/null +++ b/src/tiled/wangdock.h @@ -0,0 +1,94 @@ +/* + * wangdock.h + * Copyright 2017, Benjamin Trotter + * + * 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 . + */ + +#pragma once + +#include +#include + +class QModelIndex; +class QPushButton; +class QToolBar; + +namespace Tiled { + +class WangSet; + +namespace Internal { + +class Document; +class WangSetFilterModel; +class WangSetView; +class WangSetModel; +class TilesetDocument; +class TilesetDocumentsFilterModel; + +class WangDock : public QDockWidget +{ + Q_OBJECT + +public: + WangDock(QWidget *parent = nullptr); + ~WangDock(); + + void setDocument(Document *document); + + WangSet *currentWangSet() const { return mCurrentWangSet; } + + void editWangSetName(WangSet *wangSet); + +signals: + void currentWangSetChanged(const WangSet *WangSet); + + void addWangSetRequested(); + void removeWangSetRequested(); + +public slots: + void setCurrentWangSet(WangSet *wangSet); + +protected: + void changeEvent(QEvent *event) override; + +private slots: + void refreshCurrentWangSet(); + void indexPressed(const QModelIndex &index); + void expandRows(const QModelIndex &parent, int first, int last); + +private: + void retranslateUi(); + + QModelIndex wangSetIndex(WangSet *wangSet) const; + + QToolBar *mToolBar; + QAction *mAddWangSet; + QAction *mRemoveWangSet; + + Document *mDocument; + WangSetView *mWangSetView; + WangSet *mCurrentWangSet; + TilesetDocumentsFilterModel *mTilesetDocumentFilterModel; + WangSetModel *mWangSetModel; + WangSetFilterModel *mProxyModel; + + bool mInitializing; +}; + +} // namespace Internal +} // namespace Tiled diff --git a/src/tiled/wangsetmodel.cpp b/src/tiled/wangsetmodel.cpp new file mode 100644 index 0000000000..7bd98c0053 --- /dev/null +++ b/src/tiled/wangsetmodel.cpp @@ -0,0 +1,292 @@ +/* + * wangsetmodel.cpp + * Copyright 2017, Benjamin Trotter + * + * 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 "wangsetmodel.h" + +#include "containerhelpers.h" +#include "map.h" +#include "mapdocument.h" +#include "wangset.h" +#include "tileset.h" +#include "tile.h" +#include "tilesetdocument.h" +#include "tilesetdocumentsmodel.h" +#include "tileset.h" +#include "tilesetwangsetmodel.h" + +#include +#include +#include + +using namespace Tiled; +using namespace Tiled::Internal; + +WangSetModel::WangSetModel(QAbstractItemModel *tilesetDocumentModel, + QObject *parent): + QAbstractItemModel(parent), + mTilesetDocumentsModel(tilesetDocumentModel) +{ + connect(mTilesetDocumentsModel, &QAbstractItemModel::rowsInserted, + this, &WangSetModel::onTilesetRowsInserted); + connect(mTilesetDocumentsModel, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &WangSetModel::onTilesetRowsAboutToBeRemoved); + connect(mTilesetDocumentsModel, &QAbstractItemModel::rowsMoved, + this, &WangSetModel::onTilesetRowsMoved); + connect(mTilesetDocumentsModel, &QAbstractItemModel::layoutChanged, + this, &WangSetModel::onTilesetLayoutChanged); + connect(mTilesetDocumentsModel, &QAbstractItemModel::dataChanged, + this, &WangSetModel::onTilesetDataChanged); +} + +WangSetModel::~WangSetModel() +{ +} + +QModelIndex WangSetModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + if (!parent.isValid()) + return createIndex(row, column); + else if (Tileset *tileset = tilesetAt(parent)) + return createIndex(row, column, tileset); + + return QModelIndex(); +} + +QModelIndex WangSetModel::index(Tileset *tileset) const +{ + for (int row = 0; row < mTilesetDocuments.size(); ++row) + if (mTilesetDocuments.at(row)->tileset() == tileset) + return createIndex(row, 0); + + return QModelIndex(); +} + +QModelIndex WangSetModel::index(WangSet *wangSet) const +{ + Tileset *tileset = wangSet->tileset(); + int row = tileset->wangSets().indexOf(wangSet); + return createIndex(row, 0, tileset); +} + +QModelIndex WangSetModel::parent(const QModelIndex &child) const +{ + if (WangSet *wangSet = wangSetAt(child)) + return index(wangSet->tileset()); + + return QModelIndex(); +} + +int WangSetModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return mTilesetDocuments.size(); + else if (Tileset *tileset = tilesetAt(parent)) + return tileset->wangSetCount(); + + return 0; +} + +int WangSetModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant WangSetModel::data(const QModelIndex &index, int role) const +{ + if (WangSet *wangSet = wangSetAt(index)) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return wangSet->name(); + case Qt::DecorationRole: + if (Tile *tile = wangSet->imageTile()) + return tile->image(); + break; + case WangSetRole: + return QVariant::fromValue(wangSet); + } + } else if (Tileset *tileset = tilesetAt(index)) { + switch (role) { + case Qt::DisplayRole: + return tileset->name(); + case Qt::SizeHintRole: + return QSize(1, 32); + case Qt::FontRole: { + QFont font = QApplication::font(); + font.setBold(true); + return font; + } + case Qt::BackgroundRole: { + QColor bg = QApplication::palette().alternateBase().color(); + return bg;//.darker(103); + } + } + } + + return QVariant(); +} + +Qt::ItemFlags WangSetModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); + + if (tilesetAt(index)) + defaultFlags &= ~Qt::ItemIsSelectable; + + return defaultFlags; +} + +Tileset *WangSetModel::tilesetAt(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + if (index.parent().isValid()) // tilesets don't have parents + return nullptr; + if (index.row() >= mTilesetDocuments.size()) + return nullptr; + + return mTilesetDocuments.at(index.row())->tileset().data(); +} + +WangSet *WangSetModel::wangSetAt(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + + if (Tileset *tileset = static_cast(index.internalPointer())) + return tileset->wangSet(index.row()); + + return nullptr; +} + +void WangSetModel::onTilesetRowsInserted(const QModelIndex &parent, int first, int last) +{ + beginInsertRows(QModelIndex(), first, last); + for (int row = first; row <= last; ++row) { + const QModelIndex index = mTilesetDocumentsModel->index(row, 0, parent); + const QVariant var = mTilesetDocumentsModel->data(index, TilesetDocumentsModel::TilesetDocumentRole); + TilesetDocument *tilesetDocument = var.value(); + + mTilesetDocuments.insert(row, tilesetDocument); + + TilesetWangSetModel *tilesetWangSetModel = tilesetDocument->wangSetModel(); + connect(tilesetWangSetModel, &TilesetWangSetModel::wangSetAboutToBeAdded, + this, &WangSetModel::onWangSetAboutToBeAdded); + connect(tilesetWangSetModel, &TilesetWangSetModel::wangSetAdded, + this, &WangSetModel::onWangSetAdded); + connect(tilesetWangSetModel, &TilesetWangSetModel::wangSetAboutToBeRemoved, + this, &WangSetModel::onWangSetAboutToBeRemoved); + connect(tilesetWangSetModel, &TilesetWangSetModel::wangSetRemoved, + this, &WangSetModel::onWangSetRemoved); + } + endInsertRows(); +} + +void WangSetModel::onTilesetRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + Q_UNUSED(parent) + + beginRemoveRows(QModelIndex(), first, last); + for (int index = last; index >= first; --index) { + TilesetDocument *tilesetDocument = mTilesetDocuments.takeAt(index); + tilesetDocument->wangSetModel()->disconnect(this); + } + endRemoveRows(); +} + +void WangSetModel::onTilesetRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) +{ + Q_UNUSED(parent) + Q_UNUSED(destination) + + beginMoveRows(QModelIndex(), start, end, QModelIndex(), row); + + if (start == row) + return; + + while (start <= end) { + mTilesetDocuments.move(start, row); + + if (row < start) { + ++start; + ++row; + } else { + --end; + } + } + + endMoveRows(); +} + +void WangSetModel::onTilesetLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) +{ + Q_UNUSED(parents) + Q_UNUSED(hint) + + // Make sure the tileset documents are still in the right order + for (int i = 0, rows = mTilesetDocuments.size(); i < rows; ++i) { + const QModelIndex index = mTilesetDocumentsModel->index(i, 0); + const QVariant var = mTilesetDocumentsModel->data(index, TilesetDocumentsModel::TilesetDocumentRole); + TilesetDocument *tilesetDocument = var.value(); + int currentIndex = mTilesetDocuments.indexOf(tilesetDocument); + if (currentIndex != i) { + Q_ASSERT(currentIndex > i); + onTilesetRowsMoved(QModelIndex(), currentIndex, currentIndex, QModelIndex(), i); + } + } +} + +void WangSetModel::onTilesetDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + emit dataChanged(index(topLeft.row(), topLeft.column()), + index(bottomRight.row(), bottomRight.column())); +} + +void WangSetModel::onWangSetAboutToBeAdded(Tileset *tileset) +{ + QModelIndex parent = index(tileset); + beginInsertRows(parent, tileset->wangSetCount(), tileset->wangSetCount()); +} + +void WangSetModel::onWangSetAdded(Tileset *tileset) +{ + endInsertRows(); + + const QModelIndex index = WangSetModel::index(tileset); + emit dataChanged(index, index); +} + +void WangSetModel::onWangSetAboutToBeRemoved(WangSet *wangSet) +{ + QModelIndex parent = index(wangSet->tileset()); + beginRemoveRows(parent, index(wangSet).row(), index(wangSet).row()); +} + +void WangSetModel::onWangSetRemoved(WangSet *wangSet) +{ + endRemoveRows(); + + const QModelIndex index = WangSetModel::index(wangSet->tileset()); + emit dataChanged(index, index); +} diff --git a/src/tiled/wangsetmodel.h b/src/tiled/wangsetmodel.h new file mode 100644 index 0000000000..7946c7c35c --- /dev/null +++ b/src/tiled/wangsetmodel.h @@ -0,0 +1,85 @@ +/* + * wangsetmodel.h + * Copyright 2017, Benjamin Trotter + * + * 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 . + */ + +#pragma once + +#include +#include + +namespace Tiled { + +class Tileset; +class WangSet; + +namespace Internal { + +class TilesetDocument; + +class WangSetModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum UserRoles { + WangSetRole = Qt::UserRole + }; + + WangSetModel(QAbstractItemModel *tilesetDocumentModel, + QObject *parent = nullptr); + ~WangSetModel(); + + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex index(Tileset *tileset) const; + QModelIndex index(WangSet *wangSet) const; + + QModelIndex parent(const QModelIndex &child) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + Tileset *tilesetAt(const QModelIndex &index) const; + WangSet *wangSetAt(const QModelIndex &index) const; + +private slots: + void onTilesetRowsInserted(const QModelIndex &parent, int first, int last); + void onTilesetRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void onTilesetRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); + void onTilesetLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); + void onTilesetDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + void onWangSetAboutToBeAdded(Tileset *tileset); + void onWangSetAdded(Tileset *tileset); + void onWangSetAboutToBeRemoved(WangSet *wangSet); + void onWangSetRemoved(WangSet *wangSet); + +private: + QAbstractItemModel *mTilesetDocumentsModel; + QList mTilesetDocuments; +}; + +} // namespace Internal +} // namespace Tiled diff --git a/src/tiled/wangsetview.cpp b/src/tiled/wangsetview.cpp new file mode 100644 index 0000000000..3ccbad225a --- /dev/null +++ b/src/tiled/wangsetview.cpp @@ -0,0 +1,128 @@ +/* + * wangsetview.cpp + * Copyright 2017, Benjamin Trotter + * + * 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 "wangsetview.h" + +#include "tileset.h" +#include "tilesetdocument.h" +#include "wangset.h" +#include "wangsetmodel.h" +#include "utils.h" +#include "zoomable.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace Tiled; +using namespace Tiled::Internal; + +WangSetView::WangSetView(QWidget *parent) + : QTreeView(parent) + , mZoomable(new Zoomable(this)) + , mTilesetDocument(nullptr) +{ + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setRootIsDecorated(false); + setIndentation(0); + setItemsExpandable(false); + setHeaderHidden(true); + + connect(mZoomable, SIGNAL(scaleChanged(qreal)), SLOT(adjustScale())); +} + +void WangSetView::setTilesetDocument(TilesetDocument *tilesetDocument) +{ + mTilesetDocument = tilesetDocument; +} + +WangSet *WangSetView::wangSetAt(const QModelIndex &index) const +{ + const QVariant data = model()->data(index, WangSetModel::WangSetRole); + return data.value(); +} + +bool WangSetView::event(QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride) { + if (static_cast(event)->key() == Qt::Key_Tab) { + if (indexWidget(currentIndex())) { + event->accept(); + return true; + } + } + } + + return QTreeView::event(event); +} + +void WangSetView::wheelEvent(QWheelEvent *event) +{ + if (event->modifiers() & Qt::ControlModifier + && event->orientation() == Qt::Vertical) + { + mZoomable->handleWheelDelta(event->delta()); + return; + } + + QTreeView::wheelEvent(event); +} + +void WangSetView::contextMenuEvent(QContextMenuEvent *event) +{ + WangSet *wangSet = wangSetAt(indexAt(event->pos())); + if (!wangSet) + return; + if (!mTilesetDocument) + return; + + QMenu menu; + + QIcon propIcon(QLatin1String(":images/16x16/document-properties.png")); + + QAction *wangSetProperties = menu.addAction(propIcon, + tr("Wang Set &Properties...")); + Utils::setThemeIcon(wangSetProperties, "document-properties"); + + connect(wangSetProperties, SIGNAL(triggered()), + SLOT(editWangSetProperties())); + + menu.exec(event->globalPos()); +} + +void WangSetView::editWangSetProperties() +{ + WangSet *wangSet = wangSetAt(selectionModel()->currentIndex()); + + if (!wangSet) + return; + + mTilesetDocument->setCurrentObject(wangSet); + emit mTilesetDocument->editCurrentObject(); +} + +void WangSetView::adjustScale() +{ +} + diff --git a/src/tiled/wangsetview.h b/src/tiled/wangsetview.h new file mode 100644 index 0000000000..9db2b046f3 --- /dev/null +++ b/src/tiled/wangsetview.h @@ -0,0 +1,62 @@ +/* + * wangsetview.h + * Copyright 2017, Benjamin Trotter + * + * 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 "wangsetmodel.h" + +#include + +namespace Tiled { +namespace Internal { + +class TilesetDocument; +class Zoomable; + +class WangSetView : public QTreeView +{ + Q_OBJECT + +public: + WangSetView(QWidget *parent = nullptr); + + void setTilesetDocument(TilesetDocument *tilesetDocument); + + Zoomable *zoomable() const { return mZoomable; } + + WangSet *wangSetAt(const QModelIndex &index) const; + +protected: + bool event(QEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + +private slots: + void editWangSetProperties(); + + void adjustScale(); + +private: + Zoomable *mZoomable; + TilesetDocument *mTilesetDocument; +}; + +} // namespace Internal +} // namespace Tiled + +Q_DECLARE_METATYPE(Tiled::Internal::WangSetView *)