From 9ce865a9c17de48e5e7f6190295ad5c1d2b4ff5b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 28 Jul 2025 11:58:44 -0400 Subject: [PATCH 1/8] Replace QPoint where QSize was meant --- include/ui/cursortilerect.h | 3 +- include/ui/layoutpixmapitem.h | 12 ++-- include/ui/metatileselector.h | 4 +- include/ui/selectablepixmapitem.h | 2 +- include/ui/tileseteditortileselector.h | 2 +- src/editor.cpp | 3 +- src/mainwindow.cpp | 10 ++-- src/ui/bordermetatilespixmapitem.cpp | 6 +- src/ui/currentselectedmetatilespixmapitem.cpp | 10 ++-- src/ui/cursortilerect.cpp | 4 +- src/ui/layoutpixmapitem.cpp | 60 +++++++++---------- src/ui/metatilelayersitem.cpp | 12 ++-- src/ui/metatileselector.cpp | 12 ++-- src/ui/prefab.cpp | 12 ++-- src/ui/prefabcreationdialog.cpp | 6 +- src/ui/selectablepixmapitem.cpp | 8 +-- src/ui/tileseteditor.cpp | 14 ++--- src/ui/tileseteditortileselector.cpp | 14 ++--- 18 files changed, 97 insertions(+), 97 deletions(-) diff --git a/include/ui/cursortilerect.h b/include/ui/cursortilerect.h index 92f37f96..fd150dc2 100644 --- a/include/ui/cursortilerect.h +++ b/include/ui/cursortilerect.h @@ -43,7 +43,8 @@ public: bool getSingleTileMode() const { return m_singleTileMode; } void updateLocation(int x, int y); - void updateSelectionSize(int width, int height); + void updateSelectionSize(const QSize &size); + void updateSelectionSize(int width, int height) { updateSelectionSize(QSize(width, height)); } private: const QSize m_tileSize; diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h index 51f850d9..9d908cc9 100644 --- a/include/ui/layoutpixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -61,17 +61,17 @@ public: void magicFill( int initialX, int initialY, - QPoint selectionDimensions, - QList selectedMetatiles, - QList selectedCollisions, + const QSize &selectionDimensions, + const QList &selectedMetatiles, + const QList &selectedCollisions, bool fromScriptCall = false); void floodFill(int x, int y, bool fromScriptCall = false); void floodFill(int x, int y, uint16_t metatileId, bool fromScriptCall = false); void floodFill(int initialX, int initialY, - QPoint selectionDimensions, - QList selectedMetatiles, - QList selectedCollisions, + const QSize &selectionDimensions, + const QList &selectedMetatiles, + const QList &selectedCollisions, bool fromScriptCall = false); void floodFillSmartPath(int initialX, int initialY, bool fromScriptCall = false); diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index edc3bf77..8bcb6d5c 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -22,7 +22,7 @@ struct CollisionSelectionItem struct MetatileSelection { - QPoint dimensions; + QSize dimensions; bool hasCollision; QList metatileItems; QList collisionItems; @@ -43,7 +43,7 @@ public: setAcceptHoverEvents(true); } - QPoint getSelectionDimensions() const override; + QSize getSelectionDimensions() const override; void draw() override; void refresh(); diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h index 8323f7e1..05dadb86 100644 --- a/include/ui/selectablepixmapitem.h +++ b/include/ui/selectablepixmapitem.h @@ -19,7 +19,7 @@ public: selectionOffsetX(0), selectionOffsetY(0) {} - virtual QPoint getSelectionDimensions() const; + virtual QSize getSelectionDimensions() const; virtual void draw() = 0; protected: diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index 0ad22b97..01b0aed9 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -18,7 +18,7 @@ public: this->paletteChanged = false; setAcceptHoverEvents(true); } - QPoint getSelectionDimensions() const override; + QSize getSelectionDimensions() const override; void draw() override; void select(uint16_t metatileId); void highlight(uint16_t metatileId); diff --git a/src/editor.cpp b/src/editor.cpp index b4f97576..69009613 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1120,8 +1120,7 @@ void Editor::onHoveredMetatileSelectionCleared() { } void Editor::onSelectedMetatilesChanged() { - QPoint size = this->metatile_selector_item->getSelectionDimensions(); - this->cursorMapTileRect->updateSelectionSize(size.x(), size.y()); + this->cursorMapTileRect->updateSelectionSize(this->metatile_selector_item->getSelectionDimensions()); this->redrawCurrentMetatilesSelection(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac7c2385..dc6d8ee1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1787,7 +1787,7 @@ void MainWindow::redrawMetatileSelection() { void MainWindow::scrollMetatileSelectorToSelection() { // Internal selections or 1x1 external selections can be scrolled to - if (!editor->metatile_selector_item->isInternalSelection() && editor->metatile_selector_item->getSelectionDimensions() != QPoint(1, 1)) + if (!editor->metatile_selector_item->isInternalSelection() && editor->metatile_selector_item->getSelectionDimensions() != QSize(1, 1)) return; MetatileSelection selection = editor->metatile_selector_item->getMetatileSelection(); @@ -1795,8 +1795,8 @@ void MainWindow::scrollMetatileSelectorToSelection() { return; QPoint pos = editor->metatile_selector_item->getMetatileIdCoordsOnWidget(selection.metatileItems.first().metatileId); - QPoint size = editor->metatile_selector_item->getSelectionDimensions(); - pos += QPoint((size.x() - 1) * Metatile::pixelWidth(), (size.y() - 1) * Metatile::pixelHeight()) / 2; // We want to focus on the center of the whole selection + QSize size = editor->metatile_selector_item->getSelectionDimensions(); + pos += QPoint((size.width() - 1) * Metatile::pixelWidth(), (size.height() - 1) * Metatile::pixelHeight()) / 2; // We want to focus on the center of the whole selection pos *= getMetatilesZoomScale(); auto viewport = ui->scrollArea_MetatileSelector->viewport(); @@ -1970,8 +1970,8 @@ void MainWindow::copy() { } copyObject["metatile_selection"] = metatiles; copyObject["collision_selection"] = collisions; - copyObject["width"] = editor->metatile_selector_item->getSelectionDimensions().x(); - copyObject["height"] = editor->metatile_selector_item->getSelectionDimensions().y(); + copyObject["width"] = editor->metatile_selector_item->getSelectionDimensions().width(); + copyObject["height"] = editor->metatile_selector_item->getSelectionDimensions().height(); setClipboardData(copyObject); logInfo("Copied metatile selection to clipboard"); } diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index ef3c33db..46b0a18c 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -20,9 +20,9 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) Blockdata oldBorder = layout->border; - for (int i = 0; i < selection.dimensions.x() && (i + pos.x()) < width; i++) { - for (int j = 0; j < selection.dimensions.y() && (j + pos.y()) < height; j++) { - MetatileSelectionItem item = selection.metatileItems.at(j * selection.dimensions.x() + i); + for (int i = 0; i < selection.dimensions.width() && (i + pos.x()) < width; i++) { + for (int j = 0; j < selection.dimensions.height() && (j + pos.y()) < height; j++) { + MetatileSelectionItem item = selection.metatileItems.at(j * selection.dimensions.width() + i); layout->setBorderMetatileId(pos.x() + i, pos.y() + j, item.metatileId, true); } } diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp index 33425131..627c5e12 100644 --- a/src/ui/currentselectedmetatilespixmapitem.cpp +++ b/src/ui/currentselectedmetatilespixmapitem.cpp @@ -3,18 +3,18 @@ #include QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) { - int width = selection.dimensions.x() * Metatile::pixelWidth(); - int height = selection.dimensions.y() * Metatile::pixelHeight(); + int width = selection.dimensions.width() * Metatile::pixelWidth(); + int height = selection.dimensions.height() * Metatile::pixelHeight(); QImage image(width, height, QImage::Format_RGBA8888); image.fill(QColor(0, 0, 0, 0)); QPainter painter(&image); - for (int i = 0; i < selection.dimensions.x(); i++) { - for (int j = 0; j < selection.dimensions.y(); j++) { + for (int i = 0; i < selection.dimensions.width(); i++) { + for (int j = 0; j < selection.dimensions.height(); j++) { int x = i * Metatile::pixelWidth(); int y = j * Metatile::pixelHeight(); QPoint metatile_origin = QPoint(x, y); - int index = j * selection.dimensions.x() + i; + int index = j * selection.dimensions.width() + i; MetatileSelectionItem item = selection.metatileItems.at(index); if (item.enabled) { QImage metatile_image = getMetatileImage(item.metatileId, layout); diff --git a/src/ui/cursortilerect.cpp b/src/ui/cursortilerect.cpp index e62bcdc8..8b93ada1 100644 --- a/src/ui/cursortilerect.cpp +++ b/src/ui/cursortilerect.cpp @@ -39,8 +39,8 @@ void CursorTileRect::stopRightClickSelectionAnchor() { m_rightClickSelectionAnchored = false; } -void CursorTileRect::updateSelectionSize(int width, int height) { - m_selectionSize = QSize(width, height).expandedTo(QSize(1,1)); +void CursorTileRect::updateSelectionSize(const QSize &size) { + m_selectionSize = size.expandedTo(QSize(1,1)); // Enforce minimum of 1x1 cell prepareGeometryChange(); update(); } diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index 9f8a8c04..c7489598 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -25,15 +25,15 @@ void LayoutPixmapItem::paint(QGraphicsSceneMouseEvent *event) { // Paint onto the map. bool shiftPressed = event->modifiers() & Qt::ShiftModifier; - QPoint selectionDimensions = this->metatileSelector->getSelectionDimensions(); + QSize selectionDimensions = this->metatileSelector->getSelectionDimensions(); if (settings->smartPathsEnabled) { - if (!shiftPressed && selectionDimensions.x() == 3 && selectionDimensions.y() == 3) { + if (!shiftPressed && selectionDimensions == QSize(3,3)) { paintSmartPath(pos.x(), pos.y()); } else { paintNormal(pos.x(), pos.y()); } } else { - if (shiftPressed && selectionDimensions.x() == 3 && selectionDimensions.y() == 3) { + if (shiftPressed && selectionDimensions == QSize(3,3)) { paintSmartPath(pos.x(), pos.y()); } else { paintNormal(pos.x(), pos.y()); @@ -110,22 +110,22 @@ void LayoutPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { // This allows painting via dragging the mouse to tile the painted region. int xDiff = x - initialX; int yDiff = y - initialY; - if (xDiff < 0 && xDiff % selection.dimensions.x() != 0) xDiff -= selection.dimensions.x(); - if (yDiff < 0 && yDiff % selection.dimensions.y() != 0) yDiff -= selection.dimensions.y(); + if (xDiff < 0 && xDiff % selection.dimensions.width() != 0) xDiff -= selection.dimensions.width(); + if (yDiff < 0 && yDiff % selection.dimensions.height() != 0) yDiff -= selection.dimensions.height(); - x = initialX + (xDiff / selection.dimensions.x()) * selection.dimensions.x(); - y = initialY + (yDiff / selection.dimensions.y()) * selection.dimensions.y(); + x = initialX + (xDiff / selection.dimensions.width()) * selection.dimensions.width(); + y = initialY + (yDiff / selection.dimensions.height()) * selection.dimensions.height(); // for edit history Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); - for (int i = 0; i < selection.dimensions.x() && i + x < this->layout->getWidth(); i++) - for (int j = 0; j < selection.dimensions.y() && j + y < this->layout->getHeight(); j++) { + for (int i = 0; i < selection.dimensions.width() && i + x < this->layout->getWidth(); i++) + for (int j = 0; j < selection.dimensions.height() && j + y < this->layout->getHeight(); j++) { int actualX = i + x; int actualY = j + y; Block block; if (this->layout->getBlock(actualX, actualY, &block)) { - int index = j * selection.dimensions.x() + i; + int index = j * selection.dimensions.width() + i; MetatileSelectionItem item = selection.metatileItems.at(index); if (!item.enabled) continue; @@ -178,7 +178,7 @@ bool isSmartPathTile(QList metatileItems, uint16_t metati } bool isValidSmartPathSelection(MetatileSelection selection) { - if (selection.dimensions.x() != 3 || selection.dimensions.y() != 3) + if (selection.dimensions != QSize(3,3)) return false; for (int i = 0; i < selection.metatileItems.length(); i++) { @@ -377,7 +377,7 @@ void LayoutPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { int metatileId = selection.metatileItems.first().metatileId; if (selection.metatileItems.count() > 1 || (this->layout->getBlock(pos.x(), pos.y(), &block) && block.metatileId() != metatileId)) { bool smartPathsEnabled = event->modifiers() & Qt::ShiftModifier; - if ((this->settings->smartPathsEnabled || smartPathsEnabled) && selection.dimensions.x() == 3 && selection.dimensions.y() == 3) + if ((this->settings->smartPathsEnabled || smartPathsEnabled) && selection.dimensions == QSize(3,3)) this->floodFillSmartPath(pos.x(), pos.y()); else this->floodFill(pos.x(), pos.y()); @@ -398,7 +398,7 @@ void LayoutPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { } void LayoutPixmapItem::magicFill(int x, int y, uint16_t metatileId, bool fromScriptCall) { - QPoint selectionDimensions(1, 1); + QSize selectionDimensions(1, 1); QList selectedMetatiles = QList({MetatileSelectionItem{ true, metatileId }}); this->magicFill(x, y, selectionDimensions, selectedMetatiles, QList(), fromScriptCall); } @@ -411,9 +411,9 @@ void LayoutPixmapItem::magicFill(int x, int y, bool fromScriptCall) { void LayoutPixmapItem::magicFill( int initialX, int initialY, - QPoint selectionDimensions, - QList selectedMetatiles, - QList selectedCollisions, + const QSize &selectionDimensions, + const QList &selectedMetatiles, + const QList &selectedCollisions, bool fromScriptCall) { Block block; if (this->layout->getBlock(initialX, initialY, &block)) { @@ -430,11 +430,11 @@ void LayoutPixmapItem::magicFill( if (this->layout->getBlock(x, y, &block) && block.metatileId() == metatileId) { int xDiff = x - initialX; int yDiff = y - initialY; - int i = xDiff % selectionDimensions.x(); - int j = yDiff % selectionDimensions.y(); - if (i < 0) i = selectionDimensions.x() + i; - if (j < 0) j = selectionDimensions.y() + j; - int index = j * selectionDimensions.x() + i; + int i = xDiff % selectionDimensions.width(); + int j = yDiff % selectionDimensions.height(); + if (i < 0) i = selectionDimensions.width() + i; + if (j < 0) j = selectionDimensions.height() + j; + int index = j * selectionDimensions.width() + i; if (selectedMetatiles.at(index).enabled) { block.setMetatileId(selectedMetatiles.at(index).metatileId); if (setCollisions) { @@ -460,7 +460,7 @@ void LayoutPixmapItem::floodFill(int initialX, int initialY, bool fromScriptCall } void LayoutPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId, bool fromScriptCall) { - QPoint selectionDimensions(1, 1); + QSize selectionDimensions(1, 1); QList selectedMetatiles = QList({MetatileSelectionItem{true, metatileId}}); this->floodFill(initialX, initialY, selectionDimensions, selectedMetatiles, QList(), fromScriptCall); } @@ -468,9 +468,9 @@ void LayoutPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId void LayoutPixmapItem::floodFill( int initialX, int initialY, - QPoint selectionDimensions, - QList selectedMetatiles, - QList selectedCollisions, + const QSize &selectionDimensions, + const QList &selectedMetatiles, + const QList &selectedCollisions, bool fromScriptCall) { bool setCollisions = selectedCollisions.length() == selectedMetatiles.length(); Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); @@ -490,11 +490,11 @@ void LayoutPixmapItem::floodFill( visited.insert(x + y * this->layout->getWidth()); int xDiff = x - initialX; int yDiff = y - initialY; - int i = xDiff % selectionDimensions.x(); - int j = yDiff % selectionDimensions.y(); - if (i < 0) i = selectionDimensions.x() + i; - if (j < 0) j = selectionDimensions.y() + j; - int index = j * selectionDimensions.x() + i; + int i = xDiff % selectionDimensions.width(); + int j = yDiff % selectionDimensions.height(); + if (i < 0) i = selectionDimensions.width() + i; + if (j < 0) j = selectionDimensions.height() + j; + int index = j * selectionDimensions.width() + i; uint16_t metatileId = selectedMetatiles.at(index).metatileId; uint16_t old_metatileId = block.metatileId(); if (selectedMetatiles.at(index).enabled && (selectedMetatiles.count() != 1 || old_metatileId != metatileId)) { diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index bae09ee6..223c6f1a 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -80,8 +80,8 @@ void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mousePressEvent(event); QPoint selectionOrigin = this->getSelectionStart(); - QPoint dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.x(), dimensions.y()); + QSize dimensions = this->getSelectionDimensions(); + emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); this->drawSelection(); } else { const QPoint pos = this->getBoundedPos(event->pos()); @@ -95,8 +95,8 @@ void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mouseMoveEvent(event); QPoint selectionOrigin = this->getSelectionStart(); - QPoint dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.x(), dimensions.y()); + QSize dimensions = this->getSelectionDimensions(); + emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); this->drawSelection(); } else { const QPoint pos = this->getBoundedPos(event->pos()); @@ -112,8 +112,8 @@ void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mouseReleaseEvent(event); QPoint selectionOrigin = this->getSelectionStart(); - QPoint dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.x(), dimensions.y()); + QSize dimensions = this->getSelectionDimensions(); + emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); } this->draw(); diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 38cc50be..639dc5fa 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -3,7 +3,7 @@ #include "project.h" #include -QPoint MetatileSelector::getSelectionDimensions() const { +QSize MetatileSelector::getSelectionDimensions() const { if (this->prefabSelection || this->externalSelection) return selection.dimensions; return SelectablePixmapItem::getSelectionDimensions(); @@ -41,7 +41,7 @@ bool MetatileSelector::select(uint16_t metatileId) { this->externalSelection = false; this->prefabSelection = false; this->selection = MetatileSelection{ - QPoint(1, 1), + QSize(1, 1), false, QList({MetatileSelectionItem{true, metatileId}}), QList(), @@ -80,7 +80,7 @@ void MetatileSelector::setExternalSelection(int width, int height, const QListselection.metatileItems.clear(); this->selection.collisionItems.clear(); this->selection.hasCollision = true; - this->selection.dimensions = QPoint(width, height); + this->selection.dimensions = QSize(width, height); for (int i = 0; i < qMin(metatiles.length(), collisions.length()); i++) { uint16_t metatileId = metatiles.at(i); uint16_t collision = collisions.at(i).first; @@ -175,8 +175,8 @@ void MetatileSelector::updateSelectedMetatiles() { this->selection.hasCollision = false; this->selection.dimensions = this->getSelectionDimensions(); QPoint origin = this->getSelectionStart(); - for (int j = 0; j < this->selection.dimensions.y(); j++) { - for (int i = 0; i < this->selection.dimensions.x(); i++) { + for (int j = 0; j < this->selection.dimensions.height(); j++) { + for (int i = 0; i < this->selection.dimensions.width(); i++) { uint16_t metatileId = posToMetatileId(origin.x() + i, origin.y() + j); this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId}); } @@ -186,7 +186,7 @@ void MetatileSelector::updateSelectedMetatiles() { void MetatileSelector::updateExternalSelectedMetatiles() { this->selection.metatileItems.clear(); - this->selection.dimensions = QPoint(this->externalSelectionWidth, this->externalSelectionHeight); + this->selection.dimensions = QSize(this->externalSelectionWidth, this->externalSelectionHeight); for (int i = 0; i < this->externalSelectedMetatiles.count(); ++i) { uint16_t metatileId = this->externalSelectedMetatiles.at(i); if (!this->layout->metatileIsValid(metatileId)) diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index 32a6c86a..edbb7eac 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -58,7 +58,7 @@ void Prefab::loadPrefabs() { QString secondaryTileset = ParseUtil::jsonToQString(prefabObj["secondary_tileset"]); MetatileSelection selection; - selection.dimensions = QPoint(width, height); + selection.dimensions = QSize(width, height); selection.hasCollision = true; for (int j = 0; j < width * height; j++) { selection.metatileItems.append(MetatileSelectionItem{false, 0}); @@ -112,14 +112,14 @@ void Prefab::savePrefabs() { for (auto item : this->items) { OrderedJson::object prefabObj; prefabObj["name"] = item.name; - prefabObj["width"] = item.selection.dimensions.x(); - prefabObj["height"] = item.selection.dimensions.y(); + prefabObj["width"] = item.selection.dimensions.width(); + prefabObj["height"] = item.selection.dimensions.height(); prefabObj["primary_tileset"] = item.primaryTileset; prefabObj["secondary_tileset"] = item.secondaryTileset; OrderedJson::array metatiles; - for (int y = 0; y < item.selection.dimensions.y(); y++) { - for (int x = 0; x < item.selection.dimensions.x(); x++) { - int index = y * item.selection.dimensions.x() + x; + for (int y = 0; y < item.selection.dimensions.height(); y++) { + for (int x = 0; x < item.selection.dimensions.width(); x++) { + int index = y * item.selection.dimensions.width() + x; auto metatileItem = item.selection.metatileItems.at(index); if (metatileItem.enabled) { OrderedJson::object metatileObj; diff --git a/src/ui/prefabcreationdialog.cpp b/src/ui/prefabcreationdialog.cpp index 76c71f39..34b11127 100644 --- a/src/ui/prefabcreationdialog.cpp +++ b/src/ui/prefabcreationdialog.cpp @@ -23,13 +23,13 @@ PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *me QObject::connect(this->ui->graphicsView_Prefab, &ClickableGraphicsView::clicked, [=](QMouseEvent *event){ auto pos = event->pos(); - int selectionWidth = this->selection.dimensions.x() * Metatile::pixelWidth(); - int selectionHeight = this->selection.dimensions.y() * Metatile::pixelHeight(); + int selectionWidth = this->selection.dimensions.width() * Metatile::pixelWidth(); + int selectionHeight = this->selection.dimensions.height() * Metatile::pixelHeight(); if (pos.x() < 0 || pos.x() >= selectionWidth || pos.y() < 0 || pos.y() >= selectionHeight) return; int metatileX = pos.x() / Metatile::pixelWidth(); int metatileY = pos.y() / Metatile::pixelHeight(); - int index = metatileY * this->selection.dimensions.x() + metatileX; + int index = metatileY * this->selection.dimensions.width() + metatileX; bool toggledState = !this->selection.metatileItems[index].enabled; this->selection.metatileItems[index].enabled = toggledState; if (this->selection.hasCollision) { diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index bd001e59..d3aa2914 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -1,9 +1,9 @@ #include "selectablepixmapitem.h" #include -QPoint SelectablePixmapItem::getSelectionDimensions() const +QSize SelectablePixmapItem::getSelectionDimensions() const { - return QPoint(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1); + return QSize(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1); } QPoint SelectablePixmapItem::getSelectionStart() @@ -93,8 +93,8 @@ QPoint SelectablePixmapItem::getCellPos(QPointF pos) void SelectablePixmapItem::drawSelection() { QPoint origin = this->getSelectionStart(); - QPoint dimensions = this->getSelectionDimensions(); - QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.x() * this->cellWidth, dimensions.y() * this->cellHeight); + QSize dimensions = this->getSelectionDimensions(); + QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.width() * this->cellWidth, dimensions.height() * this->cellHeight); // If a selection is fully outside the bounds of the selectable area, don't draw anything. // This prevents the border of the selection rectangle potentially being visible on an otherwise invisible selection. diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index b676c9ee..c5407930 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -392,12 +392,12 @@ void TilesetEditor::drawSelectedTiles() { const int imgTileHeight = 16; this->selectedTileScene->clear(); QList tiles = this->tileSelector->getSelectedTiles(); - QPoint dimensions = this->tileSelector->getSelectionDimensions(); - QImage selectionImage(imgTileWidth * dimensions.x(), imgTileHeight * dimensions.y(), QImage::Format_RGBA8888); + QSize dimensions = this->tileSelector->getSelectionDimensions(); + QImage selectionImage(imgTileWidth * dimensions.width(), imgTileHeight * dimensions.height(), QImage::Format_RGBA8888); QPainter painter(&selectionImage); int tileIndex = 0; - for (int j = 0; j < dimensions.y(); j++) { - for (int i = 0; i < dimensions.x(); i++) { + for (int j = 0; j < dimensions.height(); j++) { + for (int i = 0; i < dimensions.width(); i++) { auto tile = tiles.at(tileIndex); QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(imgTileWidth, imgTileHeight); tile.flip(&tileImage); @@ -486,12 +486,12 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { QPoint(5, 1), }; Metatile *prevMetatile = new Metatile(*this->metatile); - QPoint dimensions = this->tileSelector->getSelectionDimensions(); + QSize dimensions = this->tileSelector->getSelectionDimensions(); QList tiles = this->tileSelector->getSelectedTiles(); int selectedTileIndex = 0; int maxTileIndex = projectConfig.getNumTilesInMetatile(); - for (int j = 0; j < dimensions.y(); j++) { - for (int i = 0; i < dimensions.x(); i++) { + for (int j = 0; j < dimensions.height(); j++) { + for (int i = 0; i < dimensions.width(); i++) { int tileIndex = ((x + i) / 2 * 4) + ((y + j) * 2) + ((x + i) % 2); if (tileIndex < maxTileIndex && tileCoords.at(tileIndex).x() >= x diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 95d5be6c..3d42cb72 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -4,9 +4,9 @@ #include #include -QPoint TilesetEditorTileSelector::getSelectionDimensions() const { +QSize TilesetEditorTileSelector::getSelectionDimensions() const { if (this->externalSelection) { - return QPoint(this->externalSelectionWidth, this->externalSelectionHeight); + return QSize(this->externalSelectionWidth, this->externalSelectionHeight); } else { return SelectablePixmapItem::getSelectionDimensions(); } @@ -90,9 +90,9 @@ void TilesetEditorTileSelector::updateSelectedTiles() { this->externalSelection = false; this->selectedTiles.clear(); QPoint origin = this->getSelectionStart(); - QPoint dimensions = this->getSelectionDimensions(); - for (int j = 0; j < dimensions.y(); j++) { - for (int i = 0; i < dimensions.x(); i++) { + QSize dimensions = this->getSelectionDimensions(); + for (int j = 0; j < dimensions.height(); j++) { + for (int i = 0; i < dimensions.width(); i++) { uint16_t metatileId = this->getTileId(origin.x() + i, origin.y() + j); this->selectedTiles.append(metatileId); } @@ -103,13 +103,13 @@ QList TilesetEditorTileSelector::getSelectedTiles() { if (this->externalSelection) { return buildSelectedTiles(this->externalSelectionWidth, this->externalSelectionHeight, this->externalSelectedTiles); } else { - QPoint dimensions = this->getSelectionDimensions(); + QSize dimensions = this->getSelectionDimensions(); QList tiles; for (int i = 0; i < this->selectedTiles.length(); i++) { uint16_t tile = this->selectedTiles.at(i); tiles.append(Tile(tile, false, false, this->paletteId)); } - return buildSelectedTiles(dimensions.x(), dimensions.y(), tiles); + return buildSelectedTiles(dimensions.width(), dimensions.height(), tiles); } } From a88730ee3fa8437507d3c093ae996f5e613fdb7e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 28 Jul 2025 12:12:53 -0400 Subject: [PATCH 2/8] Add palette painting to metatile layer painter --- include/ui/metatilelayersitem.h | 7 ++++- include/ui/tileseteditor.h | 2 +- src/ui/metatilelayersitem.cpp | 52 ++++++++++++++++++++------------ src/ui/tileseteditor.cpp | 53 +++++++++++++++++++-------------- 4 files changed, 71 insertions(+), 43 deletions(-) diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h index ceea3ff8..61eae902 100644 --- a/include/ui/metatilelayersitem.h +++ b/include/ui/metatilelayersitem.h @@ -22,9 +22,14 @@ private: Tileset *secondaryTileset; QPoint prevChangedPos; QPoint prevHoveredPos; + QPoint getBoundedPos(const QPointF &); + void requestTileChange(const QPoint &pos); + void requestPaletteChange(const QPoint &pos); + void updateSelection(); signals: - void tileChanged(int, int); + void tileChanged(const QPoint &pos); + void paletteChanged(const QPoint &pos); void selectedTilesChanged(QPoint, int, int); void hoveredTileChanged(const Tile &tile); void hoveredTileCleared(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 1266da14..4d8c5b95 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -69,7 +69,6 @@ private slots: void onHoveredTileChanged(const Tile&); void onHoveredTileChanged(uint16_t); void onHoveredTileCleared(); - void onMetatileLayerTileChanged(int, int); void onMetatileLayerSelectionChanged(QPoint, int, int); void onPaletteEditorChangedPaletteColor(); @@ -138,6 +137,7 @@ private: void setRawAttributesVisible(bool visible); void refreshTileFlips(); void refreshPaletteId(); + void paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly = false); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index 223c6f1a..9c95f6ef 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -76,34 +76,49 @@ void MetatileLayersItem::setTilesets(Tileset *primaryTileset, Tileset *secondary this->clearLastHoveredCoords(); } +// We request our current selection to be painted, +// this class doesn't handle changing the metatile data. +void MetatileLayersItem::requestTileChange(const QPoint &pos) { + this->prevChangedPos = pos; + this->clearLastHoveredCoords(); + emit this->tileChanged(pos); +} +void MetatileLayersItem::requestPaletteChange(const QPoint &pos) { + this->prevChangedPos = pos; + this->clearLastHoveredCoords(); + emit this->paletteChanged(pos); +} + +void MetatileLayersItem::updateSelection() { + QPoint selectionOrigin = this->getSelectionStart(); + QSize dimensions = this->getSelectionDimensions(); + emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); + this->drawSelection(); +} + void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mousePressEvent(event); - QPoint selectionOrigin = this->getSelectionStart(); - QSize dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); - this->drawSelection(); + updateSelection(); + } else if (event->modifiers() & Qt::ControlModifier) { + requestPaletteChange(getBoundedPos(event->pos())); } else { - const QPoint pos = this->getBoundedPos(event->pos()); - this->prevChangedPos = pos; - this->clearLastHoveredCoords(); - emit this->tileChanged(pos.x(), pos.y()); + requestTileChange(getBoundedPos(event->pos())); } } void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mouseMoveEvent(event); - QPoint selectionOrigin = this->getSelectionStart(); - QSize dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); - this->drawSelection(); + updateSelection(); } else { const QPoint pos = this->getBoundedPos(event->pos()); - if (prevChangedPos != pos) { - this->prevChangedPos = pos; - this->clearLastHoveredCoords(); - emit this->tileChanged(pos.x(), pos.y()); + if (this->prevChangedPos != pos) { + if (event->modifiers() & Qt::ControlModifier) { + requestPaletteChange(pos); + } else { + requestTileChange(pos); + } } } } @@ -111,11 +126,10 @@ void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mouseReleaseEvent(event); - QPoint selectionOrigin = this->getSelectionStart(); - QSize dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); + updateSelection(); } + // Clear selection rectangle this->draw(); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index c5407930..adc3f280 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -237,15 +237,11 @@ void TilesetEditor::initMetatileSelector() void TilesetEditor::initMetatileLayersItem() { Metatile *metatile = Tileset::getMetatile(this->getSelectedMetatileId(), this->primaryTileset, this->secondaryTileset); this->metatileLayersItem = new MetatileLayersItem(metatile, this->primaryTileset, this->secondaryTileset); - connect(this->metatileLayersItem, &MetatileLayersItem::tileChanged, - this, &TilesetEditor::onMetatileLayerTileChanged); - connect(this->metatileLayersItem, &MetatileLayersItem::selectedTilesChanged, - this, &TilesetEditor::onMetatileLayerSelectionChanged); - connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, [this](const Tile &tile) { - onHoveredTileChanged(tile); - }); - connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared, - this, &TilesetEditor::onHoveredTileCleared); + connect(this->metatileLayersItem, &MetatileLayersItem::tileChanged, [this](const QPoint &pos) { paintSelectedLayerTiles(pos); }); + connect(this->metatileLayersItem, &MetatileLayersItem::paletteChanged, [this](const QPoint &pos) { paintSelectedLayerTiles(pos, true); }); + connect(this->metatileLayersItem, &MetatileLayersItem::selectedTilesChanged, this, &TilesetEditor::onMetatileLayerSelectionChanged); + connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, [this](const Tile &tile) { onHoveredTileChanged(tile); }); + connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared); bool showGrid = porymapConfig.showTilesetEditorLayerGrid; this->ui->actionLayer_Grid->setChecked(showGrid); @@ -470,7 +466,7 @@ void TilesetEditor::onHoveredTileCleared() { this->ui->statusbar->clearMessage(); } -void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { +void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) { static const QList tileCoords = QList{ QPoint(0, 0), QPoint(1, 0), @@ -485,6 +481,7 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { QPoint(4, 1), QPoint(5, 1), }; + bool changed = false; Metatile *prevMetatile = new Metatile(*this->metatile); QSize dimensions = this->tileSelector->getSelectionDimensions(); QList tiles = this->tileSelector->getSelectedTiles(); @@ -492,23 +489,35 @@ void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { int maxTileIndex = projectConfig.getNumTilesInMetatile(); for (int j = 0; j < dimensions.height(); j++) { for (int i = 0; i < dimensions.width(); i++) { - int tileIndex = ((x + i) / 2 * 4) + ((y + j) * 2) + ((x + i) % 2); - if (tileIndex < maxTileIndex - && tileCoords.at(tileIndex).x() >= x - && tileCoords.at(tileIndex).y() >= y){ - Tile &tile = this->metatile->tiles[tileIndex]; - tile.tileId = tiles.at(selectedTileIndex).tileId; - tile.xflip = tiles.at(selectedTileIndex).xflip; - tile.yflip = tiles.at(selectedTileIndex).yflip; - tile.palette = tiles.at(selectedTileIndex).palette; - if (this->tileSelector->showUnused) { - this->tileSelector->usedTiles[tile.tileId] += 1; - this->tileSelector->usedTiles[prevMetatile->tiles[tileIndex].tileId] -= 1; + int tileIndex = ((pos.x() + i) / 2 * 4) + ((pos.y() + j) * 2) + ((pos.x() + i) % 2); + QPoint tilePos = tileCoords.at(tileIndex); + if (tileIndex < maxTileIndex && tilePos.x() >= pos.x() && tilePos.y() >= pos.y()){ + Tile &destTile = this->metatile->tiles[tileIndex]; + const Tile srcTile = tiles.at(selectedTileIndex); + if (paletteOnly) { + if (srcTile.palette == destTile.palette) + continue; // Ignore no-ops for edit history + destTile.palette = srcTile.palette; + } else { + if (srcTile == destTile) + continue; // Ignore no-ops for edit history + + // Update tile usage count + if (this->tileSelector->showUnused && destTile.tileId != srcTile.tileId) { + this->tileSelector->usedTiles[srcTile.tileId] += 1; + this->tileSelector->usedTiles[destTile.tileId] -= 1; + } + destTile = srcTile; } + changed = true; } selectedTileIndex++; } } + if (!changed) { + delete prevMetatile; + return; + } this->metatileSelector->drawSelectedMetatile(); this->metatileLayersItem->draw(); From f6f07ca5fc85556b5b404a2d12f973d1d05edb8d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Jul 2025 03:10:46 -0400 Subject: [PATCH 3/8] Allow vertical layout for layer view --- forms/tileseteditor.ui | 72 +++++++++----- include/config.h | 1 + include/ui/metatilelayersitem.h | 20 +++- include/ui/selectablepixmapitem.h | 19 +++- include/ui/tileseteditor.h | 3 +- include/ui/tileseteditortileselector.h | 7 +- src/config.cpp | 5 + src/editor.cpp | 4 +- src/project.cpp | 17 ++++ src/ui/metatilelayersitem.cpp | 131 +++++++++++++++---------- src/ui/selectablepixmapitem.cpp | 84 +++++++++------- src/ui/tileseteditor.cpp | 116 +++++++++++++--------- src/ui/tileseteditortileselector.cpp | 15 ++- 13 files changed, 317 insertions(+), 177 deletions(-) diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 895fae0f..b7f78f66 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -158,7 +158,7 @@ 0 - 166 + 190 @@ -194,7 +194,7 @@ false - + @@ -451,12 +451,6 @@ - - - 0 - 0 - - 98 @@ -497,16 +491,22 @@ + + + 0 + 0 + + - 18 - 18 + 98 + 98 98 - 34 + 98 @@ -520,22 +520,22 @@ + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + - - - - Qt::Orientation::Vertical - - - - 20 - 10 - - - - @@ -686,6 +686,14 @@ View + + + Layer Arrangement + + + + + @@ -863,6 +871,22 @@ Secondary... + + + true + + + Horizontal + + + + + true + + + Vertical + + diff --git a/include/config.h b/include/config.h index 25482457..1f2a1a14 100644 --- a/include/config.h +++ b/include/config.h @@ -108,6 +108,7 @@ public: int metatilesZoom; int tilesetEditorMetatilesZoom; int tilesetEditorTilesZoom; + Qt::Orientation tilesetEditorLayerOrientation; bool showPlayerView; bool showCursorTile; bool showBorder; diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h index 61eae902..7d1c74ce 100644 --- a/include/ui/metatilelayersitem.h +++ b/include/ui/metatilelayersitem.h @@ -9,28 +9,40 @@ class MetatileLayersItem: public SelectablePixmapItem { Q_OBJECT public: - MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset); + MetatileLayersItem(Metatile *metatile, + Tileset *primaryTileset, + Tileset *secondaryTileset, + Qt::Orientation orientation = Qt::Horizontal); void draw(); void setTilesets(Tileset*, Tileset*); void setMetatile(Metatile*); void clearLastModifiedCoords(); void clearLastHoveredCoords(); + + QPoint tileIndexToPos(int index) const { return this->tilePositions.value(index); } + int posToTileIndex(const QPoint &pos) const { return this->tilePositions.indexOf(pos); } + int posToTileIndex(int x, int y) const { return posToTileIndex(QPoint(x, y)); } + + void setOrientation(Qt::Orientation orientation); + bool showGrid; private: Metatile* metatile; Tileset *primaryTileset; Tileset *secondaryTileset; + Qt::Orientation orientation; QPoint prevChangedPos; QPoint prevHoveredPos; + QList tilePositions; + QPoint getBoundedPos(const QPointF &); - void requestTileChange(const QPoint &pos); - void requestPaletteChange(const QPoint &pos); void updateSelection(); + void hover(const QPoint &pos); signals: void tileChanged(const QPoint &pos); void paletteChanged(const QPoint &pos); - void selectedTilesChanged(QPoint, int, int); + void selectedTilesChanged(const QPoint &pos, const QSize &dimensions); void hoveredTileChanged(const Tile &tile); void hoveredTileCleared(); protected: diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h index 05dadb86..433b2bca 100644 --- a/include/ui/selectablepixmapitem.h +++ b/include/ui/selectablepixmapitem.h @@ -19,9 +19,13 @@ public: selectionOffsetX(0), selectionOffsetY(0) {} - virtual QSize getSelectionDimensions() const; + virtual QSize getSelectionDimensions() const { return QSize(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1); } virtual void draw() = 0; + virtual void setMaxSelectionSize(const QSize &size) { setMaxSelectionSize(size.width(), size.height()); } + virtual void setMaxSelectionSize(int width, int height); + QSize maxSelectionSize() { return QSize(this->maxSelectionWidth, this->maxSelectionHeight); } + protected: int cellWidth; int cellHeight; @@ -33,17 +37,22 @@ protected: int selectionOffsetY; QPoint getSelectionStart(); - void select(int x, int y, int width = 0, int height = 0); - void select(const QPoint &pos, const QSize &size = QSize(0,0)) { select(pos.x(), pos.y(), size.width(), size.height()); } - void updateSelection(int, int); + 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); virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; virtual void drawSelection(); + virtual int cellsWide() const { return this->cellWidth ? (pixmap().width() / this->cellWidth) : 0; } + virtual int cellsTall() const { return this->cellHeight ? (pixmap().height() / this->cellHeight) : 0; } signals: - void selectionChanged(int, int, int, int); + void selectionChanged(const QPoint&, const QSize&); + +private: + QPoint prevCellPos = QPoint(-1,-1); }; #endif // SELECTABLEPIXMAPITEM_H diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 4d8c5b95..43467713 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -69,7 +69,7 @@ private slots: void onHoveredTileChanged(const Tile&); void onHoveredTileChanged(uint16_t); void onHoveredTileCleared(); - void onMetatileLayerSelectionChanged(QPoint, int, int); + void onMetatileLayerSelectionChanged(const QPoint&, const QSize&); void onPaletteEditorChangedPaletteColor(); void on_actionChange_Metatiles_Count_triggered(); @@ -138,6 +138,7 @@ private: void refreshTileFlips(); void refreshPaletteId(); void paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly = false); + void setMetatileLayerOrientation(Qt::Orientation orientation); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index 01b0aed9..915a9227 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -7,8 +7,8 @@ class TilesetEditorTileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, int numLayers) - : SelectablePixmapItem(16, 16, numLayers * Metatile::tileWidth(), Metatile::tileHeight()) { + TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset) + : SelectablePixmapItem(16, 16, Metatile::tileWidth(), Metatile::tileWidth()) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; this->numTilesWide = 16; @@ -19,6 +19,7 @@ public: setAcceptHoverEvents(true); } QSize getSelectionDimensions() const override; + void setMaxSelectionSize(int width, int height) override; void draw() override; void select(uint16_t metatileId); void highlight(uint16_t metatileId); @@ -31,6 +32,7 @@ public: QImage buildPrimaryTilesIndexedImage(); QImage buildSecondaryTilesIndexedImage(); + QVector usedTiles; bool showUnused = false; bool showDivider = false; @@ -49,6 +51,7 @@ private: int externalSelectionHeight; QList externalSelectedTiles; QList externalSelectedPos; + QPoint prevCellPos = QPoint(-1,-1); Tileset *primaryTileset; Tileset *secondaryTileset; diff --git a/src/config.cpp b/src/config.cpp index b53f171b..4bff6a78 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -338,6 +338,7 @@ void PorymapConfig::reset() { this->metatilesZoom = 30; this->tilesetEditorMetatilesZoom = 30; this->tilesetEditorTilesZoom = 30; + this->tilesetEditorLayerOrientation = Qt::Horizontal; this->showPlayerView = false; this->showCursorTile = true; this->showBorder = true; @@ -454,6 +455,9 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetEditorMetatilesZoom = getConfigInteger(key, value, 10, 100, 30); } else if (key == "tileset_editor_tiles_zoom") { this->tilesetEditorTilesZoom = getConfigInteger(key, value, 10, 100, 30); + } else if (key == "tileset_editor_layer_orientation") { + // Being explicit here to avoid casting out-of-range values. + this->tilesetEditorLayerOrientation = (getConfigInteger(key, value) == static_cast(Qt::Horizontal)) ? Qt::Horizontal : Qt::Vertical; } else if (key == "show_player_view") { this->showPlayerView = getConfigBool(key, value); } else if (key == "show_cursor_tile") { @@ -604,6 +608,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("metatiles_zoom", QString::number(this->metatilesZoom)); map.insert("tileset_editor_metatiles_zoom", QString::number(this->tilesetEditorMetatilesZoom)); map.insert("tileset_editor_tiles_zoom", QString::number(this->tilesetEditorTilesZoom)); + map.insert("tileset_editor_layer_orientation", QString::number(this->tilesetEditorLayerOrientation)); map.insert("show_player_view", this->showPlayerView ? "1" : "0"); map.insert("show_cursor_tile", this->showCursorTile ? "1" : "0"); map.insert("show_border", this->showBorder ? "1" : "0"); diff --git a/src/editor.cpp b/src/editor.cpp index 69009613..9c6a7d0a 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1773,8 +1773,8 @@ void Editor::displayMovementPermissionSelector() { this, &Editor::onHoveredMovementPermissionChanged); connect(movement_permissions_selector_item, &MovementPermissionsSelector::hoveredMovementPermissionCleared, this, &Editor::onHoveredMovementPermissionCleared); - connect(movement_permissions_selector_item, &SelectablePixmapItem::selectionChanged, [this](int x, int y, int, int) { - this->setCollisionTabSpinBoxes(x, y); + connect(movement_permissions_selector_item, &SelectablePixmapItem::selectionChanged, [this](const QPoint &pos, const QSize&) { + this->setCollisionTabSpinBoxes(pos.x(), pos.y()); }); movement_permissions_selector_item->select(projectConfig.defaultCollision, projectConfig.defaultElevation); } diff --git a/src/project.cpp b/src/project.cpp index 9c5fd9af..07ac72ea 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1187,6 +1187,18 @@ bool Project::loadLayoutTilesets(Layout *layout) { logError(QString("Failed to load %1: missing secondary tileset label.").arg(layout->name)); return false; } + if (!this->primaryTilesetLabels.contains(layout->tileset_primary_label)) { + logError(QString("Failed to load %1: unknown primary tileset label '%2'.") + .arg(layout->name) + .arg(layout->tileset_primary_label)); + return false; + } + if (!this->secondaryTilesetLabels.contains(layout->tileset_secondary_label)) { + logError(QString("Failed to load %1: unknown secondary tileset label '%2'.") + .arg(layout->name) + .arg(layout->tileset_secondary_label)); + return false; + } layout->tileset_primary = getTileset(layout->tileset_primary_label); layout->tileset_secondary = getTileset(layout->tileset_secondary_label); @@ -1194,6 +1206,11 @@ bool Project::loadLayoutTilesets(Layout *layout) { } Tileset* Project::getTileset(const QString &label, bool forceLoad) { + if (!this->tilesetLabelsOrdered.contains(label)) { + logError(QString("Unknown tileset name '%1'.").arg(label)); + return nullptr; + } + Tileset *tileset = nullptr; auto it = this->tilesetCache.constFind(label); diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index 9c95f6ef..34afce00 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -3,8 +3,8 @@ #include "imageproviders.h" #include -MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset) - : SelectablePixmapItem(16, 16, Metatile::tileWidth() * projectConfig.getNumLayersInMetatile(), Metatile::tileHeight()), +MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset, Qt::Orientation orientation) + : SelectablePixmapItem(16, 16, Metatile::tileWidth(), Metatile::tileHeight()), metatile(metatile), primaryTileset(primaryTileset), secondaryTileset(secondaryTileset) @@ -12,28 +12,51 @@ MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTiles clearLastModifiedCoords(); clearLastHoveredCoords(); setAcceptHoverEvents(true); + setOrientation(orientation); } -static const QList tilePositions = { - QPoint(0, 0), - QPoint(1, 0), - QPoint(0, 1), - QPoint(1, 1), - QPoint(2, 0), - QPoint(3, 0), - QPoint(2, 1), - QPoint(3, 1), - QPoint(4, 0), - QPoint(5, 0), - QPoint(4, 1), - QPoint(5, 1), -}; +void MetatileLayersItem::setOrientation(Qt::Orientation orientation) { + this->orientation = orientation; + int maxWidth = Metatile::tileWidth(); + int maxHeight = Metatile::tileHeight(); + + // Generate a table of tile positions that allows us to map between + // the index of a tile in the metatile and its position in this layer view. + this->tilePositions.clear(); + if (this->orientation == Qt::Horizontal) { + // Tiles are laid out horizontally, with the bottom layer on the left: + // 0 1 4 5 8 9 + // 2 3 6 7 10 11 + for (int layer = 0; layer < projectConfig.getNumLayersInMetatile(); layer++) + for (int y = 0; y < Metatile::tileHeight(); y++) + for (int x = 0; x < Metatile::tileWidth(); x++) { + this->tilePositions.append(QPoint(x + layer * Metatile::tileWidth(), y)); + } + maxWidth *= projectConfig.getNumLayersInMetatile(); + } else if (this->orientation == Qt::Vertical) { + // Tiles are laid out vertically, with the bottom layer on the bottom: + // 8 9 + // 10 11 + // 4 5 + // 6 7 + // 0 1 + // 2 3 + for (int layer = projectConfig.getNumLayersInMetatile() - 1; layer >= 0; layer--) + for (int y = 0; y < Metatile::tileHeight(); y++) + for (int x = 0; x < Metatile::tileWidth(); x++) { + this->tilePositions.append(QPoint(x, y + layer * Metatile::tileHeight())); + } + maxHeight *= projectConfig.getNumLayersInMetatile(); + } + setMaxSelectionSize(maxWidth, maxHeight); + update(); + if (!this->pixmap().isNull()) { + draw(); + } +} void MetatileLayersItem::draw() { - const int numLayers = projectConfig.getNumLayersInMetatile(); - const int layerWidth = this->cellWidth * Metatile::tileWidth(); - const int layerHeight = this->cellHeight * Metatile::tileHeight(); - QPixmap pixmap(numLayers * layerWidth, layerHeight); + QPixmap pixmap(this->cellWidth * this->maxSelectionWidth, this->cellHeight * this->maxSelectionHeight); QPainter painter(&pixmap); // Draw tile images @@ -47,15 +70,22 @@ void MetatileLayersItem::draw() { true ).scaled(this->cellWidth, this->cellHeight); tile.flip(&tileImage); - QPoint pos = tilePositions.at(i); + QPoint pos = tileIndexToPos(i); painter.drawImage(pos.x() * this->cellWidth, pos.y() * this->cellHeight, tileImage); } if (this->showGrid) { // Draw grid painter.setPen(Qt::white); - for (int i = 1; i < numLayers; i++) { - int x = i * layerWidth; - painter.drawLine(x, 0, x, layerHeight); + const int layerWidth = this->cellWidth * Metatile::tileWidth(); + const int layerHeight = this->cellHeight * Metatile::tileHeight(); + for (int i = 1; i < projectConfig.getNumLayersInMetatile(); i++) { + if (this->orientation == Qt::Vertical) { + int y = i * layerHeight; + painter.drawLine(0, y, layerWidth, y); + } else if (this->orientation == Qt::Horizontal) { + int x = i * layerWidth; + painter.drawLine(x, 0, x, layerHeight); + } } } @@ -76,51 +106,41 @@ void MetatileLayersItem::setTilesets(Tileset *primaryTileset, Tileset *secondary this->clearLastHoveredCoords(); } -// We request our current selection to be painted, -// this class doesn't handle changing the metatile data. -void MetatileLayersItem::requestTileChange(const QPoint &pos) { - this->prevChangedPos = pos; - this->clearLastHoveredCoords(); - emit this->tileChanged(pos); -} -void MetatileLayersItem::requestPaletteChange(const QPoint &pos) { - this->prevChangedPos = pos; - this->clearLastHoveredCoords(); - emit this->paletteChanged(pos); -} - void MetatileLayersItem::updateSelection() { - QPoint selectionOrigin = this->getSelectionStart(); - QSize dimensions = this->getSelectionDimensions(); - emit this->selectedTilesChanged(selectionOrigin, dimensions.width(), dimensions.height()); - this->drawSelection(); + drawSelection(); + emit selectedTilesChanged(getSelectionStart(), getSelectionDimensions()); } void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + const QPoint pos = this->getBoundedPos(event->pos()); + hover(pos); + if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mousePressEvent(event); updateSelection(); } else if (event->modifiers() & Qt::ControlModifier) { - requestPaletteChange(getBoundedPos(event->pos())); + emit paletteChanged(pos); } else { - requestTileChange(getBoundedPos(event->pos())); + emit tileChanged(pos); } + this->prevChangedPos = pos; } void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + const QPoint pos = this->getBoundedPos(event->pos()); + if (this->prevChangedPos == pos) + return; + hover(pos); + if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mouseMoveEvent(event); updateSelection(); + } else if (event->modifiers() & Qt::ControlModifier) { + emit paletteChanged(pos); } else { - const QPoint pos = this->getBoundedPos(event->pos()); - if (this->prevChangedPos != pos) { - if (event->modifiers() & Qt::ControlModifier) { - requestPaletteChange(pos); - } else { - requestTileChange(pos); - } - } + emit tileChanged(pos); } + this->prevChangedPos = pos; } void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { @@ -130,16 +150,19 @@ void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { } // Clear selection rectangle - this->draw(); + draw(); } void MetatileLayersItem::hoverMoveEvent(QGraphicsSceneHoverEvent * event) { - const QPoint pos = this->getBoundedPos(event->pos()); + hover(getBoundedPos(event->pos())); +} + +void MetatileLayersItem::hover(const QPoint &pos) { if (pos == this->prevHoveredPos) return; this->prevHoveredPos = pos; - int tileIndex = tilePositions.indexOf(pos); + int tileIndex = posToTileIndex(pos); if (tileIndex < 0 || tileIndex >= this->metatile->tiles.length()) return; diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index d3aa2914..7a38913e 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -1,11 +1,6 @@ #include "selectablepixmapitem.h" #include -QSize SelectablePixmapItem::getSelectionDimensions() const -{ - return QSize(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1); -} - QPoint SelectablePixmapItem::getSelectionStart() { int x = this->selectionInitialX; @@ -15,47 +10,62 @@ QPoint SelectablePixmapItem::getSelectionStart() return QPoint(x, y); } -void SelectablePixmapItem::select(int x, int y, int width, int height) +void SelectablePixmapItem::select(const QPoint &pos, const QSize &size) { - this->selectionInitialX = x; - this->selectionInitialY = y; - this->selectionOffsetX = qBound(0, width, this->maxSelectionWidth); - this->selectionOffsetY = qBound(0, height, this->maxSelectionHeight); - this->draw(); - emit this->selectionChanged(x, y, width, height); + 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); + draw(); + emit selectionChanged(pos, getSelectionDimensions()); } void SelectablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = this->getCellPos(event->pos()); + QPoint pos = getCellPos(event->pos()); this->selectionInitialX = pos.x(); this->selectionInitialY = pos.y(); this->selectionOffsetX = 0; this->selectionOffsetY = 0; - this->updateSelection(pos.x(), pos.y()); + this->prevCellPos = pos; + updateSelection(pos); } void SelectablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = this->getCellPos(event->pos()); - this->updateSelection(pos.x(), pos.y()); + QPoint pos = getCellPos(event->pos()); + if (pos == this->prevCellPos) + return; + this->prevCellPos = pos; + updateSelection(pos); } void SelectablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = this->getCellPos(event->pos()); - this->updateSelection(pos.x(), pos.y()); + updateSelection(getCellPos(event->pos())); } -void SelectablePixmapItem::updateSelection(int x, int y) -{ +void SelectablePixmapItem::setMaxSelectionSize(int width, int height) { + this->maxSelectionWidth = qMax(width, 1); + this->maxSelectionHeight = qMax(height, 1); + + // Update the selection if we shrank below the current selection size. + QSize size = getSelectionDimensions(); + if (size.width() > this->maxSelectionWidth || size.height() > this->maxSelectionHeight) { + 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; + draw(); + emit selectionChanged(getSelectionStart(), getSelectionDimensions()); + } +} + +void SelectablePixmapItem::updateSelection(const QPoint &pos) { // Snap to a valid position inside the selection area. - int width = pixmap().width() / this->cellWidth; - int height = pixmap().height() / this->cellHeight; - if (x < 0) x = 0; - if (x >= width) x = width - 1; - if (y < 0) y = 0; - if (y >= height) y = height - 1; + int x = qBound(0, pos.x(), cellsWide() - 1); + int y = qBound(0, pos.y(), cellsTall() - 1); this->selectionOffsetX = x - this->selectionInitialX; this->selectionOffsetY = y - this->selectionInitialY; @@ -76,22 +86,20 @@ void SelectablePixmapItem::updateSelection(int x, int y) this->selectionOffsetY = -this->maxSelectionHeight + 1; } - this->draw(); - emit this->selectionChanged(x, y, width, height); + draw(); + emit selectionChanged(QPoint(x, y), getSelectionDimensions()); } -QPoint SelectablePixmapItem::getCellPos(QPointF pos) -{ - if (pos.x() < 0) pos.setX(0); - if (pos.y() < 0) pos.setY(0); - if (pos.x() >= this->pixmap().width()) pos.setX(this->pixmap().width() - 1); - if (pos.y() >= this->pixmap().height()) pos.setY(this->pixmap().height() - 1); - return QPoint(static_cast(pos.x()) / this->cellWidth, - static_cast(pos.y()) / this->cellHeight); +QPoint SelectablePixmapItem::getCellPos(QPointF pos) { + 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); + return QPoint(x / this->cellWidth, y / this->cellHeight); } -void SelectablePixmapItem::drawSelection() -{ +void SelectablePixmapItem::drawSelection() { QPoint origin = this->getSelectionStart(); QSize dimensions = this->getSelectionDimensions(); QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.width() * this->cellWidth, dimensions.height() * this->cellHeight); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index adc3f280..5b4c34b1 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -62,12 +62,16 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) connect(ui->spinBox_paletteSelector, QOverload::of(&QSpinBox::valueChanged), this, &TilesetEditor::refreshPaletteId); + connect(ui->actionLayer_Arrangement_Horizontal, &QAction::triggered, [this] { setMetatileLayerOrientation(Qt::Horizontal); }); + connect(ui->actionLayer_Arrangement_Vertical, &QAction::triggered, [this] { setMetatileLayerOrientation(Qt::Vertical); }); + initAttributesUi(); initMetatileSelector(); initMetatileLayersItem(); initTileSelector(); initSelectedTileItem(); initShortcuts(); + setMetatileLayerOrientation(porymapConfig.tilesetEditorLayerOrientation); this->metatileSelector->select(0); restoreWindowState(); } @@ -234,6 +238,53 @@ void TilesetEditor::initMetatileSelector() this->ui->horizontalSlider_MetatilesZoom->setValue(porymapConfig.tilesetEditorMetatilesZoom); } +void TilesetEditor::setMetatileLayerOrientation(Qt::Orientation orientation) { + // Sync settings + bool horizontal = (orientation == Qt::Horizontal); + porymapConfig.tilesetEditorLayerOrientation = orientation; + const QSignalBlocker b_Horizontal(ui->actionLayer_Arrangement_Horizontal); + const QSignalBlocker b_Vertical(ui->actionLayer_Arrangement_Vertical); + ui->actionLayer_Arrangement_Horizontal->setChecked(horizontal); + ui->actionLayer_Arrangement_Vertical->setChecked(!horizontal); + + this->metatileLayersItem->setOrientation(orientation); + + int numTilesWide = Metatile::tileWidth(); + int numTilesTall = Metatile::tileHeight(); + int numLayers = projectConfig.getNumLayersInMetatile(); + if (horizontal) { + numTilesWide *= numLayers; + } else { + numTilesTall *= numLayers; + } + this->tileSelector->setMaxSelectionSize(numTilesWide, numTilesTall); + + const int scale = 2; + int w = Tile::pixelWidth() * numTilesWide * scale + 2; + int h = Tile::pixelHeight() * numTilesTall * scale + 2; + ui->graphicsView_selectedTile->setFixedSize(w, h); + ui->graphicsView_metatileLayers->setFixedSize(w, h); + + // If the layers are laid out vertically then the orientation is obvious, no need to label them. + ui->label_BottomTop->setVisible(horizontal); + + // Let the graphics view take over the label's vertical space (or conversely, give the space back). + // (This is a bit of a process, apparently there's no quick way to set a widget's row / row span once they're added to the layout + int row, col, rowSpan, colSpan; + int index = ui->gridLayout_MetatileProperties->indexOf(ui->label_BottomTop); + ui->gridLayout_MetatileProperties->getItemPosition(index, &row, &col, &rowSpan, &colSpan); + + // TODO: Rearrange the rest of the metatile properties panel. The vertical triple-layer metatiles layout esp. looks terrible. + ui->gridLayout_MetatileProperties->removeWidget(ui->graphicsView_metatileLayers); + if (horizontal) { + // Give space from graphics view back to label + ui->gridLayout_MetatileProperties->addWidget(ui->graphicsView_metatileLayers, row + 1, col, rowSpan, colSpan); + } else { + // Take space from label and give it to graphics view + ui->gridLayout_MetatileProperties->addWidget(ui->graphicsView_metatileLayers, row, col, rowSpan + 1, colSpan); + } +} + void TilesetEditor::initMetatileLayersItem() { Metatile *metatile = Tileset::getMetatile(this->getSelectedMetatileId(), this->primaryTileset, this->secondaryTileset); this->metatileLayersItem = new MetatileLayersItem(metatile, this->primaryTileset, this->secondaryTileset); @@ -252,9 +303,8 @@ void TilesetEditor::initMetatileLayersItem() { this->ui->graphicsView_metatileLayers->setScene(this->metatileLayersScene); } -void TilesetEditor::initTileSelector() -{ - this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset, projectConfig.getNumLayersInMetatile()); +void TilesetEditor::initTileSelector() { + this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset); connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) { onHoveredTileChanged(tileId); }); @@ -277,7 +327,6 @@ void TilesetEditor::initSelectedTileItem() { this->selectedTileScene = new QGraphicsScene; this->drawSelectedTiles(); this->ui->graphicsView_selectedTile->setScene(this->selectedTileScene); - this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width() + 2, this->selectedTilePixmapItem->pixmap().height() + 2); } void TilesetEditor::initShortcuts() { @@ -392,13 +441,12 @@ void TilesetEditor::drawSelectedTiles() { QImage selectionImage(imgTileWidth * dimensions.width(), imgTileHeight * dimensions.height(), QImage::Format_RGBA8888); QPainter painter(&selectionImage); int tileIndex = 0; - for (int j = 0; j < dimensions.height(); j++) { - for (int i = 0; i < dimensions.width(); i++) { - auto tile = tiles.at(tileIndex); + for (int y = 0; y < dimensions.height(); y++) { + for (int x = 0; x < dimensions.width(); x++) { + auto tile = tiles.at(tileIndex++); QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(imgTileWidth, imgTileHeight); tile.flip(&tileImage); - tileIndex++; - painter.drawImage(i * imgTileWidth, j * imgTileHeight, tileImage); + painter.drawImage(x * imgTileWidth, y * imgTileHeight, tileImage); } } @@ -407,7 +455,6 @@ void TilesetEditor::drawSelectedTiles() { QSize size(this->selectedTilePixmapItem->pixmap().width(), this->selectedTilePixmapItem->pixmap().height()); this->ui->graphicsView_selectedTile->setSceneRect(0, 0, size.width(), size.height()); - this->ui->graphicsView_selectedTile->setFixedSize(size.width() + 2, size.height() + 2); } void TilesetEditor::onHoveredMetatileChanged(uint16_t metatileId) { @@ -436,7 +483,6 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { this->metatileLayersItem->setMetatile(metatile); this->metatileLayersItem->draw(); - this->ui->graphicsView_metatileLayers->setFixedSize(this->metatileLayersItem->pixmap().width() + 2, this->metatileLayersItem->pixmap().height() + 2); MetatileLabelPair labels = Tileset::getMetatileLabelPair(metatileId, this->primaryTileset, this->secondaryTileset); this->ui->lineEdit_metatileLabel->setText(labels.owned); @@ -467,33 +513,18 @@ void TilesetEditor::onHoveredTileCleared() { } void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) { - static const QList tileCoords = QList{ - QPoint(0, 0), - QPoint(1, 0), - QPoint(0, 1), - QPoint(1, 1), - QPoint(2, 0), - QPoint(3, 0), - QPoint(2, 1), - QPoint(3, 1), - QPoint(4, 0), - QPoint(5, 0), - QPoint(4, 1), - QPoint(5, 1), - }; bool changed = false; Metatile *prevMetatile = new Metatile(*this->metatile); QSize dimensions = this->tileSelector->getSelectionDimensions(); QList tiles = this->tileSelector->getSelectedTiles(); - int selectedTileIndex = 0; + int srcTileIndex = 0; int maxTileIndex = projectConfig.getNumTilesInMetatile(); - for (int j = 0; j < dimensions.height(); j++) { - for (int i = 0; i < dimensions.width(); i++) { - int tileIndex = ((pos.x() + i) / 2 * 4) + ((pos.y() + j) * 2) + ((pos.x() + i) % 2); - QPoint tilePos = tileCoords.at(tileIndex); - if (tileIndex < maxTileIndex && tilePos.x() >= pos.x() && tilePos.y() >= pos.y()){ - Tile &destTile = this->metatile->tiles[tileIndex]; - const Tile srcTile = tiles.at(selectedTileIndex); + for (int y = 0; y < dimensions.height(); y++) { + for (int x = 0; x < dimensions.width(); x++) { + int destTileIndex = this->metatileLayersItem->posToTileIndex(pos.x() + x, pos.y() + y); + if (destTileIndex < maxTileIndex) { + Tile &destTile = this->metatile->tiles[destTileIndex]; + const Tile srcTile = tiles.at(srcTileIndex++); if (paletteOnly) { if (srcTile.palette == destTile.palette) continue; // Ignore no-ops for edit history @@ -511,7 +542,6 @@ void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) } changed = true; } - selectedTileIndex++; } } if (!changed) { @@ -525,26 +555,24 @@ void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) this->commitMetatileChange(prevMetatile); } -void TilesetEditor::onMetatileLayerSelectionChanged(QPoint selectionOrigin, int width, int height) { +void TilesetEditor::onMetatileLayerSelectionChanged(const QPoint &selectionOrigin, const QSize &size) { QList tiles; QList tileIdxs; - int x = selectionOrigin.x(); - int y = selectionOrigin.y(); int maxTileIndex = projectConfig.getNumTilesInMetatile(); - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - int tileIndex = ((x + i) / 2 * 4) + ((y + j) * 2) + ((x + i) % 2); + for (int j = 0; j < size.height(); j++) { + 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.at(tileIndex)); + tiles.append(this->metatile->tiles.value(tileIndex)); tileIdxs.append(tileIndex); } } } - this->tileSelector->setExternalSelection(width, height, tiles, tileIdxs); - if (width == 1 && height == 1) { + this->tileSelector->setExternalSelection(size.width(), size.height(), tiles, tileIdxs); + if (size == QSize(1,1)) { setPaletteId(tiles[0].palette); - this->tileSelector->highlight(static_cast(tiles[0].tileId)); + this->tileSelector->highlight(tiles[0].tileId); this->redrawTileSelector(); } this->metatileLayersItem->clearLastModifiedCoords(); diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 3d42cb72..ec3caa83 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -12,6 +12,11 @@ QSize TilesetEditorTileSelector::getSelectionDimensions() const { } } +void TilesetEditorTileSelector::setMaxSelectionSize(int width, int height) { + SelectablePixmapItem::setMaxSelectionSize(width, height); + updateSelectedTiles(); +} + void TilesetEditorTileSelector::updateBasePixmap() { if (!this->primaryTileset || !this->secondaryTileset || this->numTilesWide == 0) { this->basePixmap = QPixmap(); @@ -134,7 +139,7 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, // If we've completed a layer row, or its the last tile of an incompletely // selected layer, then append the layer row to the full row // If not an external selection, treat the whole row as 1 "layer" - if (i == width - 1 || (this->externalSelection && (this->externalSelectedPos.at(index) % 4) & 1)) { + if (i == width - 1 || (this->externalSelection && (this->externalSelectedPos.at(index) % Metatile::tilesPerLayer()) & 1)) { row.append(layerRow); layerRow.clear(); } @@ -170,16 +175,20 @@ uint16_t TilesetEditorTileSelector::getTileId(int x, int y) { } void TilesetEditorTileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { + this->prevCellPos = getCellPos(event->pos()); SelectablePixmapItem::mousePressEvent(event); this->updateSelectedTiles(); emit selectedTilesChanged(); } void TilesetEditorTileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + QPoint pos = getCellPos(event->pos()); + if (this->prevCellPos == pos) + return; + this->prevCellPos = pos; + SelectablePixmapItem::mouseMoveEvent(event); this->updateSelectedTiles(); - - QPoint pos = this->getCellPos(event->pos()); uint16_t tile = this->getTileId(pos.x(), pos.y()); emit hoveredTileChanged(tile); emit selectedTilesChanged(); From d0337a7ae383e3597c4b64f2b1f79896ea15bd37 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Jul 2025 16:45:31 -0400 Subject: [PATCH 4/8] Add metatile swap --- forms/paletteeditor.ui | 6 + forms/tileseteditor.ui | 21 ++ include/core/history.h | 29 +- include/core/maplayout.h | 5 +- include/project.h | 1 + include/ui/paletteeditor.h | 3 +- include/ui/selectablepixmapitem.h | 10 +- include/ui/tileseteditor.h | 27 +- include/ui/tileseteditormetatileselector.h | 13 +- resources/icons/swap_cursor.ico | Bin 0 -> 4414 bytes resources/images.qrc | 1 + src/core/maplayout.cpp | 16 +- src/project.cpp | 16 ++ src/scriptapi/apimap.cpp | 10 +- src/ui/metatilelayersitem.cpp | 2 +- src/ui/paletteeditor.cpp | 36 ++- src/ui/selectablepixmapitem.cpp | 66 +++-- src/ui/tileseteditor.cpp | 310 ++++++++++++++------- src/ui/tileseteditormetatileselector.cpp | 109 ++++++-- 19 files changed, 504 insertions(+), 177 deletions(-) create mode 100644 resources/icons/swap_cursor.ico 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 0000000000000000000000000000000000000000..d352b1f5ea57362f6e560b28adcb7eb789177857 GIT binary patch literal 4414 zcmeHI%}*0S6n|9{PZ(Q~Lye}93kMB{#)Bsf7F&vVQLC}|LA)3*9*l_>4r*$O4;sb91X>Xg3AK=t^y6UkXrh0?&o{HX1e%aQ*;Nxdn>X|J&CKuj=JzJM zL>7J=4iY`pS&3?hsE&<8bdXKNW!tI_CihOpuQ!=vId~^^G=ak2-^(p9G47<`Aq;$* zLv8Flm`CWa*mj%EeLFt^`{WDIn=a7ZQ=of(P=^$y=>PY;4p zQJl<;z?VisSIWYg_>|5C8nEy*JX!-#t-4FUYppNYCoF78{ z)MHq}d_RbG8Jhs31zk9oehzCP0h;XQvocSRb?-Y1dU}y{31d?sj+)_{*wF4u7{ssN zFT2DG9mdhjAZ$G+#2H1~%jTqrSD}#m7rnj)nt#Z-HS6 z$~;5WtgyKBx1^u&U5sHH--#>P_nec*7~M zqu4%`KtT8=@ym3=?l>u2=MZZz^jF+`FS@W}doGCv y@fAR>dWi(rIILs3`FNDDVg4`=%BE literal 0 HcmV?d00001 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(); +} From db9ecd744f982791cf627366788920753694fd50 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 1 Aug 2025 21:51:01 -0400 Subject: [PATCH 5/8] Improve metatile properties layout --- forms/tileseteditor.ui | 505 ++++++++++++++++++------- include/ui/tileseteditor.h | 7 +- include/ui/tileseteditortileselector.h | 1 - src/config.cpp | 2 +- src/ui/tileseteditor.cpp | 173 +++++---- 5 files changed, 465 insertions(+), 223 deletions(-) diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 4c797767..5f7a512f 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -65,7 +65,7 @@ 0 0 - 253 + 241 659 @@ -195,170 +195,290 @@ false - - - - true - - - - - + + - + 0 0 - - - 185 - 0 - - + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Raw Attributes Value + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + - - - - Metatile Label (Optional) - - - - - - - Bottom/Top - - - - - - - Terrain Type - - - - - - - Layer Type - - - - - + + - + 0 0 - - QComboBox::InsertPolicy::NoInsert - + + + 0 + + + 0 + + + 0 + + + + + Layer Type + + + + + + + + 0 + 0 + + + + + 185 + 0 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + - - + + - + 0 0 - - - 1 - 1 - - - - Qt::ScrollBarPolicy::ScrollBarAlwaysOff - - - Qt::ScrollBarPolicy::ScrollBarAlwaysOff - + + + 0 + + + 0 + + + 0 + + + 2 + + + + + <html><head/><body><p>Copies the full metatile label to the clipboard.</p></body></html> + + + ... + + + + :/icons/clipboard.ico:/icons/clipboard.ico + + + + + + + true + + + + + + + Metatile Label (Optional) + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + - - + + - + 0 0 - - QComboBox::InsertPolicy::NoInsert - + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Terrain Type + + + + + + + + 0 + 0 + + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + - - - - Qt::Orientation::Horizontal - - - QSizePolicy::Policy::Maximum - - - - 10 - 20 - - - - - - + + - + 0 0 - - QComboBox::InsertPolicy::NoInsert - + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Encounter Type + + + + + + + + 0 + 0 + + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + - - - - Metatile Behavior - - - - - - - Raw Attributes Value - - - - - - - Encounter Type - - - - - - - <html><head/><body><p>Copies the full metatile label to the clipboard.</p></body></html> - - - ... - - - - :/icons/clipboard.ico:/icons/clipboard.ico - - - - + Qt::Orientation::Vertical @@ -371,8 +491,119 @@ - - + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Metatile Behavior + + + + + + + + 0 + 0 + + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + + + Bottom/Top + + + + + + + + 0 + 0 + + + + + 1 + 1 + + + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + @@ -512,12 +743,6 @@ QFrame::Shape::StyledPanel - - Qt::ScrollBarPolicy::ScrollBarAlwaysOff - - - Qt::ScrollBarPolicy::ScrollBarAlwaysOff - @@ -555,8 +780,8 @@ 0 0 - 446 - 241 + 458 + 203 diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 75a81a24..80242ab8 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -96,9 +96,7 @@ private slots: void on_actionUndo_triggered(); void on_actionRedo_triggered(); - void on_lineEdit_metatileLabel_editingFinished(); - - void on_copyButton_metatileLabel_clicked(); + void on_copyButton_MetatileLabel_clicked(); void on_actionCut_triggered(); void on_actionCopy_triggered(); @@ -155,6 +153,8 @@ private: bool swapMetatiles(uint16_t metatileIdA, uint16_t metatileIdB); void applyMetatileSwapToLayouts(uint16_t metatileIdA, uint16_t metatileIdB); void applyMetatileSwapsToLayouts(); + void rebuildMetatilePropertiesFrame(); + void addWidgetToMetatileProperties(QWidget *w, int *row, int rowSpan); Ui::TilesetEditor *ui; History metatileHistory; @@ -179,6 +179,7 @@ private: QSet metatileReloadQueue; MetatileImageExporter::Settings *metatileImageExportSettings = nullptr; QList> metatileIdSwaps; + int numLayerViewRows; bool save(); diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index 915a9227..8c4367e3 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -32,7 +32,6 @@ public: QImage buildPrimaryTilesIndexedImage(); QImage buildSecondaryTilesIndexedImage(); - QVector usedTiles; bool showUnused = false; bool showDivider = false; diff --git a/src/config.cpp b/src/config.cpp index 4bff6a78..51d63682 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -338,7 +338,7 @@ void PorymapConfig::reset() { this->metatilesZoom = 30; this->tilesetEditorMetatilesZoom = 30; this->tilesetEditorTilesZoom = 30; - this->tilesetEditorLayerOrientation = Qt::Horizontal; + this->tilesetEditorLayerOrientation = Qt::Vertical; this->showPlayerView = false; this->showCursorTile = true; this->showBorder = true; diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 879fa1f8..52eab755 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -30,7 +30,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) auto validator = new IdentifierValidator(this); validator->setAllowEmpty(true); - ui->lineEdit_metatileLabel->setValidator(validator); + ui->lineEdit_MetatileLabel->setValidator(validator); ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes); @@ -65,6 +65,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) connect(ui->actionLayer_Arrangement_Horizontal, &QAction::triggered, [this] { setMetatileLayerOrientation(Qt::Horizontal); }); connect(ui->actionLayer_Arrangement_Vertical, &QAction::triggered, [this] { setMetatileLayerOrientation(Qt::Vertical); }); + connect(ui->lineEdit_MetatileLabel, &QLineEdit::editingFinished, this, &TilesetEditor::commitMetatileLabel); + initAttributesUi(); initMetatileSelector(); initMetatileLayersItem(); @@ -141,77 +143,71 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi } void TilesetEditor::initAttributesUi() { - connect(ui->comboBox_metatileBehaviors, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitMetatileBehavior); - connect(ui->comboBox_encounterType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitEncounterType); - connect(ui->comboBox_terrainType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitTerrainType); - connect(ui->comboBox_layerType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitLayerType); + connect(ui->comboBox_MetatileBehaviors, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitMetatileBehavior); + connect(ui->comboBox_EncounterType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitEncounterType); + connect(ui->comboBox_TerrainType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitTerrainType); + connect(ui->comboBox_LayerType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitLayerType); // Behavior if (projectConfig.metatileBehaviorMask) { for (auto i = project->metatileBehaviorMapInverse.constBegin(); i != project->metatileBehaviorMapInverse.constEnd(); i++) { - this->ui->comboBox_metatileBehaviors->addItem(i.value(), i.key()); + this->ui->comboBox_MetatileBehaviors->addItem(i.value(), i.key()); } - this->ui->comboBox_metatileBehaviors->setMinimumContentsLength(0); + this->ui->comboBox_MetatileBehaviors->setMinimumContentsLength(0); } else { - this->ui->comboBox_metatileBehaviors->setVisible(false); - this->ui->label_metatileBehavior->setVisible(false); + this->ui->frame_MetatileBehavior->setVisible(false); } // Terrain Type if (projectConfig.metatileTerrainTypeMask) { for (auto i = project->terrainTypeToName.constBegin(); i != project->terrainTypeToName.constEnd(); i++) { - this->ui->comboBox_terrainType->addItem(i.value(), i.key()); + this->ui->comboBox_TerrainType->addItem(i.value(), i.key()); } - this->ui->comboBox_terrainType->setMinimumContentsLength(0); + this->ui->comboBox_TerrainType->setMinimumContentsLength(0); } else { - this->ui->comboBox_terrainType->setVisible(false); - this->ui->label_terrainType->setVisible(false); + this->ui->frame_TerrainType->setVisible(false); } // Encounter Type if (projectConfig.metatileEncounterTypeMask) { for (auto i = project->encounterTypeToName.constBegin(); i != project->encounterTypeToName.constEnd(); i++) { - this->ui->comboBox_encounterType->addItem(i.value(), i.key()); + this->ui->comboBox_EncounterType->addItem(i.value(), i.key()); } - this->ui->comboBox_encounterType->setMinimumContentsLength(0); + this->ui->comboBox_EncounterType->setMinimumContentsLength(0); } else { - this->ui->comboBox_encounterType->setVisible(false); - this->ui->label_encounterType->setVisible(false); + this->ui->frame_EncounterType->setVisible(false); } // Layer Type if (!projectConfig.tripleLayerMetatilesEnabled) { - this->ui->comboBox_layerType->addItem("Normal - Middle/Top", Metatile::LayerType::Normal); - this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", Metatile::LayerType::Covered); - this->ui->comboBox_layerType->addItem("Split - Bottom/Top", Metatile::LayerType::Split); - this->ui->comboBox_layerType->setEditable(false); - this->ui->comboBox_layerType->setMinimumContentsLength(0); + this->ui->comboBox_LayerType->addItem("Normal - Middle/Top", Metatile::LayerType::Normal); + this->ui->comboBox_LayerType->addItem("Covered - Bottom/Middle", Metatile::LayerType::Covered); + this->ui->comboBox_LayerType->addItem("Split - Bottom/Top", Metatile::LayerType::Split); + this->ui->comboBox_LayerType->setEditable(false); + this->ui->comboBox_LayerType->setMinimumContentsLength(0); if (!projectConfig.metatileLayerTypeMask) { // User doesn't have triple layer metatiles, but has no layer type attribute. // Porymap is still using the layer type value to render metatiles, and with // no mask set every metatile will be "Middle/Top", so just display the combo // box but prevent the user from changing the value. - this->ui->comboBox_layerType->setEnabled(false); + this->ui->comboBox_LayerType->setEnabled(false); } } else { - this->ui->comboBox_layerType->setVisible(false); - this->ui->label_layerType->setVisible(false); + this->ui->frame_LayerType->setVisible(false); this->ui->label_BottomTop->setText("Bottom/Middle/Top"); } // Raw attributes value - ui->spinBox_rawAttributesValue->setMaximum(Metatile::getMaxAttributesMask()); + ui->spinBox_RawAttributesValue->setMaximum(Metatile::getMaxAttributesMask()); setRawAttributesVisible(ui->actionShow_Raw_Metatile_Attributes->isChecked()); - connect(ui->spinBox_rawAttributesValue, &UIntHexSpinBox::editingFinished, this, &TilesetEditor::onRawAttributesEdited); + connect(ui->spinBox_RawAttributesValue, &UIntHexSpinBox::editingFinished, this, &TilesetEditor::onRawAttributesEdited); connect(ui->actionShow_Raw_Metatile_Attributes, &QAction::toggled, this, &TilesetEditor::setRawAttributesVisible); - - this->ui->frame_Properties->adjustSize(); } void TilesetEditor::setRawAttributesVisible(bool visible) { porymapConfig.showTilesetEditorRawAttributes = visible; - ui->label_rawAttributesValue->setVisible(visible); - ui->spinBox_rawAttributesValue->setVisible(visible); + ui->frame_RawAttributesValue->setVisible(visible); + rebuildMetatilePropertiesFrame(); } void TilesetEditor::initMetatileSelector() @@ -262,25 +258,51 @@ void TilesetEditor::setMetatileLayerOrientation(Qt::Orientation orientation) { int w = Tile::pixelWidth() * numTilesWide * scale + 2; int h = Tile::pixelHeight() * numTilesTall * scale + 2; ui->graphicsView_selectedTile->setFixedSize(w, h); - ui->graphicsView_metatileLayers->setFixedSize(w, h); + ui->graphicsView_MetatileLayers->setFixedSize(w, h); // If the layers are laid out vertically then the orientation is obvious, no need to label them. + // This also lets us give the vertical space of the label over to the layer view. ui->label_BottomTop->setVisible(horizontal); - // Let the graphics view take over the label's vertical space (or conversely, give the space back). - // (This is a bit of a process, apparently there's no quick way to set a widget's row / row span once they're added to the layout - int row, col, rowSpan, colSpan; - int index = ui->gridLayout_MetatileProperties->indexOf(ui->label_BottomTop); - ui->gridLayout_MetatileProperties->getItemPosition(index, &row, &col, &rowSpan, &colSpan); + rebuildMetatilePropertiesFrame(); +} - // TODO: Rearrange the rest of the metatile properties panel. The vertical triple-layer metatiles layout esp. looks terrible. - ui->gridLayout_MetatileProperties->removeWidget(ui->graphicsView_metatileLayers); - if (horizontal) { - // Give space from graphics view back to label - ui->gridLayout_MetatileProperties->addWidget(ui->graphicsView_metatileLayers, row + 1, col, rowSpan, colSpan); +// We rearrange the metatile properties panel depending on the orientation and size of the metatile layer view. +// If triple layer metatiles are in-use then layer type field is hidden, so there's an awkward amount of space +// next to the layer view, especially in the vertical orientation. +// We shift 1-2 widgets up to fill this space next to the layer view. This gets a little complicated because which +// widgets are available to move changes depending on the user's settings. +void TilesetEditor::rebuildMetatilePropertiesFrame() { + if (porymapConfig.tilesetEditorLayerOrientation == Qt::Horizontal) { + this->numLayerViewRows = 1; } else { - // Take space from label and give it to graphics view - ui->gridLayout_MetatileProperties->addWidget(ui->graphicsView_metatileLayers, row, col, rowSpan + 1, colSpan); + this->numLayerViewRows = projectConfig.tripleLayerMetatilesEnabled ? 4 : 2; + } + + for (const auto &frame : ui->gridLayout_MetatileProperties->findChildren()) { + ui->gridLayout_MetatileProperties->removeWidget(frame); + } + ui->gridLayout_MetatileProperties->addWidget(ui->frame_Layers, 0, 0, this->numLayerViewRows, 1); + + int row = 0; + addWidgetToMetatileProperties(ui->frame_LayerType, &row, 2); + if (porymapConfig.tilesetEditorLayerOrientation == Qt::Horizontal) { + // When the layer view's orientation is horizontal we only allow the + // layer type selector to share the row with the layer view. + row = this->numLayerViewRows; + } + addWidgetToMetatileProperties(ui->frame_MetatileBehavior, &row, 2); + addWidgetToMetatileProperties(ui->frame_EncounterType, &row, 2); + addWidgetToMetatileProperties(ui->frame_TerrainType, &row, 2); + addWidgetToMetatileProperties(ui->frame_RawAttributesValue, &row, 2); + addWidgetToMetatileProperties(ui->frame_MetatileLabel, &row, 2); +} + +void TilesetEditor::addWidgetToMetatileProperties(QWidget *w, int *row, int rowSpan) { + if (w->isVisibleTo(ui->frame_Properties)) { + int col = (*row < this->numLayerViewRows) ? 1 : 0; // Shift widget over if it shares the row with the layer view + ui->gridLayout_MetatileProperties->addWidget(w, *row, col, rowSpan, -1); + *row += rowSpan; } } @@ -299,7 +321,7 @@ void TilesetEditor::initMetatileLayersItem() { this->metatileLayersScene = new QGraphicsScene; this->metatileLayersScene->addItem(this->metatileLayersItem); - this->ui->graphicsView_metatileLayers->setScene(this->metatileLayersScene); + this->ui->graphicsView_MetatileLayers->setScene(this->metatileLayersScene); } void TilesetEditor::initTileSelector() { @@ -478,8 +500,8 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { this->metatileLayersItem->draw(); MetatileLabelPair labels = Tileset::getMetatileLabelPair(metatileId, this->primaryTileset, this->secondaryTileset); - this->ui->lineEdit_metatileLabel->setText(labels.owned); - this->ui->lineEdit_metatileLabel->setPlaceholderText(labels.shared); + this->ui->lineEdit_MetatileLabel->setText(labels.owned); + this->ui->lineEdit_MetatileLabel->setPlaceholderText(labels.shared); refreshMetatileAttributes(); } @@ -598,12 +620,7 @@ void TilesetEditor::refreshTileFlips() { void TilesetEditor::setMetatileLabel(QString label) { - this->ui->lineEdit_metatileLabel->setText(label); - commitMetatileLabel(); -} - -void TilesetEditor::on_lineEdit_metatileLabel_editingFinished() -{ + this->ui->lineEdit_MetatileLabel->setText(label); commitMetatileLabel(); } @@ -613,7 +630,7 @@ void TilesetEditor::commitMetatileLabel() { // Only commit if the field has changed. uint16_t metatileId = this->getSelectedMetatileId(); QString oldLabel = Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); - QString newLabel = this->ui->lineEdit_metatileLabel->text(); + QString newLabel = this->ui->lineEdit_MetatileLabel->text(); if (oldLabel != newLabel) { Metatile *prevMetatile = new Metatile(*this->metatile); Tileset::setMetatileLabel(metatileId, newLabel, this->primaryTileset, this->secondaryTileset); @@ -626,12 +643,12 @@ void TilesetEditor::commitMetatileAndLabelChange(Metatile * prevMetatile, QStrin commit(new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile), - prevLabel, this->ui->lineEdit_metatileLabel->text())); + prevLabel, this->ui->lineEdit_MetatileLabel->text())); } void TilesetEditor::commitMetatileChange(Metatile * prevMetatile) { - this->commitMetatileAndLabelChange(prevMetatile, this->ui->lineEdit_metatileLabel->text()); + this->commitMetatileAndLabelChange(prevMetatile, this->ui->lineEdit_MetatileLabel->text()); } uint32_t TilesetEditor::attributeNameToValue(Metatile::Attr attribute, const QString &text, bool *ok) { @@ -650,7 +667,7 @@ uint32_t TilesetEditor::attributeNameToValue(Metatile::Attr attribute, const QSt } } else if (attribute == Metatile::Attr::LayerType) { // The layer type text is not editable, it uses special display names. Just get the index of the display name. - int i = ui->comboBox_layerType->findText(text); + int i = ui->comboBox_LayerType->findText(text); if (i >= 0) return i; } return text.toUInt(ok, 0); @@ -667,8 +684,8 @@ void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScro this->commitMetatileChange(prevMetatile); // When an attribute changes we also need to update the raw value display. - const QSignalBlocker b_RawAttributesValue(ui->spinBox_rawAttributesValue); - ui->spinBox_rawAttributesValue->setValue(this->metatile->getAttributes()); + const QSignalBlocker b_RawAttributesValue(ui->spinBox_RawAttributesValue); + ui->spinBox_RawAttributesValue->setValue(this->metatile->getAttributes()); } // Update the text in the combo box to reflect the final value. @@ -680,7 +697,7 @@ void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScro void TilesetEditor::onRawAttributesEdited() { if (!this->metatile) return; - uint32_t newAttributes = ui->spinBox_rawAttributesValue->value(); + uint32_t newAttributes = ui->spinBox_RawAttributesValue->value(); if (newAttributes != this->metatile->getAttributes()) { Metatile *prevMetatile = new Metatile(*this->metatile); this->metatile->setAttributes(newAttributes); @@ -692,34 +709,34 @@ void TilesetEditor::onRawAttributesEdited() { void TilesetEditor::refreshMetatileAttributes() { if (!this->metatile) return; - const QSignalBlocker b_MetatileBehaviors(ui->comboBox_metatileBehaviors); - const QSignalBlocker b_EncounterType(ui->comboBox_encounterType); - const QSignalBlocker b_TerrainType(ui->comboBox_terrainType); - const QSignalBlocker b_LayerType(ui->comboBox_layerType); - const QSignalBlocker b_RawAttributesValue(ui->spinBox_rawAttributesValue); - ui->comboBox_metatileBehaviors->setHexItem(this->metatile->behavior()); - ui->comboBox_encounterType->setHexItem(this->metatile->encounterType()); - ui->comboBox_terrainType->setHexItem(this->metatile->terrainType()); - ui->comboBox_layerType->setHexItem(this->metatile->layerType()); - ui->spinBox_rawAttributesValue->setValue(this->metatile->getAttributes()); + const QSignalBlocker b_MetatileBehaviors(ui->comboBox_MetatileBehaviors); + const QSignalBlocker b_EncounterType(ui->comboBox_EncounterType); + const QSignalBlocker b_TerrainType(ui->comboBox_TerrainType); + const QSignalBlocker b_LayerType(ui->comboBox_LayerType); + const QSignalBlocker b_RawAttributesValue(ui->spinBox_RawAttributesValue); + ui->comboBox_MetatileBehaviors->setHexItem(this->metatile->behavior()); + ui->comboBox_EncounterType->setHexItem(this->metatile->encounterType()); + ui->comboBox_TerrainType->setHexItem(this->metatile->terrainType()); + ui->comboBox_LayerType->setHexItem(this->metatile->layerType()); + ui->spinBox_RawAttributesValue->setValue(this->metatile->getAttributes()); this->metatileSelector->drawSelectedMetatile(); } void TilesetEditor::commitMetatileBehavior() { - commitAttributeFromComboBox(Metatile::Attr::Behavior, ui->comboBox_metatileBehaviors); + commitAttributeFromComboBox(Metatile::Attr::Behavior, ui->comboBox_MetatileBehaviors); } void TilesetEditor::commitEncounterType() { - commitAttributeFromComboBox(Metatile::Attr::EncounterType, ui->comboBox_encounterType); + commitAttributeFromComboBox(Metatile::Attr::EncounterType, ui->comboBox_EncounterType); } void TilesetEditor::commitTerrainType() { - commitAttributeFromComboBox(Metatile::Attr::TerrainType, ui->comboBox_terrainType); + commitAttributeFromComboBox(Metatile::Attr::TerrainType, ui->comboBox_TerrainType); }; void TilesetEditor::commitLayerType() { - commitAttributeFromComboBox(Metatile::Attr::LayerType, ui->comboBox_layerType); + commitAttributeFromComboBox(Metatile::Attr::LayerType, ui->comboBox_LayerType); this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear } @@ -919,7 +936,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile &src, QS Tileset::setMetatileLabel(metatileId, newLabel, this->primaryTileset, this->secondaryTileset); if (metatileId == this->getSelectedMetatileId()) - this->ui->lineEdit_metatileLabel->setText(newLabel); + this->ui->lineEdit_MetatileLabel->setText(newLabel); // Update tile usage if any tiles changed if (this->tileSelector && this->tileSelector->showUnused) { @@ -1023,7 +1040,7 @@ void TilesetEditor::pasteMetatile(const Metatile &toPaste, QString newLabel) { if (!this->metatile) return; Metatile *prevMetatile = new Metatile(*this->metatile); - QString prevLabel = this->ui->lineEdit_metatileLabel->text(); + QString prevLabel = this->ui->lineEdit_MetatileLabel->text(); if (newLabel.isNull()) newLabel = prevLabel; // Don't change the label if one wasn't copied uint16_t metatileId = this->getSelectedMetatileId(); if (!this->replaceMetatile(metatileId, toPaste, newLabel)) { @@ -1246,7 +1263,7 @@ void TilesetEditor::countTileUsage() { } } -void TilesetEditor::on_copyButton_metatileLabel_clicked() { +void TilesetEditor::on_copyButton_MetatileLabel_clicked() { uint16_t metatileId = this->getSelectedMetatileId(); QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); if (label.isEmpty()) return; @@ -1254,7 +1271,7 @@ void TilesetEditor::on_copyButton_metatileLabel_clicked() { if (tileset) label.prepend(tileset->getMetatileLabelPrefix()); QGuiApplication::clipboard()->setText(label); - QToolTip::showText(this->ui->copyButton_metatileLabel->mapToGlobal(QPoint(0, 0)), "Copied!"); + QToolTip::showText(this->ui->copyButton_MetatileLabel->mapToGlobal(QPoint(0, 0)), "Copied!"); } void TilesetEditor::on_horizontalSlider_MetatilesZoom_valueChanged(int value) { From c1fc2c8201155e92a18f4c1ce60432ede0bd2fae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 4 Aug 2025 13:33:57 -0400 Subject: [PATCH 6/8] Fix bounds checking for external tile selections --- forms/tileseteditor.ui | 6 +++++ include/ui/tileseteditortileselector.h | 5 ++-- src/ui/tileseteditor.cpp | 17 +++++------- src/ui/tileseteditortileselector.cpp | 36 +++++++++++++++++++------- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 5f7a512f..3bab4ca2 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -743,6 +743,12 @@ QFrame::Shape::StyledPanel + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index 8c4367e3..c98239be 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -27,7 +27,7 @@ public: void setPaletteId(int); void setTileFlips(bool, bool); QList getSelectedTiles(); - void setExternalSelection(int, int, QList, QList); + void setExternalSelection(int, int, const QList&); QPoint getTileCoordsOnWidget(uint16_t); QImage buildPrimaryTilesIndexedImage(); QImage buildSecondaryTilesIndexedImage(); @@ -49,7 +49,6 @@ private: int externalSelectionWidth; int externalSelectionHeight; QList externalSelectedTiles; - QList externalSelectedPos; QPoint prevCellPos = QPoint(-1,-1); Tileset *primaryTileset; @@ -64,7 +63,7 @@ private: uint16_t getTileId(int x, int y); QPoint getTileCoords(uint16_t); QList getCurPaletteTable(); - QList buildSelectedTiles(int, int, QList); + QList buildSelectedTiles(int, int, const QList&); QImage buildImage(int tileIdStart, int numTiles); void updateBasePixmap(); void drawUnused(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 52eab755..99320682 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -260,6 +260,8 @@ void TilesetEditor::setMetatileLayerOrientation(Qt::Orientation orientation) { ui->graphicsView_selectedTile->setFixedSize(w, h); ui->graphicsView_MetatileLayers->setFixedSize(w, h); + drawSelectedTiles(); + // If the layers are laid out vertically then the orientation is obvious, no need to label them. // This also lets us give the vertical space of the label over to the layer view. ui->label_BottomTop->setVisible(horizontal); @@ -574,19 +576,14 @@ void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) void TilesetEditor::onMetatileLayerSelectionChanged(const QPoint &selectionOrigin, const QSize &size) { QList tiles; - QList tileIdxs; - int maxTileIndex = projectConfig.getNumTilesInMetatile(); - for (int j = 0; j < size.height(); j++) { - 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 ? this->metatile->tiles.value(tileIndex) : Tile()); - tileIdxs.append(tileIndex); - } + for (int y = 0; y < size.height(); y++) { + for (int x = 0; x < size.width(); x++) { + int tileIndex = this->metatileLayersItem->posToTileIndex(selectionOrigin.x() + x, selectionOrigin.y() + y); + tiles.append(this->metatile ? this->metatile->tiles.value(tileIndex) : Tile()); } } - this->tileSelector->setExternalSelection(size.width(), size.height(), tiles, tileIdxs); + this->tileSelector->setExternalSelection(size.width(), size.height(), tiles); if (size == QSize(1,1)) { setPaletteId(tiles[0].palette); this->tileSelector->highlight(tiles[0].tileId); diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index ec3caa83..e088cdee 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -13,8 +13,27 @@ QSize TilesetEditorTileSelector::getSelectionDimensions() const { } void TilesetEditorTileSelector::setMaxSelectionSize(int width, int height) { + width = qMax(1, width); + height = qMax(1, height); SelectablePixmapItem::setMaxSelectionSize(width, height); - updateSelectedTiles(); + if (this->externalSelection) { + if (this->externalSelectionWidth > this->maxSelectionWidth || this->externalSelectionHeight > this->maxSelectionHeight) { + // Crop external selection to new max size. + QList cropped; + int croppedWidth = qMin(this->externalSelectionWidth, this->maxSelectionWidth); + int croppedHeight = qMin(this->externalSelectionHeight, this->maxSelectionHeight); + for (int y = 0; y < croppedHeight; y++) + for (int x = 0; x < croppedWidth; x++) { + int index = y * this->externalSelectionWidth + x; + cropped.append(this->externalSelectedTiles.value(index)); + } + this->externalSelectionWidth = croppedWidth; + this->externalSelectionHeight = croppedHeight; + this->externalSelectedTiles = cropped; + } + } else { + updateSelectedTiles(); + } } void TilesetEditorTileSelector::updateBasePixmap() { @@ -118,7 +137,7 @@ QList TilesetEditorTileSelector::getSelectedTiles() { } } -QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, QList selected) { +QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, const QList &selected) { QList tiles; QList> tileMatrix; for (int j = 0; j < height; j++) { @@ -126,7 +145,7 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, QList layerRow; for (int i = 0; i < width; i++) { int index = i + j * width; - Tile tile = selected.at(index); + Tile tile = selected.value(index); tile.xflip ^= this->xFlip; tile.yflip ^= this->yFlip; if (this->paletteChanged) @@ -139,7 +158,7 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, // If we've completed a layer row, or its the last tile of an incompletely // selected layer, then append the layer row to the full row // If not an external selection, treat the whole row as 1 "layer" - if (i == width - 1 || (this->externalSelection && (this->externalSelectedPos.at(index) % Metatile::tilesPerLayer()) & 1)) { + if (i == width - 1) { row.append(layerRow); layerRow.clear(); } @@ -157,15 +176,14 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, return tiles; } -void TilesetEditorTileSelector::setExternalSelection(int width, int height, QList tiles, QList tileIdxs) { +void TilesetEditorTileSelector::setExternalSelection(int width, int height, const QList &tiles) { + width = qBound(1, width, this->maxSelectionWidth); + height = qBound(1, height, this->maxSelectionHeight); this->externalSelection = true; this->paletteChanged = false; this->externalSelectionWidth = width; this->externalSelectionHeight = height; - this->externalSelectedTiles.clear(); - this->externalSelectedTiles.append(tiles); - this->externalSelectedPos.clear(); - this->externalSelectedPos.append(tileIdxs); + this->externalSelectedTiles = tiles.mid(0, width * height); this->draw(); emit selectedTilesChanged(); } From b498eeec0086f1f8997fc3a2cd18b5ecd9ca8791 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 4 Aug 2025 14:13:01 -0400 Subject: [PATCH 7/8] Fix tileset divider drawing, tile usage counts --- include/core/tileset.h | 6 +++- src/core/tileset.cpp | 12 +++++-- src/ui/metatileselector.cpp | 8 ++--- src/ui/tileseteditor.cpp | 46 +++++++++++------------- src/ui/tileseteditormetatileselector.cpp | 8 ++--- src/ui/tileseteditortileselector.cpp | 22 +++++++----- 6 files changed, 56 insertions(+), 46 deletions(-) diff --git a/include/core/tileset.h b/include/core/tileset.h index 3d137056..c804a0f9 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -90,7 +90,11 @@ public: uint16_t firstMetatileId() const; uint16_t lastMetatileId() const; - bool contains(uint16_t metatileId) const { return metatileId >= firstMetatileId() && metatileId <= lastMetatileId(); } + bool containsMetatileId(uint16_t metatileId) const { return metatileId >= firstMetatileId() && metatileId <= lastMetatileId(); } + + uint16_t firstTileId() const; + uint16_t lastTileId() const; + bool containsTileId(uint16_t tileId) const { return tileId > firstTileId() && tileId <= lastTileId(); } int numTiles() const { return m_tiles.length(); } int maxTiles() const; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index d76b0138..0aa1f7d6 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -107,6 +107,14 @@ int Tileset::maxMetatiles() const { return this->is_secondary ? Project::getNumMetatilesSecondary() : Project::getNumMetatilesPrimary(); } +uint16_t Tileset::firstTileId() const { + return this->is_secondary ? Project::getNumTilesPrimary() : 0; +} + +uint16_t Tileset::lastTileId() const { + return qMax(1, firstMetatileId() + m_tiles.length()) - 1; +} + int Tileset::maxTiles() const { return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary(); } @@ -250,8 +258,8 @@ QString Tileset::getMetatileLabelPrefix(const QString &name) } bool Tileset::metatileIsValid(uint16_t metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { - return (primaryTileset && primaryTileset->contains(metatileId)) - || (secondaryTileset && secondaryTileset->contains(metatileId)); + return (primaryTileset && primaryTileset->containsMetatileId(metatileId)) + || (secondaryTileset && secondaryTileset->containsMetatileId(metatileId)); } QList> Tileset::getBlockPalettes(const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) { diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 639dc5fa..00725b9a 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -204,7 +204,7 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const { if (ok) *ok = true; int index = y * this->numMetatilesWide + x; uint16_t metatileId = static_cast(index); - if (primaryTileset() && primaryTileset()->contains(metatileId)) { + if (primaryTileset() && primaryTileset()->containsMetatileId(metatileId)) { return metatileId; } @@ -215,7 +215,7 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const { int numPrimaryRounded = numPrimaryMetatilesRounded(); int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide; metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded); - if (secondaryTileset() && secondaryTileset()->contains(metatileId) && y >= firstSecondaryRow) { + if (secondaryTileset() && secondaryTileset()->containsMetatileId(metatileId) && y >= firstSecondaryRow) { return metatileId; } @@ -224,12 +224,12 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const { } QPoint MetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const { - if (primaryTileset() && primaryTileset()->contains(metatileId)) { + if (primaryTileset() && primaryTileset()->containsMetatileId(metatileId)) { if (ok) *ok = true; int index = metatileId; return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } - if (secondaryTileset() && secondaryTileset()->contains(metatileId)) { + if (secondaryTileset() && secondaryTileset()->containsMetatileId(metatileId)) { if (ok) *ok = true; int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 99320682..17074063 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -794,7 +794,7 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset) { int numTilesWide = image.width() / Tile::pixelWidth(); int numTilesHigh = image.height() / Tile::pixelHeight(); int totalTiles = numTilesHigh * numTilesWide; - int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesTotal() - Project::getNumTilesPrimary(); + int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesSecondary(); if (totalTiles > maxAllowedTiles) { ErrorMessage::show(QStringLiteral("Failed to import tiles."), QString("The maximum number of tiles allowed in the %1 tileset is %2, but the provided image contains %3 total tiles.") @@ -899,7 +899,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() // 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)) { + if (tileset && !tileset->containsMetatileId(metatileId)) { this->metatileSelector->select(qBound(tileset->firstMetatileId(), metatileId, tileset->lastMetatileId())); } @@ -1233,31 +1233,25 @@ void TilesetEditor::countTileUsage() { this->tileSelector->usedTiles.resize(Project::getNumTilesTotal()); this->tileSelector->usedTiles.fill(0); - // 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()) - this->tileSelector->usedTiles[tile.tileId]++; + auto countTilesetTileUsage = [this](Tileset *searchTileset) { + // Count usage of our search tileset's tiles (in itself, and in any tilesets it gets paired with). + QSet tilesetNames = this->project->getPairedTilesetLabels(searchTileset); + tilesetNames.insert(searchTileset->name); + for (const auto &tilesetName : tilesetNames) { + Tileset *tileset = this->project->getTileset(tilesetName); + if (!tileset) continue; + for (const auto &metatile : tileset->metatiles()) { + for (const auto &tile : metatile->tiles) { + if (searchTileset->containsTileId(tile.tileId)) { + this->tileSelector->usedTiles[tile.tileId]++; + } + } } } - } + }; - // 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]++; - } - } - } + countTilesetTileUsage(this->primaryTileset); + countTilesetTileUsage(this->secondaryTileset); } void TilesetEditor::on_copyButton_MetatileLabel_clicked() { @@ -1396,9 +1390,9 @@ void TilesetEditor::applyMetatileSwapToLayouts(uint16_t metatileIdA, uint16_t me // Get which tilesets our swapped metatiles belong to. auto addSourceTileset = [this](uint16_t metatileId, TilesetPair *tilesets) { - if (this->primaryTileset->contains(metatileId)) { + if (this->primaryTileset->containsMetatileId(metatileId)) { tilesets->primary = this->primaryTileset; - } else if (this->secondaryTileset->contains(metatileId)) { + } else if (this->secondaryTileset->containsMetatileId(metatileId)) { tilesets->secondary = this->secondaryTileset; } else { // Invalid metatile, shouldn't happen diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index ebb05187..77b14aca 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -205,7 +205,7 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok) if (ok) *ok = true; int index = y * this->numMetatilesWide + x; uint16_t metatileId = static_cast(index); - if (this->primaryTileset && this->primaryTileset->contains(metatileId)) { + if (this->primaryTileset && this->primaryTileset->containsMetatileId(metatileId)) { return metatileId; } @@ -216,7 +216,7 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok) int numPrimaryRounded = numPrimaryMetatilesRounded(); int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide; metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded); - if (this->secondaryTileset && this->secondaryTileset->contains(metatileId) && y >= firstSecondaryRow) { + if (this->secondaryTileset && this->secondaryTileset->containsMetatileId(metatileId) && y >= firstSecondaryRow) { return metatileId; } @@ -225,12 +225,12 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok) } QPoint TilesetEditorMetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const { - if (this->primaryTileset && this->primaryTileset->contains(metatileId)) { + if (this->primaryTileset && this->primaryTileset->containsMetatileId(metatileId)) { if (ok) *ok = true; int index = metatileId; return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } - if (this->secondaryTileset && this->secondaryTileset->contains(metatileId)) { + if (this->secondaryTileset && this->secondaryTileset->containsMetatileId(metatileId)) { if (ok) *ok = true; int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index e088cdee..e82e7dc6 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -54,22 +54,26 @@ void TilesetEditorTileSelector::updateBasePixmap() { int y = (tileId / this->numTilesWide) * this->cellHeight; painter.drawImage(x, y, tileImage); } - - if (this->showDivider) { - int row = Util::roundUpToMultiple(Project::getNumTilesPrimary(), this->numTilesWide) / this->numTilesWide; - const int y = row * this->cellHeight; - painter.setPen(Qt::white); - painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y); - } - painter.end(); + this->basePixmap = QPixmap::fromImage(image); } void TilesetEditorTileSelector::draw() { if (this->basePixmap.isNull()) updateBasePixmap(); - setPixmap(this->basePixmap); + + QPixmap pixmap = this->basePixmap; + + if (this->showDivider) { + QPainter painter(&pixmap); + int row = Util::roundUpToMultiple(Project::getNumTilesPrimary(), this->numTilesWide) / this->numTilesWide; + const int y = row * this->cellHeight; + painter.setPen(Qt::white); + painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y); + } + + setPixmap(pixmap); if (!this->externalSelection || (this->externalSelectionWidth == 1 && this->externalSelectionHeight == 1)) { this->drawSelection(); From 58540ab062d455ff55b10334c58dd8c51f356c9a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 4 Aug 2025 14:36:46 -0400 Subject: [PATCH 8/8] Highlight hovered metatile while swapping --- include/ui/tileseteditormetatileselector.h | 4 +- src/ui/tileseteditormetatileselector.cpp | 73 +++++++++++++--------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 4339733a..542a67c5 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -45,9 +45,11 @@ private: QPixmap basePixmap; Tileset *primaryTileset = nullptr; Tileset *secondaryTileset = nullptr; - uint16_t selectedMetatileId; + uint16_t selectedMetatileId = 0; + QPoint prevCellPos = QPoint(-1,-1); QList swapMetatileIds; + uint16_t lastHoveredMetatileId = 0; bool inSwapMode = false; void updateBasePixmap(); diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 77b14aca..1329a49b 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -82,7 +82,9 @@ void TilesetEditorMetatileSelector::draw() { drawFilters(); if (this->inSwapMode) { - for (const auto &metatileId : this->swapMetatileIds) { + QSet metatileIds(this->swapMetatileIds.constBegin(), this->swapMetatileIds.constEnd()); + metatileIds.insert(this->lastHoveredMetatileId); + for (const auto &metatileId : metatileIds) { bool ok; QPoint pos = metatileIdToPos(metatileId, &ok); if (ok) drawSelectionRect(pos, QSize(1,1), Qt::DashLine); @@ -141,8 +143,38 @@ void TilesetEditorMetatileSelector::clearSwapSelection() { } void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { + QPoint cellPos = getCellPos(event->pos()); + bool ok; - uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok); + uint16_t metatileId = posToMetatileId(cellPos, &ok); + if (!ok) return; + + SelectablePixmapItem::mousePressEvent(event); + this->selectedMetatileId = this->lastHoveredMetatileId = metatileId; + emit selectedMetatileChanged(this->selectedMetatileId); + this->prevCellPos = cellPos; +} + +void TilesetEditorMetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + QPoint cellPos = getCellPos(event->pos()); + if (cellPos == this->prevCellPos) return; + + bool ok; + uint16_t metatileId = posToMetatileId(cellPos, &ok); + if (!ok) return; + + SelectablePixmapItem::mouseMoveEvent(event); + this->selectedMetatileId = this->lastHoveredMetatileId = metatileId; + emit selectedMetatileChanged(this->selectedMetatileId); + emit hoveredMetatileChanged(this->selectedMetatileId); + this->prevCellPos = cellPos; +} + +void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + QPoint cellPos = getCellPos(event->pos()); + + bool ok; + uint16_t metatileId = posToMetatileId(cellPos, &ok); if (!ok) return; if (this->inSwapMode) { @@ -153,48 +185,31 @@ void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *ev } } - SelectablePixmapItem::mousePressEvent(event); - this->selectedMetatileId = metatileId; - emit selectedMetatileChanged(this->selectedMetatileId); -} - -void TilesetEditorMetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (this->inSwapMode) return; - - bool ok; - uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok); - if (!ok) return; - - SelectablePixmapItem::mouseMoveEvent(event); - this->selectedMetatileId = metatileId; - emit selectedMetatileChanged(this->selectedMetatileId); - emit hoveredMetatileChanged(this->selectedMetatileId); -} - -void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (this->inSwapMode) return; - - bool ok; - uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok); - if (!ok) return; - SelectablePixmapItem::mouseReleaseEvent(event); - this->selectedMetatileId = metatileId; + this->selectedMetatileId = this->lastHoveredMetatileId = metatileId; emit selectedMetatileChanged(this->selectedMetatileId); + this->prevCellPos = cellPos; } void TilesetEditorMetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { + QPoint cellPos = getCellPos(event->pos()); + if (cellPos == this->prevCellPos) return; + bool ok; - uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok); + uint16_t metatileId = posToMetatileId(cellPos, &ok); if (ok) { + this->lastHoveredMetatileId = metatileId; emit this->hoveredMetatileChanged(metatileId); + if (this->inSwapMode) draw(); } else { emit this->hoveredMetatileCleared(); } + this->prevCellPos = cellPos; } void TilesetEditorMetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { emit this->hoveredMetatileCleared(); + this->prevCellPos = QPoint(-1,-1); } uint16_t TilesetEditorMetatileSelector::posToMetatileId(const QPoint &pos, bool *ok) const {