diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui
index ae394040..76374514 100644
--- a/forms/paletteeditor.ui
+++ b/forms/paletteeditor.ui
@@ -173,6 +173,9 @@
+
+ false
+
Undo
@@ -187,6 +190,9 @@
+
+ false
+
Redo
diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui
index 371e8623..eb2580b7 100644
--- a/forms/tileseteditor.ui
+++ b/forms/tileseteditor.ui
@@ -65,7 +65,7 @@
0
0
- 253
+ 241
659
@@ -158,7 +158,7 @@
0
- 166
+ 190
@@ -194,171 +194,291 @@
false
-
- -
-
-
- true
-
-
-
- -
-
+
+
-
+
-
+
0
0
-
-
- 185
- 0
-
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
-
+
+
+ Raw Attributes Value
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
- -
-
-
- Metatile Label (Optional)
-
-
-
- -
-
-
- Bottom/Top
-
-
-
- -
-
-
- Terrain Type
-
-
-
- -
-
-
- Layer Type
-
-
-
- -
-
+
-
+
-
+
0
0
-
- QComboBox::InsertPolicy::NoInsert
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Layer Type
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 185
+ 0
+
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
- -
-
+
-
+
-
+
0
0
-
-
- 1
- 1
-
-
-
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
-
-
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
-
+
+
+ <html><head/><body><p>Copies the full metatile label to the clipboard.</p></body></html>
+
+
+ ...
+
+
+
+ :/icons/clipboard.ico:/icons/clipboard.ico
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ Metatile Label (Optional)
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
- -
-
+
-
+
-
+
0
0
-
- QComboBox::InsertPolicy::NoInsert
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
-
+
+
+ Terrain Type
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QComboBox::InsertPolicy::NoInsert
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
- -
-
-
- Qt::Orientation::Horizontal
-
-
- QSizePolicy::Policy::Maximum
-
-
-
- 10
- 20
-
-
-
-
- -
-
+
-
+
-
+
0
0
-
- QComboBox::InsertPolicy::NoInsert
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
-
+
+
+ Encounter Type
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QComboBox::InsertPolicy::NoInsert
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
- -
-
-
- Metatile Behavior
-
-
-
- -
-
-
- Raw Attributes Value
-
-
-
- -
-
-
- Encounter Type
-
-
-
- -
-
-
- <html><head/><body><p>Copies the full metatile label to the clipboard.</p></body></html>
-
-
- ...
-
-
-
- :/icons/clipboard.ico:/icons/clipboard.ico
-
-
-
- -
+
-
Qt::Orientation::Vertical
@@ -371,8 +491,119 @@
- -
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
-
+
+
+ Metatile Behavior
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QComboBox::InsertPolicy::NoInsert
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Bottom/Top
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 1
+
+
+
+ Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+
+
+ Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
+
@@ -451,12 +682,6 @@
-
-
-
- 0
- 0
-
-
98
@@ -497,16 +722,22 @@
-
+
+
+ 0
+ 0
+
+
- 18
- 18
+ 98
+ 98
98
- 34
+ 98
@@ -520,22 +751,22 @@
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
- -
-
-
- Qt::Orientation::Vertical
-
-
-
- 20
- 10
-
-
-
-
@@ -555,8 +786,8 @@
0
0
- 446
- 241
+ 458
+ 203
@@ -681,11 +912,20 @@
+
+
+ false
+
Undo
@@ -752,6 +995,9 @@
+
+ false
+
Redo
@@ -776,6 +1022,9 @@
+
+ false
+
Paste
@@ -863,6 +1112,33 @@
Secondary...
+
+
+ true
+
+
+ Horizontal
+
+
+
+
+ true
+
+
+ Vertical
+
+
+
+
+ true
+
+
+ Swap Metatiles
+
+
+ X
+
+
diff --git a/include/config.h b/include/config.h
index 25482457..1f2a1a14 100644
--- a/include/config.h
+++ b/include/config.h
@@ -108,6 +108,7 @@ public:
int metatilesZoom;
int tilesetEditorMetatilesZoom;
int tilesetEditorTilesZoom;
+ Qt::Orientation tilesetEditorLayerOrientation;
bool showPlayerView;
bool showCursorTile;
bool showBorder;
diff --git a/include/core/history.h b/include/core/history.h
index c999a4e1..ced8a590 100644
--- a/include/core/history.h
+++ b/include/core/history.h
@@ -25,6 +25,7 @@ public:
if (head > 0) {
return history.at(--head);
}
+ head = -1;
return NULL;
}
@@ -37,9 +38,7 @@ public:
void push(T commit) {
while (head + 1 < history.length()) {
- T item = history.last();
- history.removeLast();
- delete item;
+ delete history.takeLast();
}
if (saved > head) {
saved = -1;
@@ -48,7 +47,7 @@ public:
head++;
}
- T current() {
+ T current() const {
if (head < 0 || history.length() == 0) {
return NULL;
}
@@ -59,10 +58,30 @@ public:
saved = head;
}
- bool isSaved() {
+ bool isSaved() const {
return saved == head;
}
+ int length() const {
+ return history.length();
+ }
+
+ bool isEmpty() const {
+ return history.isEmpty();
+ }
+
+ int index() const {
+ return head;
+ }
+
+ bool canUndo() const {
+ return head >= 0;
+ }
+
+ bool canRedo() const {
+ return (head + 1) < history.length();
+ }
+
private:
QList history;
int head = -1;
diff --git a/include/core/maplayout.h b/include/core/maplayout.h
index 9f073d20..92f0264e 100644
--- a/include/core/maplayout.h
+++ b/include/core/maplayout.h
@@ -124,10 +124,13 @@ public:
bool isWithinBounds(const QRect &rect) const;
bool isWithinBorderBounds(int x, int y) const;
- bool getBlock(int x, int y, Block *out);
+ bool getBlock(int x, int y, Block *out) const;
void setBlock(int x, int y, Block block, bool enableScriptCallback = false);
void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false);
+ uint16_t getMetatileId(int x, int y) const;
+ bool setMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false);
+
void adjustDimensions(const QMargins &margins, bool setNewBlockdata = true);
void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true);
void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false);
diff --git a/include/core/tileset.h b/include/core/tileset.h
index 3d137056..c804a0f9 100644
--- a/include/core/tileset.h
+++ b/include/core/tileset.h
@@ -90,7 +90,11 @@ public:
uint16_t firstMetatileId() const;
uint16_t lastMetatileId() const;
- bool contains(uint16_t metatileId) const { return metatileId >= firstMetatileId() && metatileId <= lastMetatileId(); }
+ bool containsMetatileId(uint16_t metatileId) const { return metatileId >= firstMetatileId() && metatileId <= lastMetatileId(); }
+
+ uint16_t firstTileId() const;
+ uint16_t lastTileId() const;
+ bool containsTileId(uint16_t tileId) const { return tileId > firstTileId() && tileId <= lastTileId(); }
int numTiles() const { return m_tiles.length(); }
int maxTiles() const;
diff --git a/include/project.h b/include/project.h
index 1c14f996..0ea25a4a 100644
--- a/include/project.h
+++ b/include/project.h
@@ -109,6 +109,7 @@ public:
QStringList secondaryTilesetLabels;
QStringList tilesetLabelsOrdered;
QSet getPairedTilesetLabels(const Tileset *tileset) const;
+ QSet getTilesetLayoutIds(const Tileset *priamryTileset, const Tileset *secondaryTileset) const;
bool readMapGroups();
void addNewMapGroup(const QString &groupName);
diff --git a/include/ui/cursortilerect.h b/include/ui/cursortilerect.h
index 92f37f96..fd150dc2 100644
--- a/include/ui/cursortilerect.h
+++ b/include/ui/cursortilerect.h
@@ -43,7 +43,8 @@ public:
bool getSingleTileMode() const { return m_singleTileMode; }
void updateLocation(int x, int y);
- void updateSelectionSize(int width, int height);
+ void updateSelectionSize(const QSize &size);
+ void updateSelectionSize(int width, int height) { updateSelectionSize(QSize(width, height)); }
private:
const QSize m_tileSize;
diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h
index 51f850d9..9d908cc9 100644
--- a/include/ui/layoutpixmapitem.h
+++ b/include/ui/layoutpixmapitem.h
@@ -61,17 +61,17 @@ public:
void magicFill(
int initialX,
int initialY,
- QPoint selectionDimensions,
- QList selectedMetatiles,
- QList selectedCollisions,
+ const QSize &selectionDimensions,
+ const QList &selectedMetatiles,
+ const QList &selectedCollisions,
bool fromScriptCall = false);
void floodFill(int x, int y, bool fromScriptCall = false);
void floodFill(int x, int y, uint16_t metatileId, bool fromScriptCall = false);
void floodFill(int initialX,
int initialY,
- QPoint selectionDimensions,
- QList selectedMetatiles,
- QList selectedCollisions,
+ const QSize &selectionDimensions,
+ const QList &selectedMetatiles,
+ const QList &selectedCollisions,
bool fromScriptCall = false);
void floodFillSmartPath(int initialX, int initialY, bool fromScriptCall = false);
diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h
index ceea3ff8..7d1c74ce 100644
--- a/include/ui/metatilelayersitem.h
+++ b/include/ui/metatilelayersitem.h
@@ -9,23 +9,40 @@
class MetatileLayersItem: public SelectablePixmapItem {
Q_OBJECT
public:
- MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset);
+ MetatileLayersItem(Metatile *metatile,
+ Tileset *primaryTileset,
+ Tileset *secondaryTileset,
+ Qt::Orientation orientation = Qt::Horizontal);
void draw();
void setTilesets(Tileset*, Tileset*);
void setMetatile(Metatile*);
void clearLastModifiedCoords();
void clearLastHoveredCoords();
+
+ QPoint tileIndexToPos(int index) const { return this->tilePositions.value(index); }
+ int posToTileIndex(const QPoint &pos) const { return this->tilePositions.indexOf(pos); }
+ int posToTileIndex(int x, int y) const { return posToTileIndex(QPoint(x, y)); }
+
+ void setOrientation(Qt::Orientation orientation);
+
bool showGrid;
private:
Metatile* metatile;
Tileset *primaryTileset;
Tileset *secondaryTileset;
+ Qt::Orientation orientation;
QPoint prevChangedPos;
QPoint prevHoveredPos;
+
+ QList tilePositions;
+
QPoint getBoundedPos(const QPointF &);
+ void updateSelection();
+ void hover(const QPoint &pos);
signals:
- void tileChanged(int, int);
- void selectedTilesChanged(QPoint, int, int);
+ void tileChanged(const QPoint &pos);
+ void paletteChanged(const QPoint &pos);
+ void selectedTilesChanged(const QPoint &pos, const QSize &dimensions);
void hoveredTileChanged(const Tile &tile);
void hoveredTileCleared();
protected:
diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h
index edc3bf77..8bcb6d5c 100644
--- a/include/ui/metatileselector.h
+++ b/include/ui/metatileselector.h
@@ -22,7 +22,7 @@ struct CollisionSelectionItem
struct MetatileSelection
{
- QPoint dimensions;
+ QSize dimensions;
bool hasCollision;
QList metatileItems;
QList collisionItems;
@@ -43,7 +43,7 @@ public:
setAcceptHoverEvents(true);
}
- QPoint getSelectionDimensions() const override;
+ QSize getSelectionDimensions() const override;
void draw() override;
void refresh();
diff --git a/include/ui/paletteeditor.h b/include/ui/paletteeditor.h
index 95fc80b8..2d26cf71 100644
--- a/include/ui/paletteeditor.h
+++ b/include/ui/paletteeditor.h
@@ -44,7 +44,7 @@ private:
Tileset *secondaryTileset;
QList colorInputs;
- QList> palettesHistory;
+ QMap> palettesHistory;
QMap> unusedColorCache;
QPointer colorSearchWindow;
@@ -53,6 +53,7 @@ private:
void refreshPaletteId();
void commitEditHistory();
void commitEditHistory(int paletteId);
+ void updateEditHistoryActions();
void restoreWindowState();
void invalidateCache();
void closeEvent(QCloseEvent*);
diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h
index 8323f7e1..a267291e 100644
--- a/include/ui/selectablepixmapitem.h
+++ b/include/ui/selectablepixmapitem.h
@@ -19,9 +19,15 @@ public:
selectionOffsetX(0),
selectionOffsetY(0)
{}
- virtual QPoint getSelectionDimensions() const;
+ virtual QSize getSelectionDimensions() const { return QSize(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1); }
virtual void draw() = 0;
+ virtual void setMaxSelectionSize(const QSize &size) { setMaxSelectionSize(size.width(), size.height()); }
+ virtual void setMaxSelectionSize(int width, int height);
+ QSize maxSelectionSize() { return QSize(this->maxSelectionWidth, this->maxSelectionHeight); }
+
+ void setSelectionStyle(Qt::PenStyle style);
+
protected:
int cellWidth;
int cellHeight;
@@ -33,17 +39,28 @@ protected:
int selectionOffsetY;
QPoint getSelectionStart();
- void select(int x, int y, int width = 0, int height = 0);
- void select(const QPoint &pos, const QSize &size = QSize(0,0)) { select(pos.x(), pos.y(), size.width(), size.height()); }
- void updateSelection(int, int);
- QPoint getCellPos(QPointF);
+ void select(const QPoint &pos, const QSize &size = QSize(1,1));
+ void select(int x, int y, int width = 1, int height = 1) { select(QPoint(x, y), QSize(width, height)); }
+ void updateSelection(const QPoint &pos);
+ QPoint getCellPos(const QPointF &itemPos);
+ int getBoundedWidth(int width) const { return qBound(1, width, this->maxSelectionWidth); }
+ int getBoundedHeight(int height) const { return qBound(1, height, this->maxSelectionHeight); }
virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
+ virtual void drawSelectionRect(const QPoint &, const QSize &, Qt::PenStyle style = Qt::SolidLine);
virtual void drawSelection();
+ virtual int cellsWide() const { return this->cellWidth ? (pixmap().width() / this->cellWidth) : 0; }
+ virtual int cellsTall() const { return this->cellHeight ? (pixmap().height() / this->cellHeight) : 0; }
signals:
- void selectionChanged(int, int, int, int);
+ void selectionChanged(const QPoint&, const QSize&);
+
+private:
+ QPoint prevCellPos = QPoint(-1,-1);
+ Qt::PenStyle selectionStyle = Qt::SolidLine;
+
+ void setSelection(const QPoint &pos, const QSize &size);
};
#endif // SELECTABLEPIXMAPITEM_H
diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h
index 1266da14..80242ab8 100644
--- a/include/ui/tileseteditor.h
+++ b/include/ui/tileseteditor.h
@@ -20,6 +20,7 @@ class TilesetEditor;
class MetatileHistoryItem {
public:
+ MetatileHistoryItem() {};
MetatileHistoryItem(uint16_t metatileId, Metatile *prevMetatile, Metatile *newMetatile, QString prevLabel, QString newLabel) {
this->metatileId = metatileId;
this->prevMetatile = prevMetatile;
@@ -27,15 +28,24 @@ public:
this->prevLabel = prevLabel;
this->newLabel = newLabel;
}
+ MetatileHistoryItem(uint16_t metatileIdA, uint16_t metatileIdB) {
+ this->metatileId = metatileIdA;
+ this->swapMetatileId = metatileIdB;
+ this->isSwap = true;
+ }
~MetatileHistoryItem() {
delete this->prevMetatile;
delete this->newMetatile;
}
- uint16_t metatileId;
- Metatile *prevMetatile;
- Metatile *newMetatile;
+
+ uint16_t metatileId = 0;
+ Metatile *prevMetatile = nullptr;
+ Metatile *newMetatile = nullptr;
QString prevLabel;
QString newLabel;
+
+ uint16_t swapMetatileId = 0;
+ bool isSwap = false;
};
class TilesetEditor : public QMainWindow
@@ -69,8 +79,7 @@ private slots:
void onHoveredTileChanged(const Tile&);
void onHoveredTileChanged(uint16_t);
void onHoveredTileCleared();
- void onMetatileLayerTileChanged(int, int);
- void onMetatileLayerSelectionChanged(QPoint, int, int);
+ void onMetatileLayerSelectionChanged(const QPoint&, const QSize&);
void onPaletteEditorChangedPaletteColor();
void on_actionChange_Metatiles_Count_triggered();
@@ -87,9 +96,7 @@ private slots:
void on_actionUndo_triggered();
void on_actionRedo_triggered();
- void on_lineEdit_metatileLabel_editingFinished();
-
- void on_copyButton_metatileLabel_clicked();
+ void on_copyButton_MetatileLabel_clicked();
void on_actionCut_triggered();
void on_actionCopy_triggered();
@@ -123,8 +130,8 @@ private:
void countMetatileUsage();
void countTileUsage();
void copyMetatile(bool cut);
- void pasteMetatile(const Metatile * toPaste, QString label);
- bool replaceMetatile(uint16_t metatileId, const Metatile * src, QString label);
+ void pasteMetatile(const Metatile &toPaste, QString label);
+ bool replaceMetatile(uint16_t metatileId, const Metatile &src, QString label);
void commitMetatileChange(Metatile * prevMetatile);
void commitMetatileAndLabelChange(Metatile * prevMetatile, QString prevLabel);
uint32_t attributeNameToValue(Metatile::Attr attribute, const QString &text, bool *ok);
@@ -135,9 +142,19 @@ private:
void commitEncounterType();
void commitTerrainType();
void commitLayerType();
+ void commit(MetatileHistoryItem *item);
+ void updateEditHistoryActions();
void setRawAttributesVisible(bool visible);
void refreshTileFlips();
void refreshPaletteId();
+ void paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly = false);
+ void setMetatileLayerOrientation(Qt::Orientation orientation);
+ void commitMetatileSwap(uint16_t metatileIdA, uint16_t metatileIdB);
+ bool swapMetatiles(uint16_t metatileIdA, uint16_t metatileIdB);
+ void applyMetatileSwapToLayouts(uint16_t metatileIdA, uint16_t metatileIdB);
+ void applyMetatileSwapsToLayouts();
+ void rebuildMetatilePropertiesFrame();
+ void addWidgetToMetatileProperties(QWidget *w, int *row, int rowSpan);
Ui::TilesetEditor *ui;
History metatileHistory;
@@ -161,6 +178,8 @@ private:
bool lockSelection = false;
QSet metatileReloadQueue;
MetatileImageExporter::Settings *metatileImageExportSettings = nullptr;
+ QList> metatileIdSwaps;
+ int numLayerViewRows;
bool save();
diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h
index 6ca9088f..542a67c5 100644
--- a/include/ui/tileseteditormetatileselector.h
+++ b/include/ui/tileseteditormetatileselector.h
@@ -19,9 +19,13 @@ public:
bool select(uint16_t metatileId);
void setTilesets(Tileset*, Tileset*);
uint16_t getSelectedMetatileId() const { return this->selectedMetatileId; }
- void updateSelectedMetatile();
QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId) const;
+ void setSwapMode(bool enabeled);
+ void addToSwapSelection(uint16_t metatileId);
+ void removeFromSwapSelection(uint16_t metatileId);
+ void clearSwapSelection();
+
QVector usedMetatiles;
bool selectorShowUnused = false;
bool selectorShowCounts = false;
@@ -41,12 +45,18 @@ private:
QPixmap basePixmap;
Tileset *primaryTileset = nullptr;
Tileset *secondaryTileset = nullptr;
- uint16_t selectedMetatileId;
+ uint16_t selectedMetatileId = 0;
+ QPoint prevCellPos = QPoint(-1,-1);
+
+ QList swapMetatileIds;
+ uint16_t lastHoveredMetatileId = 0;
+ bool inSwapMode = false;
+
void updateBasePixmap();
uint16_t posToMetatileId(int x, int y, bool *ok = nullptr) const;
uint16_t posToMetatileId(const QPoint &pos, bool *ok = nullptr) const;
QPoint metatileIdToPos(uint16_t metatileId, bool *ok = nullptr) const;
- bool shouldAcceptEvent(QGraphicsSceneMouseEvent*);
+ bool isValidMetatileId(uint16_t metatileId) const;
int numRows(int numMetatiles) const;
int numRows() const;
void drawGrid();
@@ -60,6 +70,7 @@ signals:
void hoveredMetatileChanged(uint16_t);
void hoveredMetatileCleared();
void selectedMetatileChanged(uint16_t);
+ void swapRequested(uint16_t, uint16_t);
};
#endif // TILESETEDITORMETATILESELECTOR_H
diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h
index 0ad22b97..c98239be 100644
--- a/include/ui/tileseteditortileselector.h
+++ b/include/ui/tileseteditortileselector.h
@@ -7,8 +7,8 @@
class TilesetEditorTileSelector: public SelectablePixmapItem {
Q_OBJECT
public:
- TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, int numLayers)
- : SelectablePixmapItem(16, 16, numLayers * Metatile::tileWidth(), Metatile::tileHeight()) {
+ TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset)
+ : SelectablePixmapItem(16, 16, Metatile::tileWidth(), Metatile::tileWidth()) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
this->numTilesWide = 16;
@@ -18,7 +18,8 @@ public:
this->paletteChanged = false;
setAcceptHoverEvents(true);
}
- QPoint getSelectionDimensions() const override;
+ QSize getSelectionDimensions() const override;
+ void setMaxSelectionSize(int width, int height) override;
void draw() override;
void select(uint16_t metatileId);
void highlight(uint16_t metatileId);
@@ -26,7 +27,7 @@ public:
void setPaletteId(int);
void setTileFlips(bool, bool);
QList getSelectedTiles();
- void setExternalSelection(int, int, QList, QList);
+ void setExternalSelection(int, int, const QList&);
QPoint getTileCoordsOnWidget(uint16_t);
QImage buildPrimaryTilesIndexedImage();
QImage buildSecondaryTilesIndexedImage();
@@ -48,7 +49,7 @@ private:
int externalSelectionWidth;
int externalSelectionHeight;
QList externalSelectedTiles;
- QList externalSelectedPos;
+ QPoint prevCellPos = QPoint(-1,-1);
Tileset *primaryTileset;
Tileset *secondaryTileset;
@@ -62,7 +63,7 @@ private:
uint16_t getTileId(int x, int y);
QPoint getTileCoords(uint16_t);
QList getCurPaletteTable();
- QList buildSelectedTiles(int, int, QList);
+ QList buildSelectedTiles(int, int, const QList&);
QImage buildImage(int tileIdStart, int numTiles);
void updateBasePixmap();
void drawUnused();
diff --git a/resources/icons/swap_cursor.ico b/resources/icons/swap_cursor.ico
new file mode 100644
index 00000000..d352b1f5
Binary files /dev/null and b/resources/icons/swap_cursor.ico differ
diff --git a/resources/images.qrc b/resources/images.qrc
index 2891ff3c..856a3fbe 100644
--- a/resources/images.qrc
+++ b/resources/images.qrc
@@ -44,6 +44,7 @@
icons/refresh.ico
icons/shift_cursor.ico
icons/shift.ico
+ icons/swap_cursor.ico
icons/tall_grass.ico
icons/warning.ico
icons/minimap.ico
diff --git a/src/config.cpp b/src/config.cpp
index b53f171b..51d63682 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -338,6 +338,7 @@ void PorymapConfig::reset() {
this->metatilesZoom = 30;
this->tilesetEditorMetatilesZoom = 30;
this->tilesetEditorTilesZoom = 30;
+ this->tilesetEditorLayerOrientation = Qt::Vertical;
this->showPlayerView = false;
this->showCursorTile = true;
this->showBorder = true;
@@ -454,6 +455,9 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
this->tilesetEditorMetatilesZoom = getConfigInteger(key, value, 10, 100, 30);
} else if (key == "tileset_editor_tiles_zoom") {
this->tilesetEditorTilesZoom = getConfigInteger(key, value, 10, 100, 30);
+ } else if (key == "tileset_editor_layer_orientation") {
+ // Being explicit here to avoid casting out-of-range values.
+ this->tilesetEditorLayerOrientation = (getConfigInteger(key, value) == static_cast(Qt::Horizontal)) ? Qt::Horizontal : Qt::Vertical;
} else if (key == "show_player_view") {
this->showPlayerView = getConfigBool(key, value);
} else if (key == "show_cursor_tile") {
@@ -604,6 +608,7 @@ QMap PorymapConfig::getKeyValueMap() {
map.insert("metatiles_zoom", QString::number(this->metatilesZoom));
map.insert("tileset_editor_metatiles_zoom", QString::number(this->tilesetEditorMetatilesZoom));
map.insert("tileset_editor_tiles_zoom", QString::number(this->tilesetEditorTilesZoom));
+ map.insert("tileset_editor_layer_orientation", QString::number(this->tilesetEditorLayerOrientation));
map.insert("show_player_view", this->showPlayerView ? "1" : "0");
map.insert("show_cursor_tile", this->showCursorTile ? "1" : "0");
map.insert("show_border", this->showBorder ? "1" : "0");
diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp
index a965ddb8..b80bad0f 100644
--- a/src/core/maplayout.cpp
+++ b/src/core/maplayout.cpp
@@ -99,7 +99,7 @@ QRect Layout::getVisibleRect() const {
return area += Project::getPixelViewDistance();
}
-bool Layout::getBlock(int x, int y, Block *out) {
+bool Layout::getBlock(int x, int y, Block *out) const {
if (isWithinBounds(x, y)) {
int i = y * getWidth() + x;
*out = this->blockdata.value(i);
@@ -134,6 +134,20 @@ void Layout::setBlockdata(Blockdata newBlockdata, bool enableScriptCallback) {
}
}
+uint16_t Layout::getMetatileId(int x, int y) const {
+ Block block;
+ return getBlock(x, y, &block) ? block.metatileId() : 0;
+}
+
+bool Layout::setMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) {
+ Block block;
+ if (!getBlock(x, y, &block)) {
+ return false;
+ }
+ setBlock(x, y, Block(metatileId, block.collision(), block.elevation()), enableScriptCallback);
+ return true;
+}
+
void Layout::clearBorderCache() {
this->cached_border.clear();
}
diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp
index d76b0138..0aa1f7d6 100644
--- a/src/core/tileset.cpp
+++ b/src/core/tileset.cpp
@@ -107,6 +107,14 @@ int Tileset::maxMetatiles() const {
return this->is_secondary ? Project::getNumMetatilesSecondary() : Project::getNumMetatilesPrimary();
}
+uint16_t Tileset::firstTileId() const {
+ return this->is_secondary ? Project::getNumTilesPrimary() : 0;
+}
+
+uint16_t Tileset::lastTileId() const {
+ return qMax(1, firstMetatileId() + m_tiles.length()) - 1;
+}
+
int Tileset::maxTiles() const {
return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary();
}
@@ -250,8 +258,8 @@ QString Tileset::getMetatileLabelPrefix(const QString &name)
}
bool Tileset::metatileIsValid(uint16_t metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
- return (primaryTileset && primaryTileset->contains(metatileId))
- || (secondaryTileset && secondaryTileset->contains(metatileId));
+ return (primaryTileset && primaryTileset->containsMetatileId(metatileId))
+ || (secondaryTileset && secondaryTileset->containsMetatileId(metatileId));
}
QList> Tileset::getBlockPalettes(const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) {
diff --git a/src/editor.cpp b/src/editor.cpp
index b4f97576..9c6a7d0a 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1120,8 +1120,7 @@ void Editor::onHoveredMetatileSelectionCleared() {
}
void Editor::onSelectedMetatilesChanged() {
- QPoint size = this->metatile_selector_item->getSelectionDimensions();
- this->cursorMapTileRect->updateSelectionSize(size.x(), size.y());
+ this->cursorMapTileRect->updateSelectionSize(this->metatile_selector_item->getSelectionDimensions());
this->redrawCurrentMetatilesSelection();
}
@@ -1774,8 +1773,8 @@ void Editor::displayMovementPermissionSelector() {
this, &Editor::onHoveredMovementPermissionChanged);
connect(movement_permissions_selector_item, &MovementPermissionsSelector::hoveredMovementPermissionCleared,
this, &Editor::onHoveredMovementPermissionCleared);
- connect(movement_permissions_selector_item, &SelectablePixmapItem::selectionChanged, [this](int x, int y, int, int) {
- this->setCollisionTabSpinBoxes(x, y);
+ connect(movement_permissions_selector_item, &SelectablePixmapItem::selectionChanged, [this](const QPoint &pos, const QSize&) {
+ this->setCollisionTabSpinBoxes(pos.x(), pos.y());
});
movement_permissions_selector_item->select(projectConfig.defaultCollision, projectConfig.defaultElevation);
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 8ba318b9..f3bc15f8 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1787,7 +1787,7 @@ void MainWindow::redrawMetatileSelection() {
void MainWindow::scrollMetatileSelectorToSelection() {
// Internal selections or 1x1 external selections can be scrolled to
- if (!editor->metatile_selector_item->isInternalSelection() && editor->metatile_selector_item->getSelectionDimensions() != QPoint(1, 1))
+ if (!editor->metatile_selector_item->isInternalSelection() && editor->metatile_selector_item->getSelectionDimensions() != QSize(1, 1))
return;
MetatileSelection selection = editor->metatile_selector_item->getMetatileSelection();
@@ -1795,8 +1795,8 @@ void MainWindow::scrollMetatileSelectorToSelection() {
return;
QPoint pos = editor->metatile_selector_item->getMetatileIdCoordsOnWidget(selection.metatileItems.first().metatileId);
- QPoint size = editor->metatile_selector_item->getSelectionDimensions();
- pos += QPoint((size.x() - 1) * Metatile::pixelWidth(), (size.y() - 1) * Metatile::pixelHeight()) / 2; // We want to focus on the center of the whole selection
+ QSize size = editor->metatile_selector_item->getSelectionDimensions();
+ pos += QPoint((size.width() - 1) * Metatile::pixelWidth(), (size.height() - 1) * Metatile::pixelHeight()) / 2; // We want to focus on the center of the whole selection
pos *= getMetatilesZoomScale();
auto viewport = ui->scrollArea_MetatileSelector->viewport();
@@ -1970,8 +1970,8 @@ void MainWindow::copy() {
}
copyObject["metatile_selection"] = metatiles;
copyObject["collision_selection"] = collisions;
- copyObject["width"] = editor->metatile_selector_item->getSelectionDimensions().x();
- copyObject["height"] = editor->metatile_selector_item->getSelectionDimensions().y();
+ copyObject["width"] = editor->metatile_selector_item->getSelectionDimensions().width();
+ copyObject["height"] = editor->metatile_selector_item->getSelectionDimensions().height();
setClipboardData(copyObject);
logInfo("Copied metatile selection to clipboard");
}
diff --git a/src/project.cpp b/src/project.cpp
index 9c5fd9af..f26a49ad 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1187,6 +1187,18 @@ bool Project::loadLayoutTilesets(Layout *layout) {
logError(QString("Failed to load %1: missing secondary tileset label.").arg(layout->name));
return false;
}
+ if (!this->primaryTilesetLabels.contains(layout->tileset_primary_label)) {
+ logError(QString("Failed to load %1: unknown primary tileset label '%2'.")
+ .arg(layout->name)
+ .arg(layout->tileset_primary_label));
+ return false;
+ }
+ if (!this->secondaryTilesetLabels.contains(layout->tileset_secondary_label)) {
+ logError(QString("Failed to load %1: unknown secondary tileset label '%2'.")
+ .arg(layout->name)
+ .arg(layout->tileset_secondary_label));
+ return false;
+ }
layout->tileset_primary = getTileset(layout->tileset_primary_label);
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
@@ -1194,6 +1206,11 @@ bool Project::loadLayoutTilesets(Layout *layout) {
}
Tileset* Project::getTileset(const QString &label, bool forceLoad) {
+ if (!this->tilesetLabelsOrdered.contains(label)) {
+ logError(QString("Unknown tileset name '%1'.").arg(label));
+ return nullptr;
+ }
+
Tileset *tileset = nullptr;
auto it = this->tilesetCache.constFind(label);
@@ -3546,3 +3563,19 @@ QSet Project::getPairedTilesetLabels(const Tileset *tileset) const {
}
return pairedLabels;
}
+
+// Returns the set of IDs for the layouts that use the specified tilesets.
+// nullptr for either tileset is treated as a wildcard (so 'getTilesetLayouts(nullptr, nullptr)' returns all layout IDs).
+QSet Project::getTilesetLayoutIds(const Tileset *primaryTileset, const Tileset *secondaryTileset) const {
+ // Note: We're intentioanlly just returning the layout IDs, and not the pointer to the layout.
+ // The layout may not be loaded yet (which isn't obvious), and we should leave it up to the caller to request that.
+ QSet layoutIds;
+ for (const auto &layout : this->mapLayouts) {
+ if (primaryTileset && primaryTileset->name != layout->tileset_primary_label)
+ continue;
+ if (secondaryTileset && secondaryTileset->name != layout->tileset_secondary_label)
+ continue;
+ layoutIds.insert(layout->id);
+ }
+ return layoutIds;
+}
diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp
index 472dd23a..d87a2b0b 100644
--- a/src/scriptapi/apimap.cpp
+++ b/src/scriptapi/apimap.cpp
@@ -96,21 +96,15 @@ void MainWindow::setBlocksFromSelection(int x, int y, bool forceRedraw, bool com
int MainWindow::getMetatileId(int x, int y) {
if (!this->editor || !this->editor->layout)
return 0;
- Block block;
- if (!this->editor->layout->getBlock(x, y, &block)) {
- return 0;
- }
- return block.metatileId();
+ return this->editor->layout->getMetatileId(x, y);
}
void MainWindow::setMetatileId(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) {
if (!this->editor || !this->editor->layout)
return;
- Block block;
- if (!this->editor->layout->getBlock(x, y, &block)) {
+ if (!this->editor->layout->setMetatileId(x, y, metatileId)) {
return;
}
- this->editor->layout->setBlock(x, y, Block(metatileId, block.collision(), block.elevation()));
this->tryCommitMapChanges(commitChanges);
this->tryRedrawMapArea(forceRedraw);
}
diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp
index ef3c33db..46b0a18c 100644
--- a/src/ui/bordermetatilespixmapitem.cpp
+++ b/src/ui/bordermetatilespixmapitem.cpp
@@ -20,9 +20,9 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
Blockdata oldBorder = layout->border;
- for (int i = 0; i < selection.dimensions.x() && (i + pos.x()) < width; i++) {
- for (int j = 0; j < selection.dimensions.y() && (j + pos.y()) < height; j++) {
- MetatileSelectionItem item = selection.metatileItems.at(j * selection.dimensions.x() + i);
+ for (int i = 0; i < selection.dimensions.width() && (i + pos.x()) < width; i++) {
+ for (int j = 0; j < selection.dimensions.height() && (j + pos.y()) < height; j++) {
+ MetatileSelectionItem item = selection.metatileItems.at(j * selection.dimensions.width() + i);
layout->setBorderMetatileId(pos.x() + i, pos.y() + j, item.metatileId, true);
}
}
diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp
index 33425131..627c5e12 100644
--- a/src/ui/currentselectedmetatilespixmapitem.cpp
+++ b/src/ui/currentselectedmetatilespixmapitem.cpp
@@ -3,18 +3,18 @@
#include
QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) {
- int width = selection.dimensions.x() * Metatile::pixelWidth();
- int height = selection.dimensions.y() * Metatile::pixelHeight();
+ int width = selection.dimensions.width() * Metatile::pixelWidth();
+ int height = selection.dimensions.height() * Metatile::pixelHeight();
QImage image(width, height, QImage::Format_RGBA8888);
image.fill(QColor(0, 0, 0, 0));
QPainter painter(&image);
- for (int i = 0; i < selection.dimensions.x(); i++) {
- for (int j = 0; j < selection.dimensions.y(); j++) {
+ for (int i = 0; i < selection.dimensions.width(); i++) {
+ for (int j = 0; j < selection.dimensions.height(); j++) {
int x = i * Metatile::pixelWidth();
int y = j * Metatile::pixelHeight();
QPoint metatile_origin = QPoint(x, y);
- int index = j * selection.dimensions.x() + i;
+ int index = j * selection.dimensions.width() + i;
MetatileSelectionItem item = selection.metatileItems.at(index);
if (item.enabled) {
QImage metatile_image = getMetatileImage(item.metatileId, layout);
diff --git a/src/ui/cursortilerect.cpp b/src/ui/cursortilerect.cpp
index e62bcdc8..8b93ada1 100644
--- a/src/ui/cursortilerect.cpp
+++ b/src/ui/cursortilerect.cpp
@@ -39,8 +39,8 @@ void CursorTileRect::stopRightClickSelectionAnchor() {
m_rightClickSelectionAnchored = false;
}
-void CursorTileRect::updateSelectionSize(int width, int height) {
- m_selectionSize = QSize(width, height).expandedTo(QSize(1,1));
+void CursorTileRect::updateSelectionSize(const QSize &size) {
+ m_selectionSize = size.expandedTo(QSize(1,1)); // Enforce minimum of 1x1 cell
prepareGeometryChange();
update();
}
diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp
index 9f8a8c04..c7489598 100644
--- a/src/ui/layoutpixmapitem.cpp
+++ b/src/ui/layoutpixmapitem.cpp
@@ -25,15 +25,15 @@ void LayoutPixmapItem::paint(QGraphicsSceneMouseEvent *event) {
// Paint onto the map.
bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
- QPoint selectionDimensions = this->metatileSelector->getSelectionDimensions();
+ QSize selectionDimensions = this->metatileSelector->getSelectionDimensions();
if (settings->smartPathsEnabled) {
- if (!shiftPressed && selectionDimensions.x() == 3 && selectionDimensions.y() == 3) {
+ if (!shiftPressed && selectionDimensions == QSize(3,3)) {
paintSmartPath(pos.x(), pos.y());
} else {
paintNormal(pos.x(), pos.y());
}
} else {
- if (shiftPressed && selectionDimensions.x() == 3 && selectionDimensions.y() == 3) {
+ if (shiftPressed && selectionDimensions == QSize(3,3)) {
paintSmartPath(pos.x(), pos.y());
} else {
paintNormal(pos.x(), pos.y());
@@ -110,22 +110,22 @@ void LayoutPixmapItem::paintNormal(int x, int y, bool fromScriptCall) {
// This allows painting via dragging the mouse to tile the painted region.
int xDiff = x - initialX;
int yDiff = y - initialY;
- if (xDiff < 0 && xDiff % selection.dimensions.x() != 0) xDiff -= selection.dimensions.x();
- if (yDiff < 0 && yDiff % selection.dimensions.y() != 0) yDiff -= selection.dimensions.y();
+ if (xDiff < 0 && xDiff % selection.dimensions.width() != 0) xDiff -= selection.dimensions.width();
+ if (yDiff < 0 && yDiff % selection.dimensions.height() != 0) yDiff -= selection.dimensions.height();
- x = initialX + (xDiff / selection.dimensions.x()) * selection.dimensions.x();
- y = initialY + (yDiff / selection.dimensions.y()) * selection.dimensions.y();
+ x = initialX + (xDiff / selection.dimensions.width()) * selection.dimensions.width();
+ y = initialY + (yDiff / selection.dimensions.height()) * selection.dimensions.height();
// for edit history
Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata();
- for (int i = 0; i < selection.dimensions.x() && i + x < this->layout->getWidth(); i++)
- for (int j = 0; j < selection.dimensions.y() && j + y < this->layout->getHeight(); j++) {
+ for (int i = 0; i < selection.dimensions.width() && i + x < this->layout->getWidth(); i++)
+ for (int j = 0; j < selection.dimensions.height() && j + y < this->layout->getHeight(); j++) {
int actualX = i + x;
int actualY = j + y;
Block block;
if (this->layout->getBlock(actualX, actualY, &block)) {
- int index = j * selection.dimensions.x() + i;
+ int index = j * selection.dimensions.width() + i;
MetatileSelectionItem item = selection.metatileItems.at(index);
if (!item.enabled)
continue;
@@ -178,7 +178,7 @@ bool isSmartPathTile(QList metatileItems, uint16_t metati
}
bool isValidSmartPathSelection(MetatileSelection selection) {
- if (selection.dimensions.x() != 3 || selection.dimensions.y() != 3)
+ if (selection.dimensions != QSize(3,3))
return false;
for (int i = 0; i < selection.metatileItems.length(); i++) {
@@ -377,7 +377,7 @@ void LayoutPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) {
int metatileId = selection.metatileItems.first().metatileId;
if (selection.metatileItems.count() > 1 || (this->layout->getBlock(pos.x(), pos.y(), &block) && block.metatileId() != metatileId)) {
bool smartPathsEnabled = event->modifiers() & Qt::ShiftModifier;
- if ((this->settings->smartPathsEnabled || smartPathsEnabled) && selection.dimensions.x() == 3 && selection.dimensions.y() == 3)
+ if ((this->settings->smartPathsEnabled || smartPathsEnabled) && selection.dimensions == QSize(3,3))
this->floodFillSmartPath(pos.x(), pos.y());
else
this->floodFill(pos.x(), pos.y());
@@ -398,7 +398,7 @@ void LayoutPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) {
}
void LayoutPixmapItem::magicFill(int x, int y, uint16_t metatileId, bool fromScriptCall) {
- QPoint selectionDimensions(1, 1);
+ QSize selectionDimensions(1, 1);
QList selectedMetatiles = QList({MetatileSelectionItem{ true, metatileId }});
this->magicFill(x, y, selectionDimensions, selectedMetatiles, QList(), fromScriptCall);
}
@@ -411,9 +411,9 @@ void LayoutPixmapItem::magicFill(int x, int y, bool fromScriptCall) {
void LayoutPixmapItem::magicFill(
int initialX,
int initialY,
- QPoint selectionDimensions,
- QList selectedMetatiles,
- QList selectedCollisions,
+ const QSize &selectionDimensions,
+ const QList &selectedMetatiles,
+ const QList &selectedCollisions,
bool fromScriptCall) {
Block block;
if (this->layout->getBlock(initialX, initialY, &block)) {
@@ -430,11 +430,11 @@ void LayoutPixmapItem::magicFill(
if (this->layout->getBlock(x, y, &block) && block.metatileId() == metatileId) {
int xDiff = x - initialX;
int yDiff = y - initialY;
- int i = xDiff % selectionDimensions.x();
- int j = yDiff % selectionDimensions.y();
- if (i < 0) i = selectionDimensions.x() + i;
- if (j < 0) j = selectionDimensions.y() + j;
- int index = j * selectionDimensions.x() + i;
+ int i = xDiff % selectionDimensions.width();
+ int j = yDiff % selectionDimensions.height();
+ if (i < 0) i = selectionDimensions.width() + i;
+ if (j < 0) j = selectionDimensions.height() + j;
+ int index = j * selectionDimensions.width() + i;
if (selectedMetatiles.at(index).enabled) {
block.setMetatileId(selectedMetatiles.at(index).metatileId);
if (setCollisions) {
@@ -460,7 +460,7 @@ void LayoutPixmapItem::floodFill(int initialX, int initialY, bool fromScriptCall
}
void LayoutPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId, bool fromScriptCall) {
- QPoint selectionDimensions(1, 1);
+ QSize selectionDimensions(1, 1);
QList selectedMetatiles = QList({MetatileSelectionItem{true, metatileId}});
this->floodFill(initialX, initialY, selectionDimensions, selectedMetatiles, QList(), fromScriptCall);
}
@@ -468,9 +468,9 @@ void LayoutPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId
void LayoutPixmapItem::floodFill(
int initialX,
int initialY,
- QPoint selectionDimensions,
- QList selectedMetatiles,
- QList selectedCollisions,
+ const QSize &selectionDimensions,
+ const QList &selectedMetatiles,
+ const QList &selectedCollisions,
bool fromScriptCall) {
bool setCollisions = selectedCollisions.length() == selectedMetatiles.length();
Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata();
@@ -490,11 +490,11 @@ void LayoutPixmapItem::floodFill(
visited.insert(x + y * this->layout->getWidth());
int xDiff = x - initialX;
int yDiff = y - initialY;
- int i = xDiff % selectionDimensions.x();
- int j = yDiff % selectionDimensions.y();
- if (i < 0) i = selectionDimensions.x() + i;
- if (j < 0) j = selectionDimensions.y() + j;
- int index = j * selectionDimensions.x() + i;
+ int i = xDiff % selectionDimensions.width();
+ int j = yDiff % selectionDimensions.height();
+ if (i < 0) i = selectionDimensions.width() + i;
+ if (j < 0) j = selectionDimensions.height() + j;
+ int index = j * selectionDimensions.width() + i;
uint16_t metatileId = selectedMetatiles.at(index).metatileId;
uint16_t old_metatileId = block.metatileId();
if (selectedMetatiles.at(index).enabled && (selectedMetatiles.count() != 1 || old_metatileId != metatileId)) {
diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp
index bae09ee6..8ec0cceb 100644
--- a/src/ui/metatilelayersitem.cpp
+++ b/src/ui/metatilelayersitem.cpp
@@ -3,8 +3,8 @@
#include "imageproviders.h"
#include
-MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset)
- : SelectablePixmapItem(16, 16, Metatile::tileWidth() * projectConfig.getNumLayersInMetatile(), Metatile::tileHeight()),
+MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset, Qt::Orientation orientation)
+ : SelectablePixmapItem(16, 16, Metatile::tileWidth(), Metatile::tileHeight()),
metatile(metatile),
primaryTileset(primaryTileset),
secondaryTileset(secondaryTileset)
@@ -12,28 +12,51 @@ MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTiles
clearLastModifiedCoords();
clearLastHoveredCoords();
setAcceptHoverEvents(true);
+ setOrientation(orientation);
}
-static const QList tilePositions = {
- QPoint(0, 0),
- QPoint(1, 0),
- QPoint(0, 1),
- QPoint(1, 1),
- QPoint(2, 0),
- QPoint(3, 0),
- QPoint(2, 1),
- QPoint(3, 1),
- QPoint(4, 0),
- QPoint(5, 0),
- QPoint(4, 1),
- QPoint(5, 1),
-};
+void MetatileLayersItem::setOrientation(Qt::Orientation orientation) {
+ this->orientation = orientation;
+ int maxWidth = Metatile::tileWidth();
+ int maxHeight = Metatile::tileHeight();
+
+ // Generate a table of tile positions that allows us to map between
+ // the index of a tile in the metatile and its position in this layer view.
+ this->tilePositions.clear();
+ if (this->orientation == Qt::Horizontal) {
+ // Tiles are laid out horizontally, with the bottom layer on the left:
+ // 0 1 4 5 8 9
+ // 2 3 6 7 10 11
+ for (int layer = 0; layer < projectConfig.getNumLayersInMetatile(); layer++)
+ for (int y = 0; y < Metatile::tileHeight(); y++)
+ for (int x = 0; x < Metatile::tileWidth(); x++) {
+ this->tilePositions.append(QPoint(x + layer * Metatile::tileWidth(), y));
+ }
+ maxWidth *= projectConfig.getNumLayersInMetatile();
+ } else if (this->orientation == Qt::Vertical) {
+ // Tiles are laid out vertically, with the bottom layer on the bottom:
+ // 8 9
+ // 10 11
+ // 4 5
+ // 6 7
+ // 0 1
+ // 2 3
+ for (int layer = projectConfig.getNumLayersInMetatile() - 1; layer >= 0; layer--)
+ for (int y = 0; y < Metatile::tileHeight(); y++)
+ for (int x = 0; x < Metatile::tileWidth(); x++) {
+ this->tilePositions.append(QPoint(x, y + layer * Metatile::tileHeight()));
+ }
+ maxHeight *= projectConfig.getNumLayersInMetatile();
+ }
+ setMaxSelectionSize(maxWidth, maxHeight);
+ update();
+ if (!this->pixmap().isNull()) {
+ draw();
+ }
+}
void MetatileLayersItem::draw() {
- const int numLayers = projectConfig.getNumLayersInMetatile();
- const int layerWidth = this->cellWidth * Metatile::tileWidth();
- const int layerHeight = this->cellHeight * Metatile::tileHeight();
- QPixmap pixmap(numLayers * layerWidth, layerHeight);
+ QPixmap pixmap(this->cellWidth * this->maxSelectionWidth, this->cellHeight * this->maxSelectionHeight);
QPainter painter(&pixmap);
// Draw tile images
@@ -47,15 +70,22 @@ void MetatileLayersItem::draw() {
true
).scaled(this->cellWidth, this->cellHeight);
tile.flip(&tileImage);
- QPoint pos = tilePositions.at(i);
+ QPoint pos = tileIndexToPos(i);
painter.drawImage(pos.x() * this->cellWidth, pos.y() * this->cellHeight, tileImage);
}
if (this->showGrid) {
// Draw grid
painter.setPen(Qt::white);
- for (int i = 1; i < numLayers; i++) {
- int x = i * layerWidth;
- painter.drawLine(x, 0, x, layerHeight);
+ const int layerWidth = this->cellWidth * Metatile::tileWidth();
+ const int layerHeight = this->cellHeight * Metatile::tileHeight();
+ for (int i = 1; i < projectConfig.getNumLayersInMetatile(); i++) {
+ if (this->orientation == Qt::Vertical) {
+ int y = i * layerHeight;
+ painter.drawLine(0, y, layerWidth, y);
+ } else if (this->orientation == Qt::Horizontal) {
+ int x = i * layerWidth;
+ painter.drawLine(x, 0, x, layerHeight);
+ }
}
}
@@ -76,57 +106,64 @@ void MetatileLayersItem::setTilesets(Tileset *primaryTileset, Tileset *secondary
this->clearLastHoveredCoords();
}
+void MetatileLayersItem::updateSelection() {
+ drawSelection();
+ emit selectedTilesChanged(getSelectionStart(), getSelectionDimensions());
+}
+
void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
+ const QPoint pos = this->getBoundedPos(event->pos());
+ hover(pos);
+
if (event->buttons() & Qt::RightButton) {
SelectablePixmapItem::mousePressEvent(event);
- QPoint selectionOrigin = this->getSelectionStart();
- QPoint dimensions = this->getSelectionDimensions();
- emit this->selectedTilesChanged(selectionOrigin, dimensions.x(), dimensions.y());
- this->drawSelection();
+ updateSelection();
+ } else if (event->modifiers() & Qt::ControlModifier) {
+ emit paletteChanged(pos);
} else {
- const QPoint pos = this->getBoundedPos(event->pos());
- this->prevChangedPos = pos;
- this->clearLastHoveredCoords();
- emit this->tileChanged(pos.x(), pos.y());
+ emit tileChanged(pos);
}
+ this->prevChangedPos = pos;
}
void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
+ const QPoint pos = this->getBoundedPos(event->pos());
+ if (this->prevChangedPos == pos)
+ return;
+ hover(pos);
+
if (event->buttons() & Qt::RightButton) {
SelectablePixmapItem::mouseMoveEvent(event);
- QPoint selectionOrigin = this->getSelectionStart();
- QPoint dimensions = this->getSelectionDimensions();
- emit this->selectedTilesChanged(selectionOrigin, dimensions.x(), dimensions.y());
- this->drawSelection();
+ updateSelection();
+ } else if (event->modifiers() & Qt::ControlModifier) {
+ emit paletteChanged(pos);
} else {
- const QPoint pos = this->getBoundedPos(event->pos());
- if (prevChangedPos != pos) {
- this->prevChangedPos = pos;
- this->clearLastHoveredCoords();
- emit this->tileChanged(pos.x(), pos.y());
- }
+ emit tileChanged(pos);
}
+ this->prevChangedPos = pos;
}
void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
if (event->buttons() & Qt::RightButton) {
SelectablePixmapItem::mouseReleaseEvent(event);
- QPoint selectionOrigin = this->getSelectionStart();
- QPoint dimensions = this->getSelectionDimensions();
- emit this->selectedTilesChanged(selectionOrigin, dimensions.x(), dimensions.y());
+ updateSelection();
}
- this->draw();
+ // Clear selection rectangle
+ draw();
}
void MetatileLayersItem::hoverMoveEvent(QGraphicsSceneHoverEvent * event) {
- const QPoint pos = this->getBoundedPos(event->pos());
+ hover(getBoundedPos(event->pos()));
+}
+
+void MetatileLayersItem::hover(const QPoint &pos) {
if (pos == this->prevHoveredPos)
return;
this->prevHoveredPos = pos;
- int tileIndex = tilePositions.indexOf(pos);
- if (tileIndex < 0 || tileIndex >= this->metatile->tiles.length())
+ int tileIndex = posToTileIndex(pos);
+ if (tileIndex < 0 || !this->metatile || tileIndex >= this->metatile->tiles.length())
return;
emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex));
diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp
index 38cc50be..00725b9a 100644
--- a/src/ui/metatileselector.cpp
+++ b/src/ui/metatileselector.cpp
@@ -3,7 +3,7 @@
#include "project.h"
#include
-QPoint MetatileSelector::getSelectionDimensions() const {
+QSize MetatileSelector::getSelectionDimensions() const {
if (this->prefabSelection || this->externalSelection)
return selection.dimensions;
return SelectablePixmapItem::getSelectionDimensions();
@@ -41,7 +41,7 @@ bool MetatileSelector::select(uint16_t metatileId) {
this->externalSelection = false;
this->prefabSelection = false;
this->selection = MetatileSelection{
- QPoint(1, 1),
+ QSize(1, 1),
false,
QList({MetatileSelectionItem{true, metatileId}}),
QList(),
@@ -80,7 +80,7 @@ void MetatileSelector::setExternalSelection(int width, int height, const QListselection.metatileItems.clear();
this->selection.collisionItems.clear();
this->selection.hasCollision = true;
- this->selection.dimensions = QPoint(width, height);
+ this->selection.dimensions = QSize(width, height);
for (int i = 0; i < qMin(metatiles.length(), collisions.length()); i++) {
uint16_t metatileId = metatiles.at(i);
uint16_t collision = collisions.at(i).first;
@@ -175,8 +175,8 @@ void MetatileSelector::updateSelectedMetatiles() {
this->selection.hasCollision = false;
this->selection.dimensions = this->getSelectionDimensions();
QPoint origin = this->getSelectionStart();
- for (int j = 0; j < this->selection.dimensions.y(); j++) {
- for (int i = 0; i < this->selection.dimensions.x(); i++) {
+ for (int j = 0; j < this->selection.dimensions.height(); j++) {
+ for (int i = 0; i < this->selection.dimensions.width(); i++) {
uint16_t metatileId = posToMetatileId(origin.x() + i, origin.y() + j);
this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId});
}
@@ -186,7 +186,7 @@ void MetatileSelector::updateSelectedMetatiles() {
void MetatileSelector::updateExternalSelectedMetatiles() {
this->selection.metatileItems.clear();
- this->selection.dimensions = QPoint(this->externalSelectionWidth, this->externalSelectionHeight);
+ this->selection.dimensions = QSize(this->externalSelectionWidth, this->externalSelectionHeight);
for (int i = 0; i < this->externalSelectedMetatiles.count(); ++i) {
uint16_t metatileId = this->externalSelectedMetatiles.at(i);
if (!this->layout->metatileIsValid(metatileId))
@@ -204,7 +204,7 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const {
if (ok) *ok = true;
int index = y * this->numMetatilesWide + x;
uint16_t metatileId = static_cast(index);
- if (primaryTileset() && primaryTileset()->contains(metatileId)) {
+ if (primaryTileset() && primaryTileset()->containsMetatileId(metatileId)) {
return metatileId;
}
@@ -215,7 +215,7 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const {
int numPrimaryRounded = numPrimaryMetatilesRounded();
int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide;
metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded);
- if (secondaryTileset() && secondaryTileset()->contains(metatileId) && y >= firstSecondaryRow) {
+ if (secondaryTileset() && secondaryTileset()->containsMetatileId(metatileId) && y >= firstSecondaryRow) {
return metatileId;
}
@@ -224,12 +224,12 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const {
}
QPoint MetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const {
- if (primaryTileset() && primaryTileset()->contains(metatileId)) {
+ if (primaryTileset() && primaryTileset()->containsMetatileId(metatileId)) {
if (ok) *ok = true;
int index = metatileId;
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
}
- if (secondaryTileset() && secondaryTileset()->contains(metatileId)) {
+ if (secondaryTileset() && secondaryTileset()->containsMetatileId(metatileId)) {
if (ok) *ok = true;
int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded();
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp
index 571185d7..a862302e 100644
--- a/src/ui/paletteeditor.cpp
+++ b/src/ui/paletteeditor.cpp
@@ -28,11 +28,6 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
ui->layout_Colors->addWidget(colorInput, i / numColorsPerRow, i % numColorsPerRow);
}
- // Setup edit-undo history for each of the palettes.
- for (int i = 0; i < Project::getNumPalettesTotal(); i++) {
- this->palettesHistory.append(History());
- }
-
int bitDepth = porymapConfig.paletteEditorBitDepth;
if (bitDepth == 15) {
this->ui->bit_depth_15->setChecked(true);
@@ -62,6 +57,8 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
connect(this->ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteEditor::refreshPaletteId);
connect(this->ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteEditor::changedPalette);
+ ui->actionRedo->setShortcuts({ui->actionRedo->shortcut(), QKeySequence("Ctrl+Shift+Z")});
+
refreshPaletteId();
restoreWindowState();
}
@@ -127,8 +124,12 @@ void PaletteEditor::refreshPaletteId() {
refreshColorInputs();
int paletteId = currentPaletteId();
+
if (!this->palettesHistory[paletteId].current()) {
+ // The original colors are saved as an initial commit.
commitEditHistory(paletteId);
+ } else {
+ updateEditHistoryActions();
}
if (this->colorSearchWindow) {
this->colorSearchWindow->setPaletteId(paletteId);
@@ -154,8 +155,8 @@ void PaletteEditor::commitEditHistory(int paletteId) {
for (int i = 0; i < this->colorInputs.length(); i++) {
colors.append(this->colorInputs.at(i)->color());
}
- PaletteHistoryItem *commit = new PaletteHistoryItem(colors);
- this->palettesHistory[paletteId].push(commit);
+ this->palettesHistory[paletteId].push(new PaletteHistoryItem(colors));
+ updateEditHistoryActions();
}
void PaletteEditor::restoreWindowState() {
@@ -165,18 +166,27 @@ void PaletteEditor::restoreWindowState() {
restoreState(geometry.value("palette_editor_state"));
}
+void PaletteEditor::updateEditHistoryActions() {
+ int paletteId = currentPaletteId();
+ // We have an initial commit that shouldn't be available to Undo, so we ignore that.
+ ui->actionUndo->setEnabled(this->palettesHistory[paletteId].index() > 0);
+ ui->actionRedo->setEnabled(this->palettesHistory[paletteId].canRedo());
+}
+
void PaletteEditor::on_actionUndo_triggered() {
int paletteId = currentPaletteId();
- PaletteHistoryItem *prev = this->palettesHistory[paletteId].back();
- if (prev)
- setPalette(paletteId, prev->colors);
+ PaletteHistoryItem *commit = this->palettesHistory[paletteId].back();
+ if (!commit) return;
+ setPalette(paletteId, commit->colors);
+ updateEditHistoryActions();
}
void PaletteEditor::on_actionRedo_triggered() {
int paletteId = currentPaletteId();
- PaletteHistoryItem *next = this->palettesHistory[paletteId].next();
- if (next)
- setPalette(paletteId, next->colors);
+ PaletteHistoryItem *commit = this->palettesHistory[paletteId].next();
+ if (!commit) return;
+ setPalette(paletteId, commit->colors);
+ updateEditHistoryActions();
}
void PaletteEditor::on_actionImport_Palette_triggered() {
diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp
index 32a6c86a..edbb7eac 100644
--- a/src/ui/prefab.cpp
+++ b/src/ui/prefab.cpp
@@ -58,7 +58,7 @@ void Prefab::loadPrefabs() {
QString secondaryTileset = ParseUtil::jsonToQString(prefabObj["secondary_tileset"]);
MetatileSelection selection;
- selection.dimensions = QPoint(width, height);
+ selection.dimensions = QSize(width, height);
selection.hasCollision = true;
for (int j = 0; j < width * height; j++) {
selection.metatileItems.append(MetatileSelectionItem{false, 0});
@@ -112,14 +112,14 @@ void Prefab::savePrefabs() {
for (auto item : this->items) {
OrderedJson::object prefabObj;
prefabObj["name"] = item.name;
- prefabObj["width"] = item.selection.dimensions.x();
- prefabObj["height"] = item.selection.dimensions.y();
+ prefabObj["width"] = item.selection.dimensions.width();
+ prefabObj["height"] = item.selection.dimensions.height();
prefabObj["primary_tileset"] = item.primaryTileset;
prefabObj["secondary_tileset"] = item.secondaryTileset;
OrderedJson::array metatiles;
- for (int y = 0; y < item.selection.dimensions.y(); y++) {
- for (int x = 0; x < item.selection.dimensions.x(); x++) {
- int index = y * item.selection.dimensions.x() + x;
+ for (int y = 0; y < item.selection.dimensions.height(); y++) {
+ for (int x = 0; x < item.selection.dimensions.width(); x++) {
+ int index = y * item.selection.dimensions.width() + x;
auto metatileItem = item.selection.metatileItems.at(index);
if (metatileItem.enabled) {
OrderedJson::object metatileObj;
diff --git a/src/ui/prefabcreationdialog.cpp b/src/ui/prefabcreationdialog.cpp
index 76c71f39..34b11127 100644
--- a/src/ui/prefabcreationdialog.cpp
+++ b/src/ui/prefabcreationdialog.cpp
@@ -23,13 +23,13 @@ PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *me
QObject::connect(this->ui->graphicsView_Prefab, &ClickableGraphicsView::clicked, [=](QMouseEvent *event){
auto pos = event->pos();
- int selectionWidth = this->selection.dimensions.x() * Metatile::pixelWidth();
- int selectionHeight = this->selection.dimensions.y() * Metatile::pixelHeight();
+ int selectionWidth = this->selection.dimensions.width() * Metatile::pixelWidth();
+ int selectionHeight = this->selection.dimensions.height() * Metatile::pixelHeight();
if (pos.x() < 0 || pos.x() >= selectionWidth || pos.y() < 0 || pos.y() >= selectionHeight)
return;
int metatileX = pos.x() / Metatile::pixelWidth();
int metatileY = pos.y() / Metatile::pixelHeight();
- int index = metatileY * this->selection.dimensions.x() + metatileX;
+ int index = metatileY * this->selection.dimensions.width() + metatileX;
bool toggledState = !this->selection.metatileItems[index].enabled;
this->selection.metatileItems[index].enabled = toggledState;
if (this->selection.hasCollision) {
diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp
index bd001e59..267fbd1f 100644
--- a/src/ui/selectablepixmapitem.cpp
+++ b/src/ui/selectablepixmapitem.cpp
@@ -1,11 +1,6 @@
#include "selectablepixmapitem.h"
#include
-QPoint SelectablePixmapItem::getSelectionDimensions() const
-{
- return QPoint(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1);
-}
-
QPoint SelectablePixmapItem::getSelectionStart()
{
int x = this->selectionInitialX;
@@ -15,47 +10,60 @@ QPoint SelectablePixmapItem::getSelectionStart()
return QPoint(x, y);
}
-void SelectablePixmapItem::select(int x, int y, int width, int height)
-{
- this->selectionInitialX = x;
- this->selectionInitialY = y;
- this->selectionOffsetX = qBound(0, width, this->maxSelectionWidth);
- this->selectionOffsetY = qBound(0, height, this->maxSelectionHeight);
- this->draw();
- emit this->selectionChanged(x, y, width, height);
+void SelectablePixmapItem::setSelection(const QPoint &pos, const QSize &size) {
+ this->selectionInitialX = pos.x();
+ this->selectionInitialY = pos.y();
+ this->selectionOffsetX = getBoundedWidth(size.width()) - 1;
+ this->selectionOffsetY = getBoundedHeight(size.height()) - 1;
+}
+
+void SelectablePixmapItem::select(const QPoint &pos, const QSize &size) {
+ setSelection(pos, size);
+ draw();
+ emit selectionChanged(pos, getSelectionDimensions());
}
void SelectablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
- QPoint pos = this->getCellPos(event->pos());
- this->selectionInitialX = pos.x();
- this->selectionInitialY = pos.y();
- this->selectionOffsetX = 0;
- this->selectionOffsetY = 0;
- this->updateSelection(pos.x(), pos.y());
+ QPoint pos = getCellPos(event->pos());
+ setSelection(pos, QSize(1,1));
+ this->prevCellPos = pos;
+ updateSelection(pos);
}
void SelectablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
- QPoint pos = this->getCellPos(event->pos());
- this->updateSelection(pos.x(), pos.y());
+ QPoint pos = getCellPos(event->pos());
+ if (pos == this->prevCellPos)
+ return;
+ this->prevCellPos = pos;
+ updateSelection(pos);
}
void SelectablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
- QPoint pos = this->getCellPos(event->pos());
- this->updateSelection(pos.x(), pos.y());
+ updateSelection(getCellPos(event->pos()));
}
-void SelectablePixmapItem::updateSelection(int x, int y)
-{
+void SelectablePixmapItem::setMaxSelectionSize(int width, int height) {
+ this->maxSelectionWidth = qMax(width, 1);
+ this->maxSelectionHeight = qMax(height, 1);
+
+ // Update the selection if we shrank below the current selection size.
+ QSize size = getSelectionDimensions();
+ if (size.width() > this->maxSelectionWidth || size.height() > this->maxSelectionHeight) {
+ setSelection(getSelectionStart(), size);
+ draw();
+ // 'draw' is allowed to change the selection position/size,
+ // so call these again rather than keep values from above.
+ emit selectionChanged(getSelectionStart(), getSelectionDimensions());
+ }
+}
+
+void SelectablePixmapItem::updateSelection(const QPoint &pos) {
// Snap to a valid position inside the selection area.
- int width = pixmap().width() / this->cellWidth;
- int height = pixmap().height() / this->cellHeight;
- if (x < 0) x = 0;
- if (x >= width) x = width - 1;
- if (y < 0) y = 0;
- if (y >= height) y = height - 1;
+ int x = qBound(0, pos.x(), cellsWide() - 1);
+ int y = qBound(0, pos.y(), cellsTall() - 1);
this->selectionOffsetX = x - this->selectionInitialX;
this->selectionOffsetY = y - this->selectionInitialY;
@@ -76,25 +84,30 @@ void SelectablePixmapItem::updateSelection(int x, int y)
this->selectionOffsetY = -this->maxSelectionHeight + 1;
}
- this->draw();
- emit this->selectionChanged(x, y, width, height);
+ draw();
+ emit selectionChanged(QPoint(x, y), getSelectionDimensions());
}
-QPoint SelectablePixmapItem::getCellPos(QPointF pos)
-{
- if (pos.x() < 0) pos.setX(0);
- if (pos.y() < 0) pos.setY(0);
- if (pos.x() >= this->pixmap().width()) pos.setX(this->pixmap().width() - 1);
- if (pos.y() >= this->pixmap().height()) pos.setY(this->pixmap().height() - 1);
- return QPoint(static_cast(pos.x()) / this->cellWidth,
- static_cast(pos.y()) / this->cellHeight);
+void SelectablePixmapItem::setSelectionStyle(Qt::PenStyle style) {
+ this->selectionStyle = style;
+ draw();
}
-void SelectablePixmapItem::drawSelection()
-{
- QPoint origin = this->getSelectionStart();
- QPoint dimensions = this->getSelectionDimensions();
- QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.x() * this->cellWidth, dimensions.y() * this->cellHeight);
+QPoint SelectablePixmapItem::getCellPos(const QPointF &itemPos) {
+ if (this->cellWidth == 0 || this->cellHeight == 0 || pixmap().isNull())
+ return QPoint(0,0);
+
+ int x = qBound(0, static_cast(itemPos.x()), pixmap().width() - 1);
+ int y = qBound(0, static_cast(itemPos.y()), pixmap().height() - 1);
+ return QPoint(x / this->cellWidth, y / this->cellHeight);
+}
+
+void SelectablePixmapItem::drawSelection() {
+ drawSelectionRect(getSelectionStart(), getSelectionDimensions(), this->selectionStyle);
+}
+
+void SelectablePixmapItem::drawSelectionRect(const QPoint &origin, const QSize &dimensions, Qt::PenStyle style) {
+ QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.width() * this->cellWidth, dimensions.height() * this->cellHeight);
// If a selection is fully outside the bounds of the selectable area, don't draw anything.
// This prevents the border of the selection rectangle potentially being visible on an otherwise invisible selection.
@@ -102,12 +115,27 @@ void SelectablePixmapItem::drawSelection()
if (!selectionRect.intersects(pixmap.rect()))
return;
+ auto fillPen = QPen(QColor(Qt::white));
+ auto borderPen = QPen(QColor(Qt::black));
+ borderPen.setStyle(style);
+
QPainter painter(&pixmap);
- painter.setPen(QColor(0xff, 0xff, 0xff));
- painter.drawRect(selectionRect.x(), selectionRect.y(), selectionRect.width() - 1, selectionRect.height() - 1);
- painter.setPen(QColor(0, 0, 0));
- painter.drawRect(selectionRect.x() - 1, selectionRect.y() - 1, selectionRect.width() + 1, selectionRect.height() + 1);
- painter.drawRect(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 3, selectionRect.height() - 3);
+ if (style == Qt::SolidLine) {
+ painter.setPen(fillPen);
+ painter.drawRect(selectionRect - QMargins(1,1,1,1));
+ painter.setPen(borderPen);
+ painter.drawRect(selectionRect);
+ painter.drawRect(selectionRect - QMargins(2,2,2,2));
+ } else {
+ // Having separately sized rectangles with anything but a
+ // solid line looks a little wonky because the dashes wont align.
+ // For non-solid styles we'll draw a base white rectangle, then draw
+ // a styled black rectangle on top
+ painter.setPen(fillPen);
+ painter.drawRect(selectionRect);
+ painter.setPen(borderPen);
+ painter.drawRect(selectionRect);
+ }
this->setPixmap(pixmap);
}
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index b676c9ee..17074063 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -30,7 +30,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
auto validator = new IdentifierValidator(this);
validator->setAllowEmpty(true);
- ui->lineEdit_metatileLabel->setValidator(validator);
+ ui->lineEdit_MetatileLabel->setValidator(validator);
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
@@ -62,12 +62,18 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
connect(ui->spinBox_paletteSelector, QOverload::of(&QSpinBox::valueChanged), this, &TilesetEditor::refreshPaletteId);
+ connect(ui->actionLayer_Arrangement_Horizontal, &QAction::triggered, [this] { setMetatileLayerOrientation(Qt::Horizontal); });
+ connect(ui->actionLayer_Arrangement_Vertical, &QAction::triggered, [this] { setMetatileLayerOrientation(Qt::Vertical); });
+
+ connect(ui->lineEdit_MetatileLabel, &QLineEdit::editingFinished, this, &TilesetEditor::commitMetatileLabel);
+
initAttributesUi();
initMetatileSelector();
initMetatileLayersItem();
initTileSelector();
initSelectedTileItem();
initShortcuts();
+ setMetatileLayerOrientation(porymapConfig.tilesetEditorLayerOrientation);
this->metatileSelector->select(0);
restoreWindowState();
}
@@ -133,92 +139,85 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
this->primaryTileset = new Tileset(*primaryTileset);
this->secondaryTileset = new Tileset(*secondaryTileset);
if (this->paletteEditor) this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
- this->initMetatileHistory();
+ initMetatileHistory();
}
void TilesetEditor::initAttributesUi() {
- connect(ui->comboBox_metatileBehaviors, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitMetatileBehavior);
- connect(ui->comboBox_encounterType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitEncounterType);
- connect(ui->comboBox_terrainType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitTerrainType);
- connect(ui->comboBox_layerType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitLayerType);
+ connect(ui->comboBox_MetatileBehaviors, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitMetatileBehavior);
+ connect(ui->comboBox_EncounterType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitEncounterType);
+ connect(ui->comboBox_TerrainType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitTerrainType);
+ connect(ui->comboBox_LayerType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitLayerType);
// Behavior
if (projectConfig.metatileBehaviorMask) {
for (auto i = project->metatileBehaviorMapInverse.constBegin(); i != project->metatileBehaviorMapInverse.constEnd(); i++) {
- this->ui->comboBox_metatileBehaviors->addItem(i.value(), i.key());
+ this->ui->comboBox_MetatileBehaviors->addItem(i.value(), i.key());
}
- this->ui->comboBox_metatileBehaviors->setMinimumContentsLength(0);
+ this->ui->comboBox_MetatileBehaviors->setMinimumContentsLength(0);
} else {
- this->ui->comboBox_metatileBehaviors->setVisible(false);
- this->ui->label_metatileBehavior->setVisible(false);
+ this->ui->frame_MetatileBehavior->setVisible(false);
}
// Terrain Type
if (projectConfig.metatileTerrainTypeMask) {
for (auto i = project->terrainTypeToName.constBegin(); i != project->terrainTypeToName.constEnd(); i++) {
- this->ui->comboBox_terrainType->addItem(i.value(), i.key());
+ this->ui->comboBox_TerrainType->addItem(i.value(), i.key());
}
- this->ui->comboBox_terrainType->setMinimumContentsLength(0);
+ this->ui->comboBox_TerrainType->setMinimumContentsLength(0);
} else {
- this->ui->comboBox_terrainType->setVisible(false);
- this->ui->label_terrainType->setVisible(false);
+ this->ui->frame_TerrainType->setVisible(false);
}
// Encounter Type
if (projectConfig.metatileEncounterTypeMask) {
for (auto i = project->encounterTypeToName.constBegin(); i != project->encounterTypeToName.constEnd(); i++) {
- this->ui->comboBox_encounterType->addItem(i.value(), i.key());
+ this->ui->comboBox_EncounterType->addItem(i.value(), i.key());
}
- this->ui->comboBox_encounterType->setMinimumContentsLength(0);
+ this->ui->comboBox_EncounterType->setMinimumContentsLength(0);
} else {
- this->ui->comboBox_encounterType->setVisible(false);
- this->ui->label_encounterType->setVisible(false);
+ this->ui->frame_EncounterType->setVisible(false);
}
// Layer Type
if (!projectConfig.tripleLayerMetatilesEnabled) {
- this->ui->comboBox_layerType->addItem("Normal - Middle/Top", Metatile::LayerType::Normal);
- this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", Metatile::LayerType::Covered);
- this->ui->comboBox_layerType->addItem("Split - Bottom/Top", Metatile::LayerType::Split);
- this->ui->comboBox_layerType->setEditable(false);
- this->ui->comboBox_layerType->setMinimumContentsLength(0);
+ this->ui->comboBox_LayerType->addItem("Normal - Middle/Top", Metatile::LayerType::Normal);
+ this->ui->comboBox_LayerType->addItem("Covered - Bottom/Middle", Metatile::LayerType::Covered);
+ this->ui->comboBox_LayerType->addItem("Split - Bottom/Top", Metatile::LayerType::Split);
+ this->ui->comboBox_LayerType->setEditable(false);
+ this->ui->comboBox_LayerType->setMinimumContentsLength(0);
if (!projectConfig.metatileLayerTypeMask) {
// User doesn't have triple layer metatiles, but has no layer type attribute.
// Porymap is still using the layer type value to render metatiles, and with
// no mask set every metatile will be "Middle/Top", so just display the combo
// box but prevent the user from changing the value.
- this->ui->comboBox_layerType->setEnabled(false);
+ this->ui->comboBox_LayerType->setEnabled(false);
}
} else {
- this->ui->comboBox_layerType->setVisible(false);
- this->ui->label_layerType->setVisible(false);
+ this->ui->frame_LayerType->setVisible(false);
this->ui->label_BottomTop->setText("Bottom/Middle/Top");
}
// Raw attributes value
- ui->spinBox_rawAttributesValue->setMaximum(Metatile::getMaxAttributesMask());
+ ui->spinBox_RawAttributesValue->setMaximum(Metatile::getMaxAttributesMask());
setRawAttributesVisible(ui->actionShow_Raw_Metatile_Attributes->isChecked());
- connect(ui->spinBox_rawAttributesValue, &UIntHexSpinBox::editingFinished, this, &TilesetEditor::onRawAttributesEdited);
+ connect(ui->spinBox_RawAttributesValue, &UIntHexSpinBox::editingFinished, this, &TilesetEditor::onRawAttributesEdited);
connect(ui->actionShow_Raw_Metatile_Attributes, &QAction::toggled, this, &TilesetEditor::setRawAttributesVisible);
-
- this->ui->frame_Properties->adjustSize();
}
void TilesetEditor::setRawAttributesVisible(bool visible) {
porymapConfig.showTilesetEditorRawAttributes = visible;
- ui->label_rawAttributesValue->setVisible(visible);
- ui->spinBox_rawAttributesValue->setVisible(visible);
+ ui->frame_RawAttributesValue->setVisible(visible);
+ rebuildMetatilePropertiesFrame();
}
void TilesetEditor::initMetatileSelector()
{
this->metatileSelector = new TilesetEditorMetatileSelector(projectConfig.metatileSelectorWidth, this->primaryTileset, this->secondaryTileset, this->layout);
- connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileChanged,
- this, &TilesetEditor::onHoveredMetatileChanged);
- connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileCleared,
- this, &TilesetEditor::onHoveredMetatileCleared);
- connect(this->metatileSelector, &TilesetEditorMetatileSelector::selectedMetatileChanged,
- this, &TilesetEditor::onSelectedMetatileChanged);
+ connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileChanged, this, &TilesetEditor::onHoveredMetatileChanged);
+ connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileCleared, this, &TilesetEditor::onHoveredMetatileCleared);
+ connect(this->metatileSelector, &TilesetEditorMetatileSelector::selectedMetatileChanged, this, &TilesetEditor::onSelectedMetatileChanged);
+ connect(this->metatileSelector, &TilesetEditorMetatileSelector::swapRequested, this, &TilesetEditor::commitMetatileSwap);
+ connect(ui->actionSwap_Metatiles, &QAction::triggered, this->metatileSelector, &TilesetEditorMetatileSelector::setSwapMode);
bool showGrid = porymapConfig.showTilesetEditorMetatileGrid;
this->ui->actionMetatile_Grid->setChecked(showGrid);
@@ -234,18 +233,89 @@ void TilesetEditor::initMetatileSelector()
this->ui->horizontalSlider_MetatilesZoom->setValue(porymapConfig.tilesetEditorMetatilesZoom);
}
+void TilesetEditor::setMetatileLayerOrientation(Qt::Orientation orientation) {
+ // Sync settings
+ bool horizontal = (orientation == Qt::Horizontal);
+ porymapConfig.tilesetEditorLayerOrientation = orientation;
+ const QSignalBlocker b_Horizontal(ui->actionLayer_Arrangement_Horizontal);
+ const QSignalBlocker b_Vertical(ui->actionLayer_Arrangement_Vertical);
+ ui->actionLayer_Arrangement_Horizontal->setChecked(horizontal);
+ ui->actionLayer_Arrangement_Vertical->setChecked(!horizontal);
+
+ this->metatileLayersItem->setOrientation(orientation);
+
+ int numTilesWide = Metatile::tileWidth();
+ int numTilesTall = Metatile::tileHeight();
+ int numLayers = projectConfig.getNumLayersInMetatile();
+ if (horizontal) {
+ numTilesWide *= numLayers;
+ } else {
+ numTilesTall *= numLayers;
+ }
+ this->tileSelector->setMaxSelectionSize(numTilesWide, numTilesTall);
+
+ const int scale = 2;
+ int w = Tile::pixelWidth() * numTilesWide * scale + 2;
+ int h = Tile::pixelHeight() * numTilesTall * scale + 2;
+ ui->graphicsView_selectedTile->setFixedSize(w, h);
+ ui->graphicsView_MetatileLayers->setFixedSize(w, h);
+
+ drawSelectedTiles();
+
+ // If the layers are laid out vertically then the orientation is obvious, no need to label them.
+ // This also lets us give the vertical space of the label over to the layer view.
+ ui->label_BottomTop->setVisible(horizontal);
+
+ rebuildMetatilePropertiesFrame();
+}
+
+// We rearrange the metatile properties panel depending on the orientation and size of the metatile layer view.
+// If triple layer metatiles are in-use then layer type field is hidden, so there's an awkward amount of space
+// next to the layer view, especially in the vertical orientation.
+// We shift 1-2 widgets up to fill this space next to the layer view. This gets a little complicated because which
+// widgets are available to move changes depending on the user's settings.
+void TilesetEditor::rebuildMetatilePropertiesFrame() {
+ if (porymapConfig.tilesetEditorLayerOrientation == Qt::Horizontal) {
+ this->numLayerViewRows = 1;
+ } else {
+ this->numLayerViewRows = projectConfig.tripleLayerMetatilesEnabled ? 4 : 2;
+ }
+
+ for (const auto &frame : ui->gridLayout_MetatileProperties->findChildren()) {
+ ui->gridLayout_MetatileProperties->removeWidget(frame);
+ }
+ ui->gridLayout_MetatileProperties->addWidget(ui->frame_Layers, 0, 0, this->numLayerViewRows, 1);
+
+ int row = 0;
+ addWidgetToMetatileProperties(ui->frame_LayerType, &row, 2);
+ if (porymapConfig.tilesetEditorLayerOrientation == Qt::Horizontal) {
+ // When the layer view's orientation is horizontal we only allow the
+ // layer type selector to share the row with the layer view.
+ row = this->numLayerViewRows;
+ }
+ addWidgetToMetatileProperties(ui->frame_MetatileBehavior, &row, 2);
+ addWidgetToMetatileProperties(ui->frame_EncounterType, &row, 2);
+ addWidgetToMetatileProperties(ui->frame_TerrainType, &row, 2);
+ addWidgetToMetatileProperties(ui->frame_RawAttributesValue, &row, 2);
+ addWidgetToMetatileProperties(ui->frame_MetatileLabel, &row, 2);
+}
+
+void TilesetEditor::addWidgetToMetatileProperties(QWidget *w, int *row, int rowSpan) {
+ if (w->isVisibleTo(ui->frame_Properties)) {
+ int col = (*row < this->numLayerViewRows) ? 1 : 0; // Shift widget over if it shares the row with the layer view
+ ui->gridLayout_MetatileProperties->addWidget(w, *row, col, rowSpan, -1);
+ *row += rowSpan;
+ }
+}
+
void TilesetEditor::initMetatileLayersItem() {
Metatile *metatile = Tileset::getMetatile(this->getSelectedMetatileId(), this->primaryTileset, this->secondaryTileset);
this->metatileLayersItem = new MetatileLayersItem(metatile, this->primaryTileset, this->secondaryTileset);
- connect(this->metatileLayersItem, &MetatileLayersItem::tileChanged,
- this, &TilesetEditor::onMetatileLayerTileChanged);
- connect(this->metatileLayersItem, &MetatileLayersItem::selectedTilesChanged,
- this, &TilesetEditor::onMetatileLayerSelectionChanged);
- connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, [this](const Tile &tile) {
- onHoveredTileChanged(tile);
- });
- connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared,
- this, &TilesetEditor::onHoveredTileCleared);
+ connect(this->metatileLayersItem, &MetatileLayersItem::tileChanged, [this](const QPoint &pos) { paintSelectedLayerTiles(pos); });
+ connect(this->metatileLayersItem, &MetatileLayersItem::paletteChanged, [this](const QPoint &pos) { paintSelectedLayerTiles(pos, true); });
+ connect(this->metatileLayersItem, &MetatileLayersItem::selectedTilesChanged, this, &TilesetEditor::onMetatileLayerSelectionChanged);
+ connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, [this](const Tile &tile) { onHoveredTileChanged(tile); });
+ connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared);
bool showGrid = porymapConfig.showTilesetEditorLayerGrid;
this->ui->actionLayer_Grid->setChecked(showGrid);
@@ -253,12 +323,11 @@ void TilesetEditor::initMetatileLayersItem() {
this->metatileLayersScene = new QGraphicsScene;
this->metatileLayersScene->addItem(this->metatileLayersItem);
- this->ui->graphicsView_metatileLayers->setScene(this->metatileLayersScene);
+ this->ui->graphicsView_MetatileLayers->setScene(this->metatileLayersScene);
}
-void TilesetEditor::initTileSelector()
-{
- this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset, projectConfig.getNumLayersInMetatile());
+void TilesetEditor::initTileSelector() {
+ this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset);
connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) {
onHoveredTileChanged(tileId);
});
@@ -281,7 +350,6 @@ void TilesetEditor::initSelectedTileItem() {
this->selectedTileScene = new QGraphicsScene;
this->drawSelectedTiles();
this->ui->graphicsView_selectedTile->setScene(this->selectedTileScene);
- this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width() + 2, this->selectedTilePixmapItem->pixmap().height() + 2);
}
void TilesetEditor::initShortcuts() {
@@ -344,13 +412,6 @@ void TilesetEditor::onWindowActivated() {
}
}
-void TilesetEditor::initMetatileHistory() {
- metatileHistory.clear();
- MetatileHistoryItem *commit = new MetatileHistoryItem(0, nullptr, new Metatile(), QString(), QString());
- metatileHistory.push(commit);
- this->hasUnsavedChanges = false;
-}
-
void TilesetEditor::reset() {
this->setTilesets(this->primaryTileset->name, this->secondaryTileset->name);
if (this->paletteEditor)
@@ -392,17 +453,16 @@ void TilesetEditor::drawSelectedTiles() {
const int imgTileHeight = 16;
this->selectedTileScene->clear();
QList tiles = this->tileSelector->getSelectedTiles();
- QPoint dimensions = this->tileSelector->getSelectionDimensions();
- QImage selectionImage(imgTileWidth * dimensions.x(), imgTileHeight * dimensions.y(), QImage::Format_RGBA8888);
+ QSize dimensions = this->tileSelector->getSelectionDimensions();
+ QImage selectionImage(imgTileWidth * dimensions.width(), imgTileHeight * dimensions.height(), QImage::Format_RGBA8888);
QPainter painter(&selectionImage);
int tileIndex = 0;
- for (int j = 0; j < dimensions.y(); j++) {
- for (int i = 0; i < dimensions.x(); i++) {
- auto tile = tiles.at(tileIndex);
+ for (int y = 0; y < dimensions.height(); y++) {
+ for (int x = 0; x < dimensions.width(); x++) {
+ auto tile = tiles.at(tileIndex++);
QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(imgTileWidth, imgTileHeight);
tile.flip(&tileImage);
- tileIndex++;
- painter.drawImage(i * imgTileWidth, j * imgTileHeight, tileImage);
+ painter.drawImage(x * imgTileWidth, y * imgTileHeight, tileImage);
}
}
@@ -411,7 +471,6 @@ void TilesetEditor::drawSelectedTiles() {
QSize size(this->selectedTilePixmapItem->pixmap().width(), this->selectedTilePixmapItem->pixmap().height());
this->ui->graphicsView_selectedTile->setSceneRect(0, 0, size.width(), size.height());
- this->ui->graphicsView_selectedTile->setFixedSize(size.width() + 2, size.height() + 2);
}
void TilesetEditor::onHoveredMetatileChanged(uint16_t metatileId) {
@@ -429,6 +488,7 @@ void TilesetEditor::onHoveredMetatileCleared() {
void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
this->metatile = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset);
+ if (!this->metatile) return;
// The scripting API allows users to change metatiles in the project, and these changes are saved to disk.
// The Tileset Editor (if open) needs to reflect these changes when the metatile is next displayed.
@@ -440,11 +500,10 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
this->metatileLayersItem->setMetatile(metatile);
this->metatileLayersItem->draw();
- this->ui->graphicsView_metatileLayers->setFixedSize(this->metatileLayersItem->pixmap().width() + 2, this->metatileLayersItem->pixmap().height() + 2);
MetatileLabelPair labels = Tileset::getMetatileLabelPair(metatileId, this->primaryTileset, this->secondaryTileset);
- this->ui->lineEdit_metatileLabel->setText(labels.owned);
- this->ui->lineEdit_metatileLabel->setPlaceholderText(labels.shared);
+ this->ui->lineEdit_MetatileLabel->setText(labels.owned);
+ this->ui->lineEdit_MetatileLabel->setPlaceholderText(labels.shared);
refreshMetatileAttributes();
}
@@ -470,45 +529,44 @@ void TilesetEditor::onHoveredTileCleared() {
this->ui->statusbar->clearMessage();
}
-void TilesetEditor::onMetatileLayerTileChanged(int x, int y) {
- static const QList tileCoords = QList{
- QPoint(0, 0),
- QPoint(1, 0),
- QPoint(0, 1),
- QPoint(1, 1),
- QPoint(2, 0),
- QPoint(3, 0),
- QPoint(2, 1),
- QPoint(3, 1),
- QPoint(4, 0),
- QPoint(5, 0),
- QPoint(4, 1),
- QPoint(5, 1),
- };
+void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) {
+ if (!this->metatile) return;
+
+ bool changed = false;
Metatile *prevMetatile = new Metatile(*this->metatile);
- QPoint dimensions = this->tileSelector->getSelectionDimensions();
+ QSize dimensions = this->tileSelector->getSelectionDimensions();
QList tiles = this->tileSelector->getSelectedTiles();
- int selectedTileIndex = 0;
+ int srcTileIndex = 0;
int maxTileIndex = projectConfig.getNumTilesInMetatile();
- for (int j = 0; j < dimensions.y(); j++) {
- for (int i = 0; i < dimensions.x(); i++) {
- int tileIndex = ((x + i) / 2 * 4) + ((y + j) * 2) + ((x + i) % 2);
- if (tileIndex < maxTileIndex
- && tileCoords.at(tileIndex).x() >= x
- && tileCoords.at(tileIndex).y() >= y){
- Tile &tile = this->metatile->tiles[tileIndex];
- tile.tileId = tiles.at(selectedTileIndex).tileId;
- tile.xflip = tiles.at(selectedTileIndex).xflip;
- tile.yflip = tiles.at(selectedTileIndex).yflip;
- tile.palette = tiles.at(selectedTileIndex).palette;
- if (this->tileSelector->showUnused) {
- this->tileSelector->usedTiles[tile.tileId] += 1;
- this->tileSelector->usedTiles[prevMetatile->tiles[tileIndex].tileId] -= 1;
+ for (int y = 0; y < dimensions.height(); y++) {
+ for (int x = 0; x < dimensions.width(); x++) {
+ int destTileIndex = this->metatileLayersItem->posToTileIndex(pos.x() + x, pos.y() + y);
+ if (destTileIndex < maxTileIndex) {
+ Tile &destTile = this->metatile->tiles[destTileIndex];
+ const Tile srcTile = tiles.at(srcTileIndex++);
+ if (paletteOnly) {
+ if (srcTile.palette == destTile.palette)
+ continue; // Ignore no-ops for edit history
+ destTile.palette = srcTile.palette;
+ } else {
+ if (srcTile == destTile)
+ continue; // Ignore no-ops for edit history
+
+ // Update tile usage count
+ if (this->tileSelector->showUnused && destTile.tileId != srcTile.tileId) {
+ this->tileSelector->usedTiles[srcTile.tileId] += 1;
+ this->tileSelector->usedTiles[destTile.tileId] -= 1;
+ }
+ destTile = srcTile;
}
+ changed = true;
}
- selectedTileIndex++;
}
}
+ if (!changed) {
+ delete prevMetatile;
+ return;
+ }
this->metatileSelector->drawSelectedMetatile();
this->metatileLayersItem->draw();
@@ -516,26 +574,19 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) {
this->commitMetatileChange(prevMetatile);
}
-void TilesetEditor::onMetatileLayerSelectionChanged(QPoint selectionOrigin, int width, int height) {
+void TilesetEditor::onMetatileLayerSelectionChanged(const QPoint &selectionOrigin, const QSize &size) {
QList tiles;
- QList tileIdxs;
- int x = selectionOrigin.x();
- int y = selectionOrigin.y();
- int maxTileIndex = projectConfig.getNumTilesInMetatile();
- for (int j = 0; j < height; j++) {
- for (int i = 0; i < width; i++) {
- int tileIndex = ((x + i) / 2 * 4) + ((y + j) * 2) + ((x + i) % 2);
- if (tileIndex < maxTileIndex) {
- tiles.append(this->metatile->tiles.at(tileIndex));
- tileIdxs.append(tileIndex);
- }
+ for (int y = 0; y < size.height(); y++) {
+ for (int x = 0; x < size.width(); x++) {
+ int tileIndex = this->metatileLayersItem->posToTileIndex(selectionOrigin.x() + x, selectionOrigin.y() + y);
+ tiles.append(this->metatile ? this->metatile->tiles.value(tileIndex) : Tile());
}
}
- this->tileSelector->setExternalSelection(width, height, tiles, tileIdxs);
- if (width == 1 && height == 1) {
+ this->tileSelector->setExternalSelection(size.width(), size.height(), tiles);
+ if (size == QSize(1,1)) {
setPaletteId(tiles[0].palette);
- this->tileSelector->highlight(static_cast(tiles[0].tileId));
+ this->tileSelector->highlight(tiles[0].tileId);
this->redrawTileSelector();
}
this->metatileLayersItem->clearLastModifiedCoords();
@@ -566,21 +617,17 @@ void TilesetEditor::refreshTileFlips() {
void TilesetEditor::setMetatileLabel(QString label)
{
- this->ui->lineEdit_metatileLabel->setText(label);
+ this->ui->lineEdit_MetatileLabel->setText(label);
commitMetatileLabel();
}
-void TilesetEditor::on_lineEdit_metatileLabel_editingFinished()
-{
- commitMetatileLabel();
-}
+void TilesetEditor::commitMetatileLabel() {
+ if (!this->metatile) return;
-void TilesetEditor::commitMetatileLabel()
-{
// Only commit if the field has changed.
uint16_t metatileId = this->getSelectedMetatileId();
QString oldLabel = Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset);
- QString newLabel = this->ui->lineEdit_metatileLabel->text();
+ QString newLabel = this->ui->lineEdit_MetatileLabel->text();
if (oldLabel != newLabel) {
Metatile *prevMetatile = new Metatile(*this->metatile);
Tileset::setMetatileLabel(metatileId, newLabel, this->primaryTileset, this->secondaryTileset);
@@ -588,17 +635,17 @@ void TilesetEditor::commitMetatileLabel()
}
}
-void TilesetEditor::commitMetatileAndLabelChange(Metatile * prevMetatile, QString prevLabel)
-{
- metatileHistory.push(new MetatileHistoryItem(this->getSelectedMetatileId(),
- prevMetatile, new Metatile(*this->metatile),
- prevLabel, this->ui->lineEdit_metatileLabel->text()));
- this->hasUnsavedChanges = true;
+void TilesetEditor::commitMetatileAndLabelChange(Metatile * prevMetatile, QString prevLabel) {
+ if (!this->metatile) return;
+
+ commit(new MetatileHistoryItem(this->getSelectedMetatileId(),
+ prevMetatile, new Metatile(*this->metatile),
+ prevLabel, this->ui->lineEdit_MetatileLabel->text()));
}
void TilesetEditor::commitMetatileChange(Metatile * prevMetatile)
{
- this->commitMetatileAndLabelChange(prevMetatile, this->ui->lineEdit_metatileLabel->text());
+ this->commitMetatileAndLabelChange(prevMetatile, this->ui->lineEdit_MetatileLabel->text());
}
uint32_t TilesetEditor::attributeNameToValue(Metatile::Attr attribute, const QString &text, bool *ok) {
@@ -617,15 +664,14 @@ uint32_t TilesetEditor::attributeNameToValue(Metatile::Attr attribute, const QSt
}
} else if (attribute == Metatile::Attr::LayerType) {
// The layer type text is not editable, it uses special display names. Just get the index of the display name.
- int i = ui->comboBox_layerType->findText(text);
+ int i = ui->comboBox_LayerType->findText(text);
if (i >= 0) return i;
}
return text.toUInt(ok, 0);
}
void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScrollComboBox *combo) {
- if (!this->metatile)
- return;
+ if (!this->metatile) return;
bool ok;
uint32_t newValue = this->attributeNameToValue(attribute, combo->currentText(), &ok);
@@ -635,8 +681,8 @@ void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScro
this->commitMetatileChange(prevMetatile);
// When an attribute changes we also need to update the raw value display.
- const QSignalBlocker b_RawAttributesValue(ui->spinBox_rawAttributesValue);
- ui->spinBox_rawAttributesValue->setValue(this->metatile->getAttributes());
+ const QSignalBlocker b_RawAttributesValue(ui->spinBox_RawAttributesValue);
+ ui->spinBox_RawAttributesValue->setValue(this->metatile->getAttributes());
}
// Update the text in the combo box to reflect the final value.
@@ -646,7 +692,9 @@ void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScro
}
void TilesetEditor::onRawAttributesEdited() {
- uint32_t newAttributes = ui->spinBox_rawAttributesValue->value();
+ if (!this->metatile) return;
+
+ uint32_t newAttributes = ui->spinBox_RawAttributesValue->value();
if (newAttributes != this->metatile->getAttributes()) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->setAttributes(newAttributes);
@@ -658,34 +706,34 @@ void TilesetEditor::onRawAttributesEdited() {
void TilesetEditor::refreshMetatileAttributes() {
if (!this->metatile) return;
- const QSignalBlocker b_MetatileBehaviors(ui->comboBox_metatileBehaviors);
- const QSignalBlocker b_EncounterType(ui->comboBox_encounterType);
- const QSignalBlocker b_TerrainType(ui->comboBox_terrainType);
- const QSignalBlocker b_LayerType(ui->comboBox_layerType);
- const QSignalBlocker b_RawAttributesValue(ui->spinBox_rawAttributesValue);
- ui->comboBox_metatileBehaviors->setHexItem(this->metatile->behavior());
- ui->comboBox_encounterType->setHexItem(this->metatile->encounterType());
- ui->comboBox_terrainType->setHexItem(this->metatile->terrainType());
- ui->comboBox_layerType->setHexItem(this->metatile->layerType());
- ui->spinBox_rawAttributesValue->setValue(this->metatile->getAttributes());
+ const QSignalBlocker b_MetatileBehaviors(ui->comboBox_MetatileBehaviors);
+ const QSignalBlocker b_EncounterType(ui->comboBox_EncounterType);
+ const QSignalBlocker b_TerrainType(ui->comboBox_TerrainType);
+ const QSignalBlocker b_LayerType(ui->comboBox_LayerType);
+ const QSignalBlocker b_RawAttributesValue(ui->spinBox_RawAttributesValue);
+ ui->comboBox_MetatileBehaviors->setHexItem(this->metatile->behavior());
+ ui->comboBox_EncounterType->setHexItem(this->metatile->encounterType());
+ ui->comboBox_TerrainType->setHexItem(this->metatile->terrainType());
+ ui->comboBox_LayerType->setHexItem(this->metatile->layerType());
+ ui->spinBox_RawAttributesValue->setValue(this->metatile->getAttributes());
this->metatileSelector->drawSelectedMetatile();
}
void TilesetEditor::commitMetatileBehavior() {
- commitAttributeFromComboBox(Metatile::Attr::Behavior, ui->comboBox_metatileBehaviors);
+ commitAttributeFromComboBox(Metatile::Attr::Behavior, ui->comboBox_MetatileBehaviors);
}
void TilesetEditor::commitEncounterType() {
- commitAttributeFromComboBox(Metatile::Attr::EncounterType, ui->comboBox_encounterType);
+ commitAttributeFromComboBox(Metatile::Attr::EncounterType, ui->comboBox_EncounterType);
}
void TilesetEditor::commitTerrainType() {
- commitAttributeFromComboBox(Metatile::Attr::TerrainType, ui->comboBox_terrainType);
+ commitAttributeFromComboBox(Metatile::Attr::TerrainType, ui->comboBox_TerrainType);
};
void TilesetEditor::commitLayerType() {
- commitAttributeFromComboBox(Metatile::Attr::LayerType, ui->comboBox_layerType);
+ commitAttributeFromComboBox(Metatile::Attr::LayerType, ui->comboBox_LayerType);
this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear
}
@@ -695,6 +743,7 @@ bool TilesetEditor::save() {
this->lockSelection = true;
bool success = this->project->saveTilesets(this->primaryTileset, this->secondaryTileset);
+ applyMetatileSwapsToLayouts();
emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name);
if (this->paletteEditor) {
this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
@@ -745,7 +794,7 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset) {
int numTilesWide = image.width() / Tile::pixelWidth();
int numTilesHigh = image.height() / Tile::pixelHeight();
int totalTiles = numTilesHigh * numTilesWide;
- int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesTotal() - Project::getNumTilesPrimary();
+ int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesSecondary();
if (totalTiles > maxAllowedTiles) {
ErrorMessage::show(QStringLiteral("Failed to import tiles."),
QString("The maximum number of tiles allowed in the %1 tileset is %2, but the provided image contains %3 total tiles.")
@@ -846,8 +895,15 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered()
if (dialog.exec() == QDialog::Accepted) {
this->primaryTileset->resizeMetatiles(primarySpinBox->value());
this->secondaryTileset->resizeMetatiles(secondarySpinBox->value());
- this->metatileSelector->updateSelectedMetatile();
- this->refresh();
+
+ // Our selected metatile ID may have become invalid. Make sure it's in-bounds.
+ uint16_t metatileId = this->metatileSelector->getSelectedMetatileId();
+ Tileset *tileset = Tileset::getMetatileTileset(metatileId, this->primaryTileset, this->secondaryTileset);
+ if (tileset && !tileset->containsMetatileId(metatileId)) {
+ this->metatileSelector->select(qBound(tileset->firstMetatileId(), metatileId, tileset->lastMetatileId()));
+ }
+
+ refresh();
this->hasUnsavedChanges = true;
}
}
@@ -869,23 +925,22 @@ void TilesetEditor::onPaletteEditorChangedPaletteColor() {
this->hasUnsavedChanges = true;
}
-bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, QString newLabel)
-{
+bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile &src, QString newLabel) {
Metatile * dest = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset);
QString oldLabel = Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset);
- if (!dest || !src || (*dest == *src && oldLabel == newLabel))
+ if (!dest || (*dest == src && oldLabel == newLabel))
return false;
Tileset::setMetatileLabel(metatileId, newLabel, this->primaryTileset, this->secondaryTileset);
if (metatileId == this->getSelectedMetatileId())
- this->ui->lineEdit_metatileLabel->setText(newLabel);
+ this->ui->lineEdit_MetatileLabel->setText(newLabel);
// Update tile usage if any tiles changed
if (this->tileSelector && this->tileSelector->showUnused) {
int numTiles = projectConfig.getNumTilesInMetatile();
for (int i = 0; i < numTiles; i++) {
- if (src->tiles[i].tileId != dest->tiles[i].tileId) {
- this->tileSelector->usedTiles[src->tiles[i].tileId] += 1;
+ if (src.tiles[i].tileId != dest->tiles[i].tileId) {
+ this->tileSelector->usedTiles[src.tiles[i].tileId] += 1;
this->tileSelector->usedTiles[dest->tiles[i].tileId] -= 1;
}
}
@@ -893,7 +948,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, Q
}
this->metatile = dest;
- *this->metatile = *src;
+ *this->metatile = src;
this->metatileSelector->select(metatileId);
this->metatileSelector->drawMetatile(metatileId);
this->metatileLayersItem->draw();
@@ -902,29 +957,52 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, Q
return true;
}
-void TilesetEditor::on_actionUndo_triggered()
-{
- MetatileHistoryItem *commit = this->metatileHistory.current();
- if (!commit) return;
- Metatile *prev = commit->prevMetatile;
- if (!prev) return;
- this->metatileHistory.back();
- this->replaceMetatile(commit->metatileId, prev, commit->prevLabel);
+void TilesetEditor::initMetatileHistory() {
+ this->metatileHistory.clear();
+ updateEditHistoryActions();
+ this->hasUnsavedChanges = false;
}
-void TilesetEditor::on_actionRedo_triggered()
-{
+void TilesetEditor::commit(MetatileHistoryItem *item) {
+ this->metatileHistory.push(item);
+ updateEditHistoryActions();
+ this->hasUnsavedChanges = true;
+}
+
+void TilesetEditor::updateEditHistoryActions() {
+ ui->actionUndo->setEnabled(this->metatileHistory.canUndo());
+ ui->actionRedo->setEnabled(this->metatileHistory.canRedo());
+}
+
+void TilesetEditor::on_actionUndo_triggered() {
+ MetatileHistoryItem *commit = this->metatileHistory.current();
+ if (!commit) return;
+ this->metatileHistory.back();
+
+ if (commit->isSwap) {
+ swapMetatiles(commit->swapMetatileId, commit->metatileId);
+ } else if (commit->prevMetatile) {
+ replaceMetatile(commit->metatileId, *commit->prevMetatile, commit->prevLabel);
+ };
+ updateEditHistoryActions();
+}
+
+void TilesetEditor::on_actionRedo_triggered() {
MetatileHistoryItem *commit = this->metatileHistory.next();
if (!commit) return;
- this->replaceMetatile(commit->metatileId, commit->newMetatile, commit->newLabel);
+
+ if (commit->isSwap) {
+ swapMetatiles(commit->metatileId, commit->swapMetatileId);
+ } else if (commit->newMetatile) {
+ replaceMetatile(commit->metatileId, *commit->newMetatile, commit->newLabel);
+ }
+ updateEditHistoryActions();
}
void TilesetEditor::on_actionCut_triggered()
{
- Metatile * empty = new Metatile(projectConfig.getNumTilesInMetatile());
this->copyMetatile(true);
- this->pasteMetatile(empty, "");
- delete empty;
+ this->pasteMetatile(Metatile(projectConfig.getNumTilesInMetatile()), "");
}
void TilesetEditor::on_actionCopy_triggered()
@@ -934,7 +1012,9 @@ void TilesetEditor::on_actionCopy_triggered()
void TilesetEditor::on_actionPaste_triggered()
{
- this->pasteMetatile(this->copiedMetatile, this->copiedMetatileLabel);
+ if (this->copiedMetatile) {
+ this->pasteMetatile(*this->copiedMetatile, this->copiedMetatileLabel);
+ }
}
void TilesetEditor::copyMetatile(bool cut) {
@@ -947,14 +1027,17 @@ void TilesetEditor::copyMetatile(bool cut) {
else
*this->copiedMetatile = *toCopy;
+ ui->actionPaste->setEnabled(true);
+
// Don't try to copy the label unless it's a cut, these should be unique to each metatile.
this->copiedMetatileLabel = cut ? Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset) : QString();
}
-void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel)
-{
+void TilesetEditor::pasteMetatile(const Metatile &toPaste, QString newLabel) {
+ if (!this->metatile) return;
+
Metatile *prevMetatile = new Metatile(*this->metatile);
- QString prevLabel = this->ui->lineEdit_metatileLabel->text();
+ QString prevLabel = this->ui->lineEdit_MetatileLabel->text();
if (newLabel.isNull()) newLabel = prevLabel; // Don't change the label if one wasn't copied
uint16_t metatileId = this->getSelectedMetatileId();
if (!this->replaceMetatile(metatileId, toPaste, newLabel)) {
@@ -1055,15 +1138,13 @@ void TilesetEditor::importAdvanceMapMetatiles(Tileset *tileset) {
uint16_t metatileId = static_cast(metatileIdBase + i);
QString prevLabel = Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset);
Metatile *prevMetatile = new Metatile(*tileset->metatileAt(i));
- MetatileHistoryItem *commit = new MetatileHistoryItem(metatileId,
- prevMetatile, new Metatile(*metatiles.at(i)),
- prevLabel, prevLabel);
- metatileHistory.push(commit);
+ commit(new MetatileHistoryItem(metatileId,
+ prevMetatile, new Metatile(*metatiles.at(i)),
+ prevLabel, prevLabel));
}
tileset->setMetatiles(metatiles);
this->refresh();
- this->hasUnsavedChanges = true;
}
void TilesetEditor::on_actionShow_Unused_toggled(bool checked) {
@@ -1152,59 +1233,28 @@ void TilesetEditor::countTileUsage() {
this->tileSelector->usedTiles.resize(Project::getNumTilesTotal());
this->tileSelector->usedTiles.fill(0);
- QSet primaryTilesets;
- QSet secondaryTilesets;
-
- for (const auto &layoutId : this->project->layoutIds()) {
- Layout *layout = this->project->getLayout(layoutId);
- if (layout->tileset_primary_label == this->primaryTileset->name
- || layout->tileset_secondary_label == this->secondaryTileset->name) {
- // need to check metatiles
- this->project->loadLayoutTilesets(layout);
- if (layout->tileset_primary && layout->tileset_secondary) {
- primaryTilesets.insert(layout->tileset_primary);
- secondaryTilesets.insert(layout->tileset_secondary);
+ auto countTilesetTileUsage = [this](Tileset *searchTileset) {
+ // Count usage of our search tileset's tiles (in itself, and in any tilesets it gets paired with).
+ QSet tilesetNames = this->project->getPairedTilesetLabels(searchTileset);
+ tilesetNames.insert(searchTileset->name);
+ for (const auto &tilesetName : tilesetNames) {
+ Tileset *tileset = this->project->getTileset(tilesetName);
+ if (!tileset) continue;
+ for (const auto &metatile : tileset->metatiles()) {
+ for (const auto &tile : metatile->tiles) {
+ if (searchTileset->containsTileId(tile.tileId)) {
+ this->tileSelector->usedTiles[tile.tileId]++;
+ }
+ }
}
}
- }
+ };
- // check primary tilesets that are used with this secondary tileset for
- // reference to secondary tiles in primary metatiles
- for (const auto &tileset : primaryTilesets) {
- for (const auto &metatile : tileset->metatiles()) {
- for (const auto &tile : metatile->tiles) {
- if (tile.tileId >= Project::getNumTilesPrimary())
- this->tileSelector->usedTiles[tile.tileId]++;
- }
- }
- }
-
- // do the opposite for primary tiles in secondary metatiles
- for (Tileset *tileset : secondaryTilesets) {
- for (const auto &metatile : tileset->metatiles()) {
- for (const auto &tile : metatile->tiles) {
- if (tile.tileId < Project::getNumTilesPrimary())
- this->tileSelector->usedTiles[tile.tileId]++;
- }
- }
- }
-
- // check this primary tileset metatiles
- for (const auto &metatile : this->primaryTileset->metatiles()) {
- for (const auto &tile : metatile->tiles) {
- this->tileSelector->usedTiles[tile.tileId]++;
- }
- }
-
- // and the secondary metatiles
- for (const auto &metatile : this->secondaryTileset->metatiles()) {
- for (const auto &tile : metatile->tiles) {
- this->tileSelector->usedTiles[tile.tileId]++;
- }
- }
+ countTilesetTileUsage(this->primaryTileset);
+ countTilesetTileUsage(this->secondaryTileset);
}
-void TilesetEditor::on_copyButton_metatileLabel_clicked() {
+void TilesetEditor::on_copyButton_MetatileLabel_clicked() {
uint16_t metatileId = this->getSelectedMetatileId();
QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset);
if (label.isEmpty()) return;
@@ -1212,7 +1262,7 @@ void TilesetEditor::on_copyButton_metatileLabel_clicked() {
if (tileset)
label.prepend(tileset->getMetatileLabelPrefix());
QGuiApplication::clipboard()->setText(label);
- QToolTip::showText(this->ui->copyButton_metatileLabel->mapToGlobal(QPoint(0, 0)), "Copied!");
+ QToolTip::showText(this->ui->copyButton_MetatileLabel->mapToGlobal(QPoint(0, 0)), "Copied!");
}
void TilesetEditor::on_horizontalSlider_MetatilesZoom_valueChanged(int value) {
@@ -1267,3 +1317,118 @@ void TilesetEditor::redrawTileSelector() {
this->ui->scrollArea_Tiles->ensureVisible(pos.x(), pos.y(), viewport->width() / 2, viewport->height() / 2);
}
}
+
+void TilesetEditor::commitMetatileSwap(uint16_t metatileIdA, uint16_t metatileIdB) {
+ if (swapMetatiles(metatileIdA, metatileIdB)) {
+ commit(new MetatileHistoryItem(metatileIdA, metatileIdB));
+ }
+}
+
+bool TilesetEditor::swapMetatiles(uint16_t metatileIdA, uint16_t metatileIdB) {
+ this->metatileSelector->clearSwapSelection();
+
+ QList metatiles;
+ for (const auto &metatileId : {metatileIdA, metatileIdB}) {
+ Metatile *metatile = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset);
+ if (metatile) {
+ metatiles.append(metatile);
+ } else {
+ logError(QString("Failed to load metatile %1 for swap.").arg(Metatile::getMetatileIdString(metatileId)));
+ }
+ }
+ if (metatiles.length() < 2)
+ return false;
+
+ // Swap the metatile data in the tileset
+ Metatile tempMetatile = *metatiles.at(0);
+ QString tempLabel = Tileset::getOwnedMetatileLabel(metatileIdA, this->primaryTileset, this->secondaryTileset);
+ replaceMetatile(metatileIdA, *metatiles.at(1), Tileset::getOwnedMetatileLabel(metatileIdB, this->primaryTileset, this->secondaryTileset));
+ replaceMetatile(metatileIdB, tempMetatile, tempLabel);
+
+ // Record this swap so that we can update the layouts later.
+ // If this is the inverse of the most recent swap (e.g. from Undo), we instead remove that swap to save time.
+ if (!this->metatileIdSwaps.isEmpty()) {
+ auto recentSwapPair = this->metatileIdSwaps.constLast();
+ if (recentSwapPair.first == metatileIdB && recentSwapPair.second == metatileIdA) {
+ this->metatileIdSwaps.removeLast();
+ return true;
+ }
+ }
+ this->metatileIdSwaps.append(QPair(metatileIdA, metatileIdB));
+ return true;
+}
+
+// If any metatiles swapped positions, apply the swap to all relevant layouts.
+// We only do this once changes in the Tileset Editor are saved.
+void TilesetEditor::applyMetatileSwapsToLayouts() {
+ if (this->metatileIdSwaps.isEmpty())
+ return;
+
+ QProgressDialog progress("", "", 0, this->metatileIdSwaps.length(), this);
+ progress.setAutoClose(true);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setModal(true);
+ progress.setMinimumDuration(1000);
+ progress.setValue(progress.minimum());
+
+ for (const auto &swapPair : this->metatileIdSwaps) {
+ progress.setLabelText(QString("Swapping metatiles %1 and %2 in map layouts...")
+ .arg(Metatile::getMetatileIdString(swapPair.first))
+ .arg(Metatile::getMetatileIdString(swapPair.second)));
+ applyMetatileSwapToLayouts(swapPair.first, swapPair.second);
+ progress.setValue(progress.value() + 1);
+ }
+ this->metatileIdSwaps.clear();
+}
+
+void TilesetEditor::applyMetatileSwapToLayouts(uint16_t metatileIdA, uint16_t metatileIdB) {
+ struct TilesetPair {
+ Tileset* primary = nullptr;
+ Tileset* secondary = nullptr;
+ };
+ TilesetPair tilesets;
+
+ // Get which tilesets our swapped metatiles belong to.
+ auto addSourceTileset = [this](uint16_t metatileId, TilesetPair *tilesets) {
+ if (this->primaryTileset->containsMetatileId(metatileId)) {
+ tilesets->primary = this->primaryTileset;
+ } else if (this->secondaryTileset->containsMetatileId(metatileId)) {
+ tilesets->secondary = this->secondaryTileset;
+ } else {
+ // Invalid metatile, shouldn't happen
+ this->metatileSelector->removeFromSwapSelection(metatileId);
+ }
+ };
+ addSourceTileset(metatileIdA, &tilesets);
+ addSourceTileset(metatileIdB, &tilesets);
+ if (!tilesets.primary && !tilesets.secondary) {
+ return;
+ }
+
+ // In each layout that uses the appropriate tileset(s), swap the two metatiles.
+ QSet layoutIds = this->project->getTilesetLayoutIds(tilesets.primary, tilesets.secondary);
+ for (const auto &layoutId : layoutIds) {
+ Layout *layout = this->project->loadLayout(layoutId);
+ if (!layout) continue;
+ // Perform swap(s) in layout's main data.
+ for (int y = 0; y < layout->height; y++)
+ for (int x = 0; x < layout->width; x++) {
+ uint16_t metatileId = layout->getMetatileId(x, y);
+ if (metatileId == metatileIdA) {
+ layout->setMetatileId(x, y, metatileIdB);
+ } else if (metatileId == metatileIdB) {
+ layout->setMetatileId(x, y, metatileIdA);
+ } else continue;
+ layout->hasUnsavedDataChanges = true;
+ }
+ // Perform swap(s) in layout's border data.
+ for (auto &borderBlock : layout->border) {
+ if (borderBlock.metatileId() == metatileIdA) {
+ borderBlock.setMetatileId(metatileIdB);
+ } else if (borderBlock.metatileId() == metatileIdB) {
+ borderBlock.setMetatileId(metatileIdA);
+ } else continue;
+ layout->hasUnsavedDataChanges = true;
+ }
+ }
+}
diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp
index bba51e77..1329a49b 100644
--- a/src/ui/tileseteditormetatileselector.cpp
+++ b/src/ui/tileseteditormetatileselector.cpp
@@ -81,7 +81,17 @@ void TilesetEditorMetatileSelector::draw() {
drawDivider();
drawFilters();
- drawSelection();
+ if (this->inSwapMode) {
+ QSet metatileIds(this->swapMetatileIds.constBegin(), this->swapMetatileIds.constEnd());
+ metatileIds.insert(this->lastHoveredMetatileId);
+ for (const auto &metatileId : metatileIds) {
+ bool ok;
+ QPoint pos = metatileIdToPos(metatileId, &ok);
+ if (ok) drawSelectionRect(pos, QSize(1,1), Qt::DashLine);
+ }
+ } else if (isValidMetatileId(this->selectedMetatileId)) {
+ drawSelection();
+ }
}
bool TilesetEditorMetatileSelector::select(uint16_t metatileId) {
@@ -103,53 +113,103 @@ void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset
draw();
}
-void TilesetEditorMetatileSelector::updateSelectedMetatile() {
- bool ok;
- uint16_t metatileId = posToMetatileId(getSelectionStart(), &ok);
- if (!ok)
+void TilesetEditorMetatileSelector::addToSwapSelection(uint16_t metatileId) {
+ if (this->swapMetatileIds.contains(metatileId)) {
return;
+ }
+ if (this->swapMetatileIds.length() >= 2) {
+ this->swapMetatileIds.clear();
+ }
- this->selectedMetatileId = metatileId;
- emit selectedMetatileChanged(this->selectedMetatileId);
+ this->swapMetatileIds.append(metatileId);
+ draw();
+
+ if (this->swapMetatileIds.length() == 2) {
+ emit swapRequested(this->swapMetatileIds.at(0), this->swapMetatileIds.at(1));
+ }
}
-bool TilesetEditorMetatileSelector::shouldAcceptEvent(QGraphicsSceneMouseEvent *event) {
- bool ok;
- posToMetatileId(getCellPos(event->pos()), &ok);
- return ok;
+void TilesetEditorMetatileSelector::removeFromSwapSelection(uint16_t metatileId) {
+ if (this->swapMetatileIds.removeOne(metatileId)) {
+ draw();
+ }
+}
+
+void TilesetEditorMetatileSelector::clearSwapSelection() {
+ if (this->swapMetatileIds.isEmpty())
+ return;
+ this->swapMetatileIds.clear();
+ draw();
}
void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) {
- if (!shouldAcceptEvent(event)) return;
+ QPoint cellPos = getCellPos(event->pos());
+
+ bool ok;
+ uint16_t metatileId = posToMetatileId(cellPos, &ok);
+ if (!ok) return;
+
SelectablePixmapItem::mousePressEvent(event);
- this->updateSelectedMetatile();
+ this->selectedMetatileId = this->lastHoveredMetatileId = metatileId;
+ emit selectedMetatileChanged(this->selectedMetatileId);
+ this->prevCellPos = cellPos;
}
void TilesetEditorMetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
- if (!shouldAcceptEvent(event)) return;
+ QPoint cellPos = getCellPos(event->pos());
+ if (cellPos == this->prevCellPos) return;
+
+ bool ok;
+ uint16_t metatileId = posToMetatileId(cellPos, &ok);
+ if (!ok) return;
+
SelectablePixmapItem::mouseMoveEvent(event);
- this->updateSelectedMetatile();
+ this->selectedMetatileId = this->lastHoveredMetatileId = metatileId;
+ emit selectedMetatileChanged(this->selectedMetatileId);
emit hoveredMetatileChanged(this->selectedMetatileId);
+ this->prevCellPos = cellPos;
}
void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
- if (!shouldAcceptEvent(event)) return;
+ QPoint cellPos = getCellPos(event->pos());
+
+ bool ok;
+ uint16_t metatileId = posToMetatileId(cellPos, &ok);
+ if (!ok) return;
+
+ if (this->inSwapMode) {
+ if (this->swapMetatileIds.contains(metatileId)) {
+ this->removeFromSwapSelection(metatileId);
+ } else {
+ this->addToSwapSelection(metatileId);
+ }
+ }
+
SelectablePixmapItem::mouseReleaseEvent(event);
- this->updateSelectedMetatile();
+ this->selectedMetatileId = this->lastHoveredMetatileId = metatileId;
+ emit selectedMetatileChanged(this->selectedMetatileId);
+ this->prevCellPos = cellPos;
}
void TilesetEditorMetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
+ QPoint cellPos = getCellPos(event->pos());
+ if (cellPos == this->prevCellPos) return;
+
bool ok;
- uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok);
+ uint16_t metatileId = posToMetatileId(cellPos, &ok);
if (ok) {
+ this->lastHoveredMetatileId = metatileId;
emit this->hoveredMetatileChanged(metatileId);
+ if (this->inSwapMode) draw();
} else {
emit this->hoveredMetatileCleared();
}
+ this->prevCellPos = cellPos;
}
void TilesetEditorMetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
emit this->hoveredMetatileCleared();
+ this->prevCellPos = QPoint(-1,-1);
}
uint16_t TilesetEditorMetatileSelector::posToMetatileId(const QPoint &pos, bool *ok) const {
@@ -160,7 +220,7 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok)
if (ok) *ok = true;
int index = y * this->numMetatilesWide + x;
uint16_t metatileId = static_cast(index);
- if (this->primaryTileset && this->primaryTileset->contains(metatileId)) {
+ if (this->primaryTileset && this->primaryTileset->containsMetatileId(metatileId)) {
return metatileId;
}
@@ -171,7 +231,7 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok)
int numPrimaryRounded = numPrimaryMetatilesRounded();
int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide;
metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded);
- if (this->secondaryTileset && this->secondaryTileset->contains(metatileId) && y >= firstSecondaryRow) {
+ if (this->secondaryTileset && this->secondaryTileset->containsMetatileId(metatileId) && y >= firstSecondaryRow) {
return metatileId;
}
@@ -180,12 +240,12 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok)
}
QPoint TilesetEditorMetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const {
- if (this->primaryTileset && this->primaryTileset->contains(metatileId)) {
+ if (this->primaryTileset && this->primaryTileset->containsMetatileId(metatileId)) {
if (ok) *ok = true;
int index = metatileId;
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
}
- if (this->secondaryTileset && this->secondaryTileset->contains(metatileId)) {
+ if (this->secondaryTileset && this->secondaryTileset->containsMetatileId(metatileId)) {
if (ok) *ok = true;
int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded();
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
@@ -195,6 +255,12 @@ QPoint TilesetEditorMetatileSelector::metatileIdToPos(uint16_t metatileId, bool
return QPoint(0,0);
}
+bool TilesetEditorMetatileSelector::isValidMetatileId(uint16_t metatileId) const {
+ bool ok;
+ metatileIdToPos(metatileId, &ok);
+ return ok;
+}
+
QPoint TilesetEditorMetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) const {
QPoint pos = metatileIdToPos(metatileId);
pos.rx() = (pos.x() * this->cellWidth) + (this->cellWidth / 2);
@@ -323,3 +389,19 @@ void TilesetEditorMetatileSelector::drawCounts() {
this->setPixmap(metatilesPixmap);
}
+
+void TilesetEditorMetatileSelector::setSwapMode(bool enabled) {
+ if (enabled == this->inSwapMode)
+ return;
+ this->inSwapMode = enabled;
+ this->swapMetatileIds.clear();
+ if (porymapConfig.prettyCursors) {
+ if (enabled) {
+ static const QCursor cursor = QCursor(QPixmap(":/icons/swap_cursor.ico"), 10, 10);
+ setCursor(cursor);
+ } else {
+ unsetCursor();
+ }
+ }
+ draw();
+}
diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp
index 95d5be6c..e82e7dc6 100644
--- a/src/ui/tileseteditortileselector.cpp
+++ b/src/ui/tileseteditortileselector.cpp
@@ -4,14 +4,38 @@
#include
#include
-QPoint TilesetEditorTileSelector::getSelectionDimensions() const {
+QSize TilesetEditorTileSelector::getSelectionDimensions() const {
if (this->externalSelection) {
- return QPoint(this->externalSelectionWidth, this->externalSelectionHeight);
+ return QSize(this->externalSelectionWidth, this->externalSelectionHeight);
} else {
return SelectablePixmapItem::getSelectionDimensions();
}
}
+void TilesetEditorTileSelector::setMaxSelectionSize(int width, int height) {
+ width = qMax(1, width);
+ height = qMax(1, height);
+ SelectablePixmapItem::setMaxSelectionSize(width, height);
+ if (this->externalSelection) {
+ if (this->externalSelectionWidth > this->maxSelectionWidth || this->externalSelectionHeight > this->maxSelectionHeight) {
+ // Crop external selection to new max size.
+ QList cropped;
+ int croppedWidth = qMin(this->externalSelectionWidth, this->maxSelectionWidth);
+ int croppedHeight = qMin(this->externalSelectionHeight, this->maxSelectionHeight);
+ for (int y = 0; y < croppedHeight; y++)
+ for (int x = 0; x < croppedWidth; x++) {
+ int index = y * this->externalSelectionWidth + x;
+ cropped.append(this->externalSelectedTiles.value(index));
+ }
+ this->externalSelectionWidth = croppedWidth;
+ this->externalSelectionHeight = croppedHeight;
+ this->externalSelectedTiles = cropped;
+ }
+ } else {
+ updateSelectedTiles();
+ }
+}
+
void TilesetEditorTileSelector::updateBasePixmap() {
if (!this->primaryTileset || !this->secondaryTileset || this->numTilesWide == 0) {
this->basePixmap = QPixmap();
@@ -30,22 +54,26 @@ void TilesetEditorTileSelector::updateBasePixmap() {
int y = (tileId / this->numTilesWide) * this->cellHeight;
painter.drawImage(x, y, tileImage);
}
-
- if (this->showDivider) {
- int row = Util::roundUpToMultiple(Project::getNumTilesPrimary(), this->numTilesWide) / this->numTilesWide;
- const int y = row * this->cellHeight;
- painter.setPen(Qt::white);
- painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y);
- }
-
painter.end();
+
this->basePixmap = QPixmap::fromImage(image);
}
void TilesetEditorTileSelector::draw() {
if (this->basePixmap.isNull())
updateBasePixmap();
- setPixmap(this->basePixmap);
+
+ QPixmap pixmap = this->basePixmap;
+
+ if (this->showDivider) {
+ QPainter painter(&pixmap);
+ int row = Util::roundUpToMultiple(Project::getNumTilesPrimary(), this->numTilesWide) / this->numTilesWide;
+ const int y = row * this->cellHeight;
+ painter.setPen(Qt::white);
+ painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y);
+ }
+
+ setPixmap(pixmap);
if (!this->externalSelection || (this->externalSelectionWidth == 1 && this->externalSelectionHeight == 1)) {
this->drawSelection();
@@ -90,9 +118,9 @@ void TilesetEditorTileSelector::updateSelectedTiles() {
this->externalSelection = false;
this->selectedTiles.clear();
QPoint origin = this->getSelectionStart();
- QPoint dimensions = this->getSelectionDimensions();
- for (int j = 0; j < dimensions.y(); j++) {
- for (int i = 0; i < dimensions.x(); i++) {
+ QSize dimensions = this->getSelectionDimensions();
+ for (int j = 0; j < dimensions.height(); j++) {
+ for (int i = 0; i < dimensions.width(); i++) {
uint16_t metatileId = this->getTileId(origin.x() + i, origin.y() + j);
this->selectedTiles.append(metatileId);
}
@@ -103,17 +131,17 @@ QList TilesetEditorTileSelector::getSelectedTiles() {
if (this->externalSelection) {
return buildSelectedTiles(this->externalSelectionWidth, this->externalSelectionHeight, this->externalSelectedTiles);
} else {
- QPoint dimensions = this->getSelectionDimensions();
+ QSize dimensions = this->getSelectionDimensions();
QList tiles;
for (int i = 0; i < this->selectedTiles.length(); i++) {
uint16_t tile = this->selectedTiles.at(i);
tiles.append(Tile(tile, false, false, this->paletteId));
}
- return buildSelectedTiles(dimensions.x(), dimensions.y(), tiles);
+ return buildSelectedTiles(dimensions.width(), dimensions.height(), tiles);
}
}
-QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, QList selected) {
+QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, const QList &selected) {
QList tiles;
QList> tileMatrix;
for (int j = 0; j < height; j++) {
@@ -121,7 +149,7 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height,
QList layerRow;
for (int i = 0; i < width; i++) {
int index = i + j * width;
- Tile tile = selected.at(index);
+ Tile tile = selected.value(index);
tile.xflip ^= this->xFlip;
tile.yflip ^= this->yFlip;
if (this->paletteChanged)
@@ -134,7 +162,7 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height,
// If we've completed a layer row, or its the last tile of an incompletely
// selected layer, then append the layer row to the full row
// If not an external selection, treat the whole row as 1 "layer"
- if (i == width - 1 || (this->externalSelection && (this->externalSelectedPos.at(index) % 4) & 1)) {
+ if (i == width - 1) {
row.append(layerRow);
layerRow.clear();
}
@@ -152,15 +180,14 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height,
return tiles;
}
-void TilesetEditorTileSelector::setExternalSelection(int width, int height, QList tiles, QList tileIdxs) {
+void TilesetEditorTileSelector::setExternalSelection(int width, int height, const QList &tiles) {
+ width = qBound(1, width, this->maxSelectionWidth);
+ height = qBound(1, height, this->maxSelectionHeight);
this->externalSelection = true;
this->paletteChanged = false;
this->externalSelectionWidth = width;
this->externalSelectionHeight = height;
- this->externalSelectedTiles.clear();
- this->externalSelectedTiles.append(tiles);
- this->externalSelectedPos.clear();
- this->externalSelectedPos.append(tileIdxs);
+ this->externalSelectedTiles = tiles.mid(0, width * height);
this->draw();
emit selectedTilesChanged();
}
@@ -170,16 +197,20 @@ uint16_t TilesetEditorTileSelector::getTileId(int x, int y) {
}
void TilesetEditorTileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) {
+ this->prevCellPos = getCellPos(event->pos());
SelectablePixmapItem::mousePressEvent(event);
this->updateSelectedTiles();
emit selectedTilesChanged();
}
void TilesetEditorTileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
+ QPoint pos = getCellPos(event->pos());
+ if (this->prevCellPos == pos)
+ return;
+ this->prevCellPos = pos;
+
SelectablePixmapItem::mouseMoveEvent(event);
this->updateSelectedTiles();
-
- QPoint pos = this->getCellPos(event->pos());
uint16_t tile = this->getTileId(pos.x(), pos.y());
emit hoveredTileChanged(tile);
emit selectedTilesChanged();