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 @@ + @@ -752,6 +753,9 @@ + + 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(); +}