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 b7f78f66..4c797767 100644
--- a/forms/tileseteditor.ui
+++ b/forms/tileseteditor.ui
@@ -681,6 +681,7 @@
+
+
+ false
+
Undo
@@ -760,6 +764,9 @@
+
+ false
+
Redo
@@ -784,6 +791,9 @@
+
+ false
+
Paste
@@ -887,6 +897,17 @@
Vertical
+
+
+ true
+
+
+ Swap Metatiles
+
+
+ X
+
+
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/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/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 433b2bca..a267291e 100644
--- a/include/ui/selectablepixmapitem.h
+++ b/include/ui/selectablepixmapitem.h
@@ -26,6 +26,8 @@ public:
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;
@@ -40,10 +42,13 @@ protected:
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(QPointF);
+ 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; }
@@ -53,6 +58,9 @@ signals:
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 43467713..75a81a24 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
@@ -122,8 +132,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);
@@ -134,11 +144,17 @@ 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();
Ui::TilesetEditor *ui;
History metatileHistory;
@@ -162,6 +178,7 @@ private:
bool lockSelection = false;
QSet metatileReloadQueue;
MetatileImageExporter::Settings *metatileImageExportSettings = nullptr;
+ QList> metatileIdSwaps;
bool save();
diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h
index 6ca9088f..4339733a 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;
@@ -42,11 +46,15 @@ private:
Tileset *primaryTileset = nullptr;
Tileset *secondaryTileset = nullptr;
uint16_t selectedMetatileId;
+
+ QList swapMetatileIds;
+ 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 +68,7 @@ signals:
void hoveredMetatileChanged(uint16_t);
void hoveredMetatileCleared();
void selectedMetatileChanged(uint16_t);
+ void swapRequested(uint16_t, uint16_t);
};
#endif // TILESETEDITORMETATILESELECTOR_H
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/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/project.cpp b/src/project.cpp
index 07ac72ea..f26a49ad 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -3563,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/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp
index 34afce00..8ec0cceb 100644
--- a/src/ui/metatilelayersitem.cpp
+++ b/src/ui/metatilelayersitem.cpp
@@ -163,7 +163,7 @@ void MetatileLayersItem::hover(const QPoint &pos) {
this->prevHoveredPos = pos;
int tileIndex = posToTileIndex(pos);
- if (tileIndex < 0 || tileIndex >= this->metatile->tiles.length())
+ if (tileIndex < 0 || !this->metatile || tileIndex >= this->metatile->tiles.length())
return;
emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex));
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/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp
index 7a38913e..267fbd1f 100644
--- a/src/ui/selectablepixmapitem.cpp
+++ b/src/ui/selectablepixmapitem.cpp
@@ -10,12 +10,15 @@ QPoint SelectablePixmapItem::getSelectionStart()
return QPoint(x, y);
}
-void SelectablePixmapItem::select(const QPoint &pos, const QSize &size)
-{
+void SelectablePixmapItem::setSelection(const QPoint &pos, const QSize &size) {
this->selectionInitialX = pos.x();
this->selectionInitialY = pos.y();
- this->selectionOffsetX = qBound(0, size.width(), this->maxSelectionWidth - 1);
- this->selectionOffsetY = qBound(0, size.height(), this->maxSelectionHeight - 1);
+ 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());
}
@@ -23,10 +26,7 @@ void SelectablePixmapItem::select(const QPoint &pos, const QSize &size)
void SelectablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPoint pos = getCellPos(event->pos());
- this->selectionInitialX = pos.x();
- this->selectionInitialY = pos.y();
- this->selectionOffsetX = 0;
- this->selectionOffsetY = 0;
+ setSelection(pos, QSize(1,1));
this->prevCellPos = pos;
updateSelection(pos);
}
@@ -52,12 +52,10 @@ void SelectablePixmapItem::setMaxSelectionSize(int width, int height) {
// Update the selection if we shrank below the current selection size.
QSize size = getSelectionDimensions();
if (size.width() > this->maxSelectionWidth || size.height() > this->maxSelectionHeight) {
- QPoint origin = getSelectionStart();
- this->selectionInitialX = origin.x();
- this->selectionInitialY = origin.y();
- this->selectionOffsetX = qMin(size.width(), this->maxSelectionWidth) - 1;
- this->selectionOffsetY = qMin(size.height(), this->maxSelectionHeight) - 1;
+ 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());
}
}
@@ -90,18 +88,25 @@ void SelectablePixmapItem::updateSelection(const QPoint &pos) {
emit selectionChanged(QPoint(x, y), getSelectionDimensions());
}
-QPoint SelectablePixmapItem::getCellPos(QPointF pos) {
+void SelectablePixmapItem::setSelectionStyle(Qt::PenStyle style) {
+ this->selectionStyle = style;
+ draw();
+}
+
+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(pos.x()), pixmap().width() - 1);
- int y = qBound(0, static_cast(pos.y()), pixmap().height() - 1);
+ 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() {
- QPoint origin = this->getSelectionStart();
- QSize dimensions = this->getSelectionDimensions();
+ 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.
@@ -110,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 5b4c34b1..879fa1f8 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -137,7 +137,7 @@ 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() {
@@ -217,12 +217,11 @@ void TilesetEditor::setRawAttributesVisible(bool visible) {
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);
@@ -389,13 +388,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)
@@ -472,6 +464,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.
@@ -513,6 +506,8 @@ void TilesetEditor::onHoveredTileCleared() {
}
void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) {
+ if (!this->metatile) return;
+
bool changed = false;
Metatile *prevMetatile = new Metatile(*this->metatile);
QSize dimensions = this->tileSelector->getSelectionDimensions();
@@ -563,7 +558,7 @@ void TilesetEditor::onMetatileLayerSelectionChanged(const QPoint &selectionOrigi
for (int i = 0; i < size.width(); i++) {
int tileIndex = this->metatileLayersItem->posToTileIndex(selectionOrigin.x() + i, selectionOrigin.y() + j);
if (tileIndex < maxTileIndex) {
- tiles.append(this->metatile->tiles.value(tileIndex));
+ tiles.append(this->metatile ? this->metatile->tiles.value(tileIndex) : Tile());
tileIdxs.append(tileIndex);
}
}
@@ -612,8 +607,9 @@ void TilesetEditor::on_lineEdit_metatileLabel_editingFinished()
commitMetatileLabel();
}
-void TilesetEditor::commitMetatileLabel()
-{
+void TilesetEditor::commitMetatileLabel() {
+ if (!this->metatile) return;
+
// Only commit if the field has changed.
uint16_t metatileId = this->getSelectedMetatileId();
QString oldLabel = Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset);
@@ -625,12 +621,12 @@ 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)
@@ -661,8 +657,7 @@ uint32_t TilesetEditor::attributeNameToValue(Metatile::Attr attribute, const QSt
}
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);
@@ -683,6 +678,8 @@ void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScro
}
void TilesetEditor::onRawAttributesEdited() {
+ if (!this->metatile) return;
+
uint32_t newAttributes = ui->spinBox_rawAttributesValue->value();
if (newAttributes != this->metatile->getAttributes()) {
Metatile *prevMetatile = new Metatile(*this->metatile);
@@ -732,6 +729,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);
@@ -883,8 +881,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->contains(metatileId)) {
+ this->metatileSelector->select(qBound(tileset->firstMetatileId(), metatileId, tileset->lastMetatileId()));
+ }
+
+ refresh();
this->hasUnsavedChanges = true;
}
}
@@ -906,11 +911,10 @@ 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);
@@ -921,8 +925,8 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, Q
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;
}
}
@@ -930,7 +934,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();
@@ -939,29 +943,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()
@@ -971,7 +998,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) {
@@ -984,12 +1013,15 @@ 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();
if (newLabel.isNull()) newLabel = prevLabel; // Don't change the label if one wasn't copied
@@ -1092,15 +1124,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) {
@@ -1189,35 +1219,11 @@ 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);
- }
- }
- }
-
- // 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) {
+ // Count usage of our primary tileset's tiles in the secondary tilesets it gets paired with.
+ QSet tilesetNames = this->project->getPairedTilesetLabels(this->primaryTileset);
+ 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 (tile.tileId < Project::getNumTilesPrimary())
@@ -1226,17 +1232,16 @@ void TilesetEditor::countTileUsage() {
}
}
- // 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]++;
+ // Count usage of our secondary tileset's tiles in the primary tilesets it gets paired with.
+ tilesetNames = this->project->getPairedTilesetLabels(this->secondaryTileset);
+ 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 (tile.tileId >= Project::getNumTilesPrimary())
+ this->tileSelector->usedTiles[tile.tileId]++;
+ }
}
}
}
@@ -1304,3 +1309,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->contains(metatileId)) {
+ tilesets->primary = this->primaryTileset;
+ } else if (this->secondaryTileset->contains(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..ebb05187 100644
--- a/src/ui/tileseteditormetatileselector.cpp
+++ b/src/ui/tileseteditormetatileselector.cpp
@@ -81,7 +81,15 @@ void TilesetEditorMetatileSelector::draw() {
drawDivider();
drawFilters();
- drawSelection();
+ if (this->inSwapMode) {
+ for (const auto &metatileId : this->swapMetatileIds) {
+ 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,39 +111,76 @@ 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->swapMetatileIds.append(metatileId);
+ draw();
+
+ if (this->swapMetatileIds.length() == 2) {
+ emit swapRequested(this->swapMetatileIds.at(0), this->swapMetatileIds.at(1));
+ }
+}
+
+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) {
+ bool ok;
+ uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok);
+ if (!ok) return;
+
+ if (this->inSwapMode) {
+ if (this->swapMetatileIds.contains(metatileId)) {
+ this->removeFromSwapSelection(metatileId);
+ } else {
+ this->addToSwapSelection(metatileId);
+ }
+ }
+
+ SelectablePixmapItem::mousePressEvent(event);
this->selectedMetatileId = metatileId;
emit selectedMetatileChanged(this->selectedMetatileId);
}
-bool TilesetEditorMetatileSelector::shouldAcceptEvent(QGraphicsSceneMouseEvent *event) {
- bool ok;
- posToMetatileId(getCellPos(event->pos()), &ok);
- return ok;
-}
-
-void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) {
- if (!shouldAcceptEvent(event)) return;
- SelectablePixmapItem::mousePressEvent(event);
- this->updateSelectedMetatile();
-}
-
void TilesetEditorMetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
- if (!shouldAcceptEvent(event)) return;
+ if (this->inSwapMode) return;
+
+ bool ok;
+ uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok);
+ if (!ok) return;
+
SelectablePixmapItem::mouseMoveEvent(event);
- this->updateSelectedMetatile();
+ this->selectedMetatileId = metatileId;
+ emit selectedMetatileChanged(this->selectedMetatileId);
emit hoveredMetatileChanged(this->selectedMetatileId);
}
void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
- if (!shouldAcceptEvent(event)) return;
+ if (this->inSwapMode) return;
+
+ bool ok;
+ uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok);
+ if (!ok) return;
+
SelectablePixmapItem::mouseReleaseEvent(event);
- this->updateSelectedMetatile();
+ this->selectedMetatileId = metatileId;
+ emit selectedMetatileChanged(this->selectedMetatileId);
}
void TilesetEditorMetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
@@ -195,6 +240,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 +374,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();
+}