From 513ba158386f0997551112f23b23748dee06a547 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 15 Jun 2025 19:17:07 -0400 Subject: [PATCH 01/71] Remove tile rectangle from non-tile tools --- src/editor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/editor.cpp b/src/editor.cpp index 5fb3ef80..298f4ae0 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -169,7 +169,6 @@ void Editor::setEditMode(EditMode editMode) { editStack = &this->layout->editHistory; } - this->cursorMapTileRect->setActive(editingLayout); this->playerViewRect->setActive(editingLayout); this->editGroup.setActiveStack(editStack); this->ui->toolButton_Fill->setEnabled(editingLayout); @@ -204,6 +203,10 @@ void Editor::setEditAction(EditAction editAction) { this->map_ruler->setEnabled(false); } + // Only show the tile cursor for tools that apply at a specific tile + this->cursorMapTileRect->setActive(getEditingLayout() && editAction != EditAction::Select && editAction != EditAction::Move); + + // The tile cursor can only grow while painting metatiles this->cursorMapTileRect->setSingleTileMode(!(editAction == EditAction::Paint && this->editMode == EditMode::Metatiles)); // Update cursor From dd1d856bf349f751e1da799d8f359c8ce337ddba Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Jun 2025 02:18:00 -0400 Subject: [PATCH 02/71] Fix log file cleanup warning only printing to debug --- CHANGELOG.md | 3 ++- src/log.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1a6fa6..f6416511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project somewhat adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The MAJOR version number is bumped when there are **"Breaking Changes"** in the pret projects. For more on this, see [the manual page on breaking changes](https://huderlem.github.io/porymap/manual/breaking-changes.html). ## [Unreleased] -Nothing, yet. +### Fixed +- Fix warning not appearing when the log file exceeds maximum size. ## [6.1.0] - 2025-06-09 ### Added diff --git a/src/log.cpp b/src/log.cpp index 0c23470c..7eaaec26 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -196,8 +196,9 @@ void logInit() { clearLogDisplays(); }); + Log::initialized = true; + if (cleanupLargeLog()) { logWarn(QString("Previous log file %1 was cleared due to being over 20MB in size.").arg(Log::path)); } - Log::initialized = true; } From d1142d244e6bdd11647578f37c6c3de1a7bedd54 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Jun 2025 14:36:18 -0400 Subject: [PATCH 03/71] Refactor cursor rectangles --- include/core/maplayout.h | 1 + include/editor.h | 14 +-- include/ui/collisionpixmapitem.h | 27 ++--- include/ui/cursortilerect.h | 87 ++++++--------- include/ui/layoutpixmapitem.h | 17 +-- include/ui/movablerect.h | 9 +- src/core/maplayout.cpp | 4 + src/editor.cpp | 175 +++++++++++++++---------------- src/mainwindow.cpp | 13 +-- src/ui/collisionpixmapitem.cpp | 10 +- src/ui/cursortilerect.cpp | 116 ++++++++------------ src/ui/layoutpixmapitem.cpp | 10 +- src/ui/movablerect.cpp | 24 ++--- src/ui/resizelayoutpopup.cpp | 4 +- 14 files changed, 221 insertions(+), 290 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 5a2e23b3..c7790ad3 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -100,6 +100,7 @@ public: QRect getVisibleRect() const; bool isWithinBounds(int x, int y) const; + bool isWithinBounds(const QPoint &pos) const; bool isWithinBounds(const QRect &rect) const; bool isWithinBorderBounds(int x, int y) const; diff --git a/include/editor.h b/include/editor.h index a5601196..10891e4c 100644 --- a/include/editor.h +++ b/include/editor.h @@ -122,9 +122,10 @@ public: void updateEventPixmapItemZValue(EventPixmapItem *item); qreal getEventOpacity(const Event *event) const; + bool isMouseInMap() const; void setPlayerViewRect(const QRectF &rect); - void updateCursorRectPos(int x, int y); - void setCursorRectVisible(bool visible); + void setCursorRectPos(const QPoint &pos); + void updateCursorRectVisibility(); void onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); void onEventReleased(Event *event, const QPoint &position); @@ -251,6 +252,7 @@ private: QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); void setCollisionTabSpinBoxes(uint16_t collision, uint16_t elevation); + void adjustStraightPathPos(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item, QPoint *pos) const; static bool startDetachedProcess(const QString &command, const QString &workingDirectory = QString(), qint64 *pid = nullptr); @@ -259,7 +261,6 @@ private slots: void onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void onMapEndPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void setSmartPathCursorMode(QGraphicsSceneMouseEvent *event); - void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); void mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); void setSelectedConnectionItem(ConnectionPixmapItem *connectionItem); @@ -267,10 +268,9 @@ private slots: void onHoveredMovementPermissionCleared(); void onHoveredMetatileSelectionChanged(uint16_t); void onHoveredMetatileSelectionCleared(); - void onHoveredMapMetatileChanged(const QPoint &pos); - void onHoveredMapMetatileCleared(); - void onHoveredMapMovementPermissionChanged(int, int); - void onHoveredMapMovementPermissionCleared(); + void onMapHoverEntered(const QPoint &pos); + void onMapHoverChanged(const QPoint &pos); + void onMapHoverCleared(); void onSelectedMetatilesChanged(); void onWheelZoom(int); diff --git a/include/ui/collisionpixmapitem.h b/include/ui/collisionpixmapitem.h index 1419f2e4..90376c34 100644 --- a/include/ui/collisionpixmapitem.h +++ b/include/ui/collisionpixmapitem.h @@ -23,11 +23,11 @@ public: QSpinBox * selectedElevation; qreal *opacity; void updateMovementPermissionSelection(QGraphicsSceneMouseEvent *event); - virtual void paint(QGraphicsSceneMouseEvent*); - virtual void floodFill(QGraphicsSceneMouseEvent*); - virtual void magicFill(QGraphicsSceneMouseEvent*); - virtual void pick(QGraphicsSceneMouseEvent*); - void draw(bool ignoreCache = false); + virtual void paint(QGraphicsSceneMouseEvent*) override; + virtual void floodFill(QGraphicsSceneMouseEvent*) override; + virtual void magicFill(QGraphicsSceneMouseEvent*) override; + virtual void pick(QGraphicsSceneMouseEvent*) override; + void draw(bool ignoreCache = false) override; private: unsigned actionId_ = 0; @@ -36,16 +36,17 @@ private: signals: void mouseEvent(QGraphicsSceneMouseEvent *, CollisionPixmapItem *); - void hoveredMapMovementPermissionChanged(int, int); - void hoveredMapMovementPermissionCleared(); + void hoverEntered(const QPoint &pos); + void hoverChanged(const QPoint &pos); + void hoverCleared(); protected: - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverEnterEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; }; #endif // COLLISIONPIXMAPITEM_H diff --git a/include/ui/cursortilerect.h b/include/ui/cursortilerect.h index f53ae5aa..92f37f96 100644 --- a/include/ui/cursortilerect.h +++ b/include/ui/cursortilerect.h @@ -8,78 +8,55 @@ class CursorTileRect : public QGraphicsItem { public: - CursorTileRect(bool *enabled, QRgb color); - QRectF boundingRect() const override - { - int width = this->width; - int height = this->height; - if (this->singleTileMode) { - width = 16; - height = 16; - } else if (!this->rightClickSelectionAnchored && this->smartPathMode && this->selectionHeight == 3 && this->selectionWidth == 3) { - width = 32; - height = 32; - } + CursorTileRect(const QSize &tileSize, const QRgb &color, QGraphicsItem *parent = nullptr); + + QSize size() const; + + QRectF boundingRect() const override { + auto s = size(); qreal penWidth = 4; return QRectF(-penWidth, -penWidth, - width + penWidth * 2, - height + penWidth * 2); + s.width() + penWidth * 2, + s.height() + penWidth * 2); } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override - { - if (!(*enabled)) return; - int width = this->width; - int height = this->height; - if (this->singleTileMode) { - width = 16; - height = 16; - } else if (this->smartPathInEffect()) { - width = 32; - height = 32; - } - - painter->setPen(this->color); - painter->drawRect(x() - 1, y() - 1, width + 2, height + 2); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { + if (!isVisible()) return; + auto rect = QRectF(pos(), size()); + painter->setPen(m_color); + painter->drawRect(rect + QMargins(1,1,1,1)); // Fill painter->setPen(QColor(0, 0, 0)); - painter->drawRect(x() - 2, y() - 2, width + 4, height + 4); - painter->drawRect(x(), y(), width, height); + painter->drawRect(rect + QMargins(2,2,2,2)); // Outer border + painter->drawRect(rect); // Inner border } + void initAnchor(int coordX, int coordY); void stopAnchor(); void initRightClickSelectionAnchor(int coordX, int coordY); void stopRightClickSelectionAnchor(); - void setSmartPathMode(bool enable) { this->smartPathMode = enable; } - bool getSmartPathMode() const { return this->smartPathMode; } + void setSmartPathMode(bool enable) { m_smartPathMode = enable; } + bool getSmartPathMode() const { return m_smartPathMode; } - void setStraightPathMode(bool enable) { this->straightPathMode = enable; } - bool getStraightPathMode() const { return this->straightPathMode; } - - void setSingleTileMode(bool enable) { this->singleTileMode = enable; } - bool getSingleTileMode() const { return this->singleTileMode; } + void setSingleTileMode(bool enable) { m_singleTileMode = enable; } + bool getSingleTileMode() const { return m_singleTileMode; } void updateLocation(int x, int y); void updateSelectionSize(int width, int height); - void setActive(bool active); - bool getActive(); - bool *enabled; + private: - bool active; - int width; - int height; - bool anchored; - bool rightClickSelectionAnchored; - bool smartPathMode; - bool straightPathMode; - bool singleTileMode; - int anchorCoordX; - int anchorCoordY; - int selectionWidth; - int selectionHeight; - QRgb color; - bool smartPathInEffect(); + const QSize m_tileSize; + QSize m_selectionSize; + QPoint m_anchorCoord; + QRgb m_color; + + bool m_anchored = false; + bool m_rightClickSelectionAnchored = false; + bool m_smartPathMode = false; + bool m_singleTileMode = false; + + bool smartPathInEffect() const; }; diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h index 695472a5..bed0ce8b 100644 --- a/include/ui/layoutpixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -102,16 +102,17 @@ signals: void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void mouseEvent(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); - void hoveredMapMetatileChanged(const QPoint &pos); - void hoveredMapMetatileCleared(); + void hoverEntered(const QPoint &pos); + void hoverChanged(const QPoint &pos); + void hoverCleared(); protected: - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverEnterEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; }; #endif // MAPPIXMAPITEM_H diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 92dd43f7..552ad6f5 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -10,7 +10,7 @@ class MovableRect : public QGraphicsRectItem { public: - MovableRect(bool *enabled, const QRectF &rect, const QRgb &color); + MovableRect(const QRectF &rect, const QRgb &color); QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, @@ -29,12 +29,7 @@ public: } void updateLocation(int x, int y); - void setActive(bool active); - bool getActive() const { return this->active; } - protected: - bool *enabled = nullptr; - bool active = true; QRectF baseRect; QRgb color; @@ -48,7 +43,7 @@ class ResizableRect : public QObject, public MovableRect { Q_OBJECT public: - ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color); + ResizableRect(QObject *parent, int width, int height, QRgb color); QRectF boundingRect() const override { return QRectF(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 4108400f..4ddb3b75 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -55,6 +55,10 @@ bool Layout::isWithinBounds(int x, int y) const { return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); } +bool Layout::isWithinBounds(const QPoint &pos) const { + return isWithinBounds(pos.x(), pos.y()); +} + bool Layout::isWithinBounds(const QRect &rect) const { return rect.left() >= 0 && rect.right() < this->getWidth() && rect.top() >= 0 && rect.bottom() < this->getHeight(); } diff --git a/src/editor.cpp b/src/editor.cpp index 298f4ae0..0d4f7482 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -30,7 +30,7 @@ Editor::Editor(Ui::MainWindow* ui) { this->ui = ui; this->settings = new Settings(); - this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255)); + this->cursorMapTileRect = new CursorTileRect(QSize(16,16), qRgb(255, 255, 255)); this->map_ruler = new MapRuler(4); connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged); @@ -169,7 +169,6 @@ void Editor::setEditMode(EditMode editMode) { editStack = &this->layout->editHistory; } - this->playerViewRect->setActive(editingLayout); this->editGroup.setActiveStack(editStack); this->ui->toolButton_Fill->setEnabled(editingLayout); this->ui->toolButton_Dropper->setEnabled(editingLayout); @@ -203,8 +202,7 @@ void Editor::setEditAction(EditAction editAction) { this->map_ruler->setEnabled(false); } - // Only show the tile cursor for tools that apply at a specific tile - this->cursorMapTileRect->setActive(getEditingLayout() && editAction != EditAction::Select && editAction != EditAction::Move); + updateCursorRectVisibility(); // The tile cursor can only grow while painting metatiles this->cursorMapTileRect->setSingleTileMode(!(editAction == EditAction::Paint && this->editMode == EditMode::Metatiles)); @@ -1139,15 +1137,20 @@ void Editor::scaleMapView(int s) { ui->graphicsView_Connections->setTransform(transform); } -void Editor::setPlayerViewRect(const QRectF &rect) { - delete this->playerViewRect; - this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, rect, qRgb(255, 255, 255)); - this->playerViewRect->setActive(getEditingLayout()); - if (ui->graphicsView_Map->scene()) - ui->graphicsView_Map->scene()->update(); +bool Editor::isMouseInMap() const { + return (this->map_item && this->map_item->has_mouse) || (this->collision_item && this->collision_item->has_mouse); } -void Editor::updateCursorRectPos(int x, int y) { +void Editor::setPlayerViewRect(const QRectF &rect) { + delete this->playerViewRect; + this->playerViewRect = new MovableRect(rect, qRgb(255, 255, 255)); + updateCursorRectVisibility(); +} + +void Editor::setCursorRectPos(const QPoint &pos) { + int x = qMax(0, qMin(pos.x(), this->layout ? this->layout->getWidth() - 1 : 0)); + int y = qMax(0, qMin(pos.y(), this->layout ? this->layout->getHeight() - 1 : 0)); + if (this->playerViewRect) this->playerViewRect->updateLocation(x, y); if (this->cursorMapTileRect) @@ -1156,23 +1159,55 @@ void Editor::updateCursorRectPos(int x, int y) { ui->graphicsView_Map->scene()->update(); } -void Editor::setCursorRectVisible(bool visible) { - if (this->playerViewRect) - this->playerViewRect->setVisible(visible); - if (this->cursorMapTileRect) - this->cursorMapTileRect->setVisible(visible); - if (ui->graphicsView_Map->scene()) +void Editor::updateCursorRectVisibility() { + bool mouseInMap = isMouseInMap(); + bool changed = false; + + if (this->playerViewRect) { + bool visible = this->settings->playerViewRectEnabled + && mouseInMap + && this->editMode != EditMode::Connections; + + if (visible != this->playerViewRect->isVisible()) { + this->playerViewRect->setVisible(visible); + changed = true; + } + } + + if (this->cursorMapTileRect) { + auto editAction = getEditAction(); + bool visible = this->settings->cursorTileRectEnabled + && mouseInMap + && getEditingLayout() + // Only show the tile cursor for tools that apply at a specific tile + && editAction != EditAction::Select + && editAction != EditAction::Move; + + if (visible != this->cursorMapTileRect->isVisible()) { + this->cursorMapTileRect->setVisible(visible); + changed = true; + } + } + + // TODO: Investigate whether it'd be worth limiting the scene update to the old and new areas of the cursor rectangles. + if (ui->graphicsView_Map->scene() && changed) { ui->graphicsView_Map->scene()->update(); + } } -void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { - int x = pos.x(); - int y = pos.y(); - if (!layout || !layout->isWithinBounds(x, y)) +void Editor::onMapHoverEntered(const QPoint &pos) { + updateCursorRectVisibility(); + onMapHoverChanged(pos); +} + +void Editor::onMapHoverChanged(const QPoint &pos) { + this->setCursorRectPos(pos); + if (!layout || !layout->isWithinBounds(pos)) return; - this->updateCursorRectPos(x, y); - if (this->getEditingLayout()) { + int x = pos.x(); + int y = pos.y(); + if (this->editMode == EditMode::Metatiles) { int blockIndex = y * layout->getWidth() + x; int metatileId = layout->blockdata.at(blockIndex).metatileId(); this->ui->statusBar->showMessage(QString("X: %1, Y: %2, %3, Scale = %4x") @@ -1180,8 +1215,15 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { .arg(y) .arg(getMetatileDisplayMessage(metatileId)) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); - } - else if (this->editMode == EditMode::Events) { + } else if (this->editMode == EditMode::Collision) { + int blockIndex = y * layout->getWidth() + x; + uint16_t collision = layout->blockdata.at(blockIndex).collision(); + uint16_t elevation = layout->blockdata.at(blockIndex).elevation(); + this->ui->statusBar->showMessage(QString("X: %1, Y: %2, %3") + .arg(x) + .arg(y) + .arg(this->getMovementPermissionText(collision, elevation))); + } else if (this->editMode == EditMode::Events) { this->ui->statusBar->showMessage(QString("X: %1, Y: %2, Scale = %3x") .arg(x) .arg(y) @@ -1191,36 +1233,10 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { Scripting::cb_BlockHoverChanged(x, y); } -void Editor::onHoveredMapMetatileCleared() { - this->setCursorRectVisible(false); - if (map_item->getEditsEnabled()) { - this->ui->statusBar->clearMessage(); - } - Scripting::cb_BlockHoverCleared(); -} - -void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { - if (!layout || !layout->isWithinBounds(x, y)) - return; - - this->updateCursorRectPos(x, y); - if (this->getEditingLayout()) { - int blockIndex = y * layout->getWidth() + x; - uint16_t collision = layout->blockdata.at(blockIndex).collision(); - uint16_t elevation = layout->blockdata.at(blockIndex).elevation(); - QString message = QString("X: %1, Y: %2, %3") - .arg(x) - .arg(y) - .arg(this->getMovementPermissionText(collision, elevation)); - this->ui->statusBar->showMessage(message); - } - Scripting::cb_BlockHoverChanged(x, y); -} - -void Editor::onHoveredMapMovementPermissionCleared() { - this->setCursorRectVisible(false); - if (this->getEditingLayout()) { - this->ui->statusBar->clearMessage(); +void Editor::onMapHoverCleared() { + updateCursorRectVisibility(); + if (getEditingLayout()) { + ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); } @@ -1371,11 +1387,10 @@ void Editor::setSmartPathCursorMode(QGraphicsSceneMouseEvent *event) } } -void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { +void Editor::adjustStraightPathPos(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item, QPoint *pos) const { if (event->modifiers() & Qt::ControlModifier) { - this->cursorMapTileRect->setStraightPathMode(true); - } else { - this->cursorMapTileRect->setStraightPathMode(false); + item->lockNondominantAxis(event); + *pos = item->adjustCoords(*pos); } } @@ -1399,14 +1414,10 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } else { if (event->type() == QEvent::GraphicsSceneMouseRelease) { // Update the tile rectangle at the end of a click-drag selection - this->updateCursorRectPos(pos.x(), pos.y()); - } - this->setSmartPathCursorMode(event); - this->setStraightPathCursorMode(event); - if (this->cursorMapTileRect->getStraightPathMode()) { - item->lockNondominantAxis(event); - pos = item->adjustCoords(pos); + setCursorRectPos(pos); } + setSmartPathCursorMode(event); + adjustStraightPathPos(event, item, &pos); item->paint(event); } } else if (mapEditAction == EditAction::Select) { @@ -1426,11 +1437,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i item->pick(event); } } else if (mapEditAction == EditAction::Shift) { - this->setStraightPathCursorMode(event); - if (this->cursorMapTileRect->getStraightPathMode()) { - item->lockNondominantAxis(event); - pos = item->adjustCoords(pos); - } + adjustStraightPathPos(event, item, &pos); item->shift(event); } } else if (this->editMode == EditMode::Events) { @@ -1493,11 +1500,7 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm item->floodFill(event); } } else { - this->setStraightPathCursorMode(event); - if (this->cursorMapTileRect->getStraightPathMode()) { - item->lockNondominantAxis(event); - pos = item->adjustCoords(pos); - } + adjustStraightPathPos(event, item, &pos); item->paint(event); } } else if (mapEditAction == EditAction::Select) { @@ -1513,11 +1516,7 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } else if (mapEditAction == EditAction::Pick) { item->pick(event); } else if (mapEditAction == EditAction::Shift) { - this->setStraightPathCursorMode(event); - if (this->cursorMapTileRect->getStraightPathMode()) { - item->lockNondominantAxis(event); - pos = item->adjustCoords(pos); - } + adjustStraightPathPos(event, item, &pos); item->shift(event); } } @@ -1643,8 +1642,9 @@ void Editor::displayMapMetatiles() { connect(map_item, &LayoutPixmapItem::mouseEvent, this, &Editor::mouseEvent_map); connect(map_item, &LayoutPixmapItem::startPaint, this, &Editor::onMapStartPaint); connect(map_item, &LayoutPixmapItem::endPaint, this, &Editor::onMapEndPaint); - connect(map_item, &LayoutPixmapItem::hoveredMapMetatileChanged, this, &Editor::onHoveredMapMetatileChanged); - connect(map_item, &LayoutPixmapItem::hoveredMapMetatileCleared, this, &Editor::onHoveredMapMetatileCleared); + connect(map_item, &LayoutPixmapItem::hoverEntered, this, &Editor::onMapHoverEntered); + connect(map_item, &LayoutPixmapItem::hoverChanged, this, &Editor::onMapHoverChanged); + connect(map_item, &LayoutPixmapItem::hoverCleared, this, &Editor::onMapHoverCleared); map_item->draw(true); scene->addItem(map_item); @@ -1666,10 +1666,9 @@ void Editor::displayMapMovementPermissions() { collision_item = new CollisionPixmapItem(this->layout, ui->spinBox_SelectedCollision, ui->spinBox_SelectedElevation, this->metatile_selector_item, this->settings, &this->collisionOpacity); connect(collision_item, &CollisionPixmapItem::mouseEvent, this, &Editor::mouseEvent_collision); - connect(collision_item, &CollisionPixmapItem::hoveredMapMovementPermissionChanged, - this, &Editor::onHoveredMapMovementPermissionChanged); - connect(collision_item, &CollisionPixmapItem::hoveredMapMovementPermissionCleared, - this, &Editor::onHoveredMapMovementPermissionCleared); + connect(collision_item, &CollisionPixmapItem::hoverEntered, this, &Editor::onMapHoverEntered); + connect(collision_item, &CollisionPixmapItem::hoverChanged, this, &Editor::onMapHoverChanged); + connect(collision_item, &CollisionPixmapItem::hoverCleared, this, &Editor::onMapHoverCleared); collision_item->draw(true); scene->addItem(collision_item); @@ -2092,7 +2091,7 @@ void Editor::onEventDragged(Event *event, const QPoint &oldPosition, const QPoin if (!this->map || !this->map_item) return; - this->map_item->hoveredMapMetatileChanged(newPosition); + this->map_item->hoverChanged(newPosition); // Drag all the other selected events (if any) with it QList draggedEvents; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8259c315..a7b7d165 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2176,7 +2176,6 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) prefab.updatePrefabUi(this->editor->layout); } } - editor->setCursorRectVisible(false); } void MainWindow::on_mainTabBar_tabBarClicked(int index) @@ -2240,11 +2239,7 @@ void MainWindow::on_actionPlayer_View_Rectangle_triggered() bool enabled = ui->actionPlayer_View_Rectangle->isChecked(); porymapConfig.showPlayerView = enabled; this->editor->settings->playerViewRectEnabled = enabled; - if ((this->editor->map_item && this->editor->map_item->has_mouse) - || (this->editor->collision_item && this->editor->collision_item->has_mouse)) { - this->editor->playerViewRect->setVisible(enabled && this->editor->playerViewRect->getActive()); - ui->graphicsView_Map->scene()->update(); - } + this->editor->updateCursorRectVisibility(); } void MainWindow::on_actionCursor_Tile_Outline_triggered() @@ -2252,11 +2247,7 @@ void MainWindow::on_actionCursor_Tile_Outline_triggered() bool enabled = ui->actionCursor_Tile_Outline->isChecked(); porymapConfig.showCursorTile = enabled; this->editor->settings->cursorTileRectEnabled = enabled; - if ((this->editor->map_item && this->editor->map_item->has_mouse) - || (this->editor->collision_item && this->editor->collision_item->has_mouse)) { - this->editor->cursorMapTileRect->setVisible(enabled && this->editor->cursorMapTileRect->getActive()); - ui->graphicsView_Map->scene()->update(); - } + this->editor->updateCursorRectVisibility(); } void MainWindow::on_actionShow_Events_In_Map_View_triggered() { diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index e0587b08..7774ee16 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -6,7 +6,7 @@ void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->previousPos) { this->previousPos = pos; - emit this->hoveredMapMovementPermissionChanged(pos.x(), pos.y()); + emit this->hoverChanged(pos); } if (this->settings->betterCursors && this->getEditsEnabled()) { setCursor(this->settings->mapCursor); @@ -15,16 +15,16 @@ void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { this->has_mouse = true; - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - emit this->hoveredMapMovementPermissionChanged(pos.x(), pos.y()); + this->previousPos = Metatile::coordFromPixmapCoord(event->pos()); + emit this->hoverEntered(this->previousPos); } void CollisionPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { - emit this->hoveredMapMovementPermissionCleared(); if (this->settings->betterCursors && this->getEditsEnabled()){ unsetCursor(); } this->has_mouse = false; + emit this->hoverCleared(); } void CollisionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { @@ -38,7 +38,7 @@ void CollisionPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->previousPos) { this->previousPos = pos; - emit this->hoveredMapMovementPermissionChanged(pos.x(), pos.y()); + emit this->hoverChanged(pos); } emit mouseEvent(event, this); } diff --git a/src/ui/cursortilerect.cpp b/src/ui/cursortilerect.cpp index 511f0fda..ef1de285 100644 --- a/src/ui/cursortilerect.cpp +++ b/src/ui/cursortilerect.cpp @@ -1,94 +1,70 @@ #include "cursortilerect.h" #include "log.h" -CursorTileRect::CursorTileRect(bool *enabled, QRgb color) -{ - this->enabled = enabled; - this->active = true; - this->color = color; - this->width = 16; - this->height = 16; - this->smartPathMode = false; - this->straightPathMode = false; - this->singleTileMode = false; - this->anchored = false; - this->rightClickSelectionAnchored = false; - this->anchorCoordX = 0; - this->anchorCoordY = 0; - this->selectionWidth = 1; - this->selectionHeight = 1; +CursorTileRect::CursorTileRect(const QSize &tileSize, const QRgb &color, QGraphicsItem *parent) + : QGraphicsItem(parent), + m_tileSize(tileSize), + m_selectionSize(QSize(1,1)), + m_anchorCoord(QPoint(0,0)), + m_color(color) +{ } + +// Size of the cursor may be explicitly enforced depending on settings. +QSize CursorTileRect::size() const { + if (m_singleTileMode) + return m_tileSize; + + if (smartPathInEffect()) + return m_tileSize * 2; + + return QSize(m_tileSize.width() * m_selectionSize.width(), + m_tileSize.height() * m_selectionSize.height()); } -void CursorTileRect::setActive(bool active) -{ - this->active = active; +void CursorTileRect::initAnchor(int coordX, int coordY) { + m_anchorCoord = QPoint(coordX, coordY); + m_anchored = true; } -bool CursorTileRect::getActive() -{ - return active; +void CursorTileRect::stopAnchor() { + m_anchored = false; } -void CursorTileRect::initAnchor(int coordX, int coordY) -{ - this->anchorCoordX = coordX; - this->anchorCoordY = coordY; - this->anchored = true; +void CursorTileRect::initRightClickSelectionAnchor(int coordX, int coordY) { + m_anchorCoord = QPoint(coordX, coordY); + m_rightClickSelectionAnchored = true; } -void CursorTileRect::stopAnchor() -{ - this->anchored = false; +void CursorTileRect::stopRightClickSelectionAnchor() { + m_rightClickSelectionAnchored = false; } -void CursorTileRect::initRightClickSelectionAnchor(int coordX, int coordY) -{ - this->anchorCoordX = coordX; - this->anchorCoordY = coordY; - this->rightClickSelectionAnchored = true; +void CursorTileRect::updateSelectionSize(int width, int height) { + m_selectionSize = QSize(width, height).expandedTo(QSize(1,1)); + prepareGeometryChange(); + update(); } -void CursorTileRect::stopRightClickSelectionAnchor() -{ - this->rightClickSelectionAnchored = false; +bool CursorTileRect::smartPathInEffect() const { + return !m_rightClickSelectionAnchored && m_smartPathMode && m_selectionSize == QSize(3,3); } -void CursorTileRect::updateSelectionSize(int width, int height) -{ - this->selectionWidth = width; - this->selectionHeight = height; - this->width = width * 16; - this->height = height * 16; - this->prepareGeometryChange(); - this->update(); -} +void CursorTileRect::updateLocation(int coordX, int coordY) { + if (!m_singleTileMode) { + if (m_rightClickSelectionAnchored) { + coordX = qMin(coordX, m_anchorCoord.x()); + coordY = qMin(coordY, m_anchorCoord.y()); + } else if (m_anchored && !smartPathInEffect()) { + int xDiff = coordX - m_anchorCoord.x(); + int yDiff = coordY - m_anchorCoord.y(); + if (xDiff < 0 && xDiff % m_selectionSize.width() != 0) xDiff -= m_selectionSize.width(); + if (yDiff < 0 && yDiff % m_selectionSize.height() != 0) yDiff -= m_selectionSize.height(); -bool CursorTileRect::smartPathInEffect() -{ - return !this->rightClickSelectionAnchored && this->smartPathMode && this->selectionHeight == 3 && this->selectionWidth == 3; -} - -void CursorTileRect::updateLocation(int coordX, int coordY) -{ - if (!this->singleTileMode) { - if (this->rightClickSelectionAnchored) { - coordX = qMin(coordX, this->anchorCoordX); - coordY = qMin(coordY, this->anchorCoordY); - } - else if (this->anchored && !this->smartPathInEffect()) { - int xDiff = coordX - this->anchorCoordX; - int yDiff = coordY - this->anchorCoordY; - if (xDiff < 0 && xDiff % this->selectionWidth != 0) xDiff -= this->selectionWidth; - if (yDiff < 0 && yDiff % this->selectionHeight != 0) yDiff -= this->selectionHeight; - - coordX = this->anchorCoordX + (xDiff / this->selectionWidth) * this->selectionWidth; - coordY = this->anchorCoordY + (yDiff / this->selectionHeight) * this->selectionHeight; + coordX = m_anchorCoord.x() + (xDiff / m_selectionSize.width()) * m_selectionSize.width(); + coordY = m_anchorCoord.y() + (yDiff / m_selectionSize.height()) * m_selectionSize.height(); } } - coordX = qMax(coordX, 0); - coordY = qMax(coordY, 0); this->setX(coordX * 16); this->setY(coordY * 16); - this->setVisible(*this->enabled && this->active); } diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index 2fb89171..915cfad7 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -695,7 +695,7 @@ void LayoutPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->metatilePos) { this->metatilePos = pos; - emit this->hoveredMapMetatileChanged(pos); + emit this->hoverChanged(pos); } if (this->settings->betterCursors && this->editsEnabled) { setCursor(this->settings->mapCursor); @@ -704,16 +704,16 @@ void LayoutPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { void LayoutPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { this->has_mouse = true; - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - emit this->hoveredMapMetatileChanged(pos); + this->metatilePos = Metatile::coordFromPixmapCoord(event->pos()); + emit this->hoverEntered(this->metatilePos); } void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { - emit this->hoveredMapMetatileCleared(); if (this->settings->betterCursors && this->editsEnabled) { unsetCursor(); } this->has_mouse = false; + emit this->hoverCleared(); } void LayoutPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { @@ -730,7 +730,7 @@ void LayoutPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { return; this->metatilePos = pos; - emit hoveredMapMetatileChanged(pos); + emit hoverChanged(pos); emit mouseEvent(event, this); } diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index e02222df..3fc73934 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -5,14 +5,11 @@ #include "movablerect.h" #include "utility.h" -MovableRect::MovableRect(bool *enabled, const QRectF &rect, const QRgb &color) +MovableRect::MovableRect(const QRectF &rect, const QRgb &color) : QGraphicsRectItem(rect), - enabled(enabled), baseRect(rect), color(color) -{ - updateVisibility(); -} +{ } /// Center rect on grid position (x, y) void MovableRect::updateLocation(int x, int y) { @@ -20,28 +17,19 @@ void MovableRect::updateLocation(int x, int y) { this->baseRect.y() + (y * 16), this->baseRect.width(), this->baseRect.height()); - updateVisibility(); } -void MovableRect::setActive(bool active) { - this->active = active; - updateVisibility(); -} - -void MovableRect::updateVisibility() { - setVisible(*this->enabled && this->active); -} /****************************************************************************** ************************************************************************ ******************************************************************************/ -ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) +ResizableRect::ResizableRect(QObject *parent, int width, int height, QRgb color) : QObject(parent), - MovableRect(enabled, QRect(0, 0, width * 16, height * 16), color) + MovableRect(QRect(0, 0, width * 16, height * 16), color) { - setAcceptHoverEvents(true); - setFlags(this->flags() | QGraphicsItem::ItemIsMovable); + setAcceptHoverEvents(true); + setFlags(this->flags() | QGraphicsItem::ItemIsMovable); } ResizableRect::Edge ResizableRect::detectEdge(int x, int y) { diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 189ba4af..7f11836f 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -136,9 +136,7 @@ void ResizeLayoutPopup::setupLayoutView() { this->ui->spinBox_height->setMinimum(1); this->ui->spinBox_height->setMaximum(maxHeight); - static bool layoutSizeRectVisible = true; - - this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->layout->getWidth(), this->layout->getHeight(), qRgb(255, 0, 255)); + this->outline = new ResizableRect(this, this->layout->getWidth(), this->layout->getHeight(), qRgb(255, 0, 255)); this->outline->setZValue(Editor::ZValue::ResizeLayoutPopup); // Ensure on top of view this->outline->setLimit(cover->rect().toAlignedRect()); connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ From c56bac4f9586bbfb4dc1b35a65717c50f8e7d257 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Jun 2025 22:02:14 -0400 Subject: [PATCH 04/71] Fix Map::m_scriptFileWatcher being created before its needed --- CHANGELOG.md | 1 + src/core/map.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6416511..8aa05f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ## [Unreleased] ### Fixed - Fix warning not appearing when the log file exceeds maximum size. +- Fix unnecessary resources being used to watch files. ## [6.1.0] - 2025-06-09 ### Added diff --git a/src/core/map.cpp b/src/core/map.cpp index 6ced8d4c..ceb60f33 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -15,9 +15,6 @@ Map::Map(QObject *parent) : QObject(parent) { m_editHistory = new QUndoStack(this); - m_scriptFileWatcher = new QFileSystemWatcher(this); - connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); - resetEvents(); m_header = new MapHeader(this); @@ -143,7 +140,7 @@ void Map::setSharedScriptsMap(const QString &sharedScriptsMap) { void Map::invalidateScripts() { m_scriptsLoaded = false; - m_scriptFileWatcher->removePaths(m_scriptFileWatcher->files()); + delete m_scriptFileWatcher; emit scriptsModified(); } @@ -161,6 +158,12 @@ QStringList Map::getScriptLabels(Event::Group group) { m_loggedScriptsFileError = true; } + if (!m_scriptFileWatcher) { + // Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources). + // The other option would be for Porymap to have a single global QFileSystemWatcher, but that has complications of its own. + m_scriptFileWatcher = new QFileSystemWatcher(this); + connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); + } if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath) && !m_loggedScriptsFileError) { logWarn(QString("Failed to add scripts file '%1' to file watcher for %2.") .arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/")) From 13c6c90e02682f543c9bbed8dc1cdafeca0960f8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Jun 2025 22:12:21 -0400 Subject: [PATCH 05/71] Extra pointer safety --- src/core/map.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/map.cpp b/src/core/map.cpp index ceb60f33..54d48c7b 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -140,7 +140,12 @@ void Map::setSharedScriptsMap(const QString &sharedScriptsMap) { void Map::invalidateScripts() { m_scriptsLoaded = false; + + // m_scriptFileWatcher is a QPointer so clearing it shouldn't be necessary, + // but it's possible that Map::getScriptLabels will be called before events are processed. delete m_scriptFileWatcher; + m_scriptFileWatcher = nullptr; + emit scriptsModified(); } From dbc13484b125335dbda71e15af3d99bc1eb2d7c7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Jun 2025 15:53:02 -0400 Subject: [PATCH 06/71] Disable Windows build job --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 397994a3..cd96bfc6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,6 +87,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-static-windows: + if: false # Windows job is currently disabled; it's failing to find required modules in the Qt build runs-on: windows-latest env: BUILD_NAME: porymap-windows From 573358b7581a7d10e2fd15567ac6495c6e268489 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 18 Jun 2025 14:20:19 -0400 Subject: [PATCH 07/71] Condense file watcher warning, reduce resource usage --- include/project.h | 4 ++- include/ui/preferenceeditor.h | 1 + src/core/map.cpp | 24 +++++++------ src/mainwindow.cpp | 1 + src/project.cpp | 67 +++++++++++++++++++++++++++-------- src/ui/preferenceeditor.cpp | 23 +++++++++--- 6 files changed, 89 insertions(+), 31 deletions(-) diff --git a/include/project.h b/include/project.h index 5c519bc3..ca7d9bbe 100644 --- a/include/project.h +++ b/include/project.h @@ -61,7 +61,6 @@ public: QMap metatileBehaviorMap; QMap metatileBehaviorMapInverse; ParseUtil parser; - QFileSystemWatcher fileWatcher; QSet modifiedFiles; bool usingAsmTilesets; QSet disabledSettingsNames; @@ -263,6 +262,7 @@ public: static QString getMapGroupPrefix(); private: + QPointer fileWatcher; QMap modifiedFileTimestamps; QMap facingDirections; QHash speciesToIconPath; @@ -332,6 +332,8 @@ private: void ignoreWatchedFilesTemporarily(const QStringList &filepaths); void recordFileChange(const QString &filepath); void resetFileCache(); + void resetFileWatcher(); + void logFileWatchStatus(); bool saveMapLayouts(); bool saveMapGroups(); diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h index 64647f0e..74181c37 100644 --- a/include/ui/preferenceeditor.h +++ b/include/ui/preferenceeditor.h @@ -24,6 +24,7 @@ signals: void preferencesSaved(); void themeChanged(const QString &theme); void scriptSettingsChanged(bool on); + void reloadProjectRequested(); private: Ui::PreferenceEditor *ui; diff --git a/src/core/map.cpp b/src/core/map.cpp index 54d48c7b..6110a26f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -163,17 +163,19 @@ QStringList Map::getScriptLabels(Event::Group group) { m_loggedScriptsFileError = true; } - if (!m_scriptFileWatcher) { - // Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources). - // The other option would be for Porymap to have a single global QFileSystemWatcher, but that has complications of its own. - m_scriptFileWatcher = new QFileSystemWatcher(this); - connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); - } - if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath) && !m_loggedScriptsFileError) { - logWarn(QString("Failed to add scripts file '%1' to file watcher for %2.") - .arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/")) - .arg(m_name)); - m_loggedScriptsFileError = true; + if (porymapConfig.monitorFiles) { + if (!m_scriptFileWatcher) { + // Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources). + // The other option would be for Porymap to have a single global QFileSystemWatcher, but that has complications of its own. + m_scriptFileWatcher = new QFileSystemWatcher(this); + connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); + } + if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath) && !m_loggedScriptsFileError) { + logWarn(QString("Failed to add scripts file '%1' to file watcher for %2.") + .arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/")) + .arg(m_name)); + m_loggedScriptsFileError = true; + } } m_scriptsLoaded = true; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8259c315..6fa4d4cc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2938,6 +2938,7 @@ void MainWindow::on_actionPreferences_triggered() { // require us to repopulate the EventFrames and redraw event pixmaps, respectively. connect(preferenceEditor, &PreferenceEditor::preferencesSaved, editor, &Editor::updateEvents); connect(preferenceEditor, &PreferenceEditor::scriptSettingsChanged, editor->project, &Project::readEventScriptLabels); + connect(preferenceEditor, &PreferenceEditor::reloadProjectRequested, this, &MainWindow::on_action_Reload_Project_triggered); } openSubWindow(preferenceEditor); diff --git a/src/project.cpp b/src/project.cpp index 1fab6300..99ad5583 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -32,9 +32,7 @@ int Project::num_pals_total = 13; Project::Project(QObject *parent) : QObject(parent) -{ - QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::recordFileChange); -} +{ } Project::~Project() { @@ -186,6 +184,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) { bool Project::load() { this->parser.setUpdatesSplashScreen(true); + resetFileWatcher(); resetFileCache(); this->disabledSettingsNames.clear(); bool success = readGlobalConstants() @@ -225,6 +224,7 @@ bool Project::load() { initNewLayoutSettings(); initNewMapSettings(); applyParsedLimits(); + logFileWatchStatus(); } this->parser.setUpdatesSplashScreen(false); return success; @@ -232,7 +232,6 @@ bool Project::load() { void Project::resetFileCache() { this->parser.clearFileCache(); - this->failedFileWatchPaths.clear(); const QSet filepaths = { // Whenever we load a tileset we'll need to parse some data from these files, so we cache them to avoid the overhead of opening the files. @@ -749,16 +748,21 @@ bool Project::saveMapLayouts() { } bool Project::watchFile(const QString &filename) { + if (!porymapConfig.monitorFiles) + return true; + + if (!this->fileWatcher) { + // Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources). + this->fileWatcher = new QFileSystemWatcher(this); + QObject::connect(this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::recordFileChange); + } + QString filepath = filename.startsWith(this->root) ? filename : QString("%1/%2").arg(this->root).arg(filename); - if (!this->fileWatcher.addPath(filepath) && !this->fileWatcher.files().contains(filepath)) { + if (!this->fileWatcher->addPath(filepath) && !this->fileWatcher->files().contains(filepath)) { // We failed to watch the file, and this wasn't a file we were already watching. - // Log a warning, but only if A. we actually care that we failed, because 'monitor files' is enabled, - // B. we haven't logged a warning for this file yet, and C. we would have otherwise been able to watch it, because the file exists. - if (porymapConfig.monitorFiles && !this->failedFileWatchPaths.contains(filepath) && QFileInfo::exists(filepath)) { + // Record the filepath for logging later, assuming we should have been able to watch the file. + if (QFileInfo::exists(filepath)) { this->failedFileWatchPaths.insert(filepath); - logWarn(QString("Failed to add '%1' to file watcher. Currently watching %2 files.") - .arg(Util::stripPrefix(filepath, this->root)) - .arg(this->fileWatcher.files().length())); } return false; } @@ -774,8 +778,11 @@ bool Project::watchFiles(const QStringList &filenames) { } bool Project::stopFileWatch(const QString &filename) { + if (!this->fileWatcher) + return true; + QString filepath = filename.startsWith(this->root) ? filename : QString("%1/%2").arg(this->root).arg(filename); - return this->fileWatcher.removePath(filepath); + return this->fileWatcher->removePath(filepath); } void Project::ignoreWatchedFileTemporarily(const QString &filepath) { @@ -794,8 +801,8 @@ void Project::recordFileChange(const QString &filepath) { // Note: As a safety measure, many applications save an open file by writing a new file and then deleting the old one. // In your slot function, you can check watcher.files().contains(path). // If it returns false, check whether the file still exists and then call addPath() to continue watching it. - if (!this->fileWatcher.files().contains(filepath) && QFileInfo::exists(filepath)) { - this->fileWatcher.addPath(filepath); + if (this->fileWatcher && !this->fileWatcher->files().contains(filepath) && QFileInfo::exists(filepath)) { + this->fileWatcher->addPath(filepath); } if (this->modifiedFiles.contains(filepath)) { @@ -815,6 +822,38 @@ void Project::recordFileChange(const QString &filepath) { emit fileChanged(filepath); } +// When calling 'watchFile' we record failures rather than log them immediately. +// We do this primarily to condense the warning if we fail to monitor any files. +void Project::logFileWatchStatus() { + if (!this->fileWatcher) + return; + + int numSuccessful = this->fileWatcher->files().length(); + int numAttempted = numSuccessful + this->failedFileWatchPaths.count(); + if (numAttempted == 0) + return; + + if (numSuccessful == 0) { + // We failed to watch every file we tried. As of writing this happens if Porymap is running + // on Windows and the project files are in WSL2. Rather than filling the log by + // outputting a warning for every file, just log that we failed to monitor any of them. + logWarn(QString("Failed to monitor project files")); + return; + } else { + logInfo(QString("Successfully monitoring %1/%2 project files").arg(numSuccessful).arg(numAttempted)); + } + + for (const auto &failedPath : this->failedFileWatchPaths) { + logWarn(QString("Failed to monitor project file '%1'").arg(failedPath)); + } +} + +void Project::resetFileWatcher() { + this->failedFileWatchPaths.clear(); + delete this->fileWatcher; + this->fileWatcher = nullptr; +} + bool Project::saveMapGroups() { QString mapGroupsFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups)); QFile mapGroupsFile(mapGroupsFilepath); diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index bda0ec98..4f4b0929 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -2,6 +2,7 @@ #include "ui_preferenceeditor.h" #include "config.h" #include "noscrollcombobox.h" +#include "message.h" #include #include @@ -87,6 +88,8 @@ void PreferenceEditor::updateFields() { } void PreferenceEditor::saveFields() { + bool needsProjectReload = false; + bool changedTheme = false; if (themeSelector->currentText() != porymapConfig.theme) { porymapConfig.theme = themeSelector->currentText(); @@ -100,7 +103,6 @@ void PreferenceEditor::saveFields() { porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape; porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text(); porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text(); - porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked(); porymapConfig.reopenOnLaunch = ui->checkBox_OpenRecentProject->isChecked(); porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked(); porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked(); @@ -110,6 +112,11 @@ void PreferenceEditor::saveFields() { if (ui->checkBox_StatusWarnings->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_WARN); if (ui->checkBox_StatusInformation->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_INFO); + if (porymapConfig.monitorFiles != ui->checkBox_MonitorProjectFiles->isChecked()) { + porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked(); + needsProjectReload = true; + } + if (porymapConfig.applicationFont != this->applicationFont) { porymapConfig.applicationFont = this->applicationFont; changedTheme = true; @@ -119,13 +126,19 @@ void PreferenceEditor::saveFields() { changedTheme = true; } - porymapConfig.save(); - - emit preferencesSaved(); - if (changedTheme) { emit themeChanged(porymapConfig.theme); } + + porymapConfig.save(); + emit preferencesSaved(); + + if (needsProjectReload) { + auto message = QStringLiteral("Some changes will only take effect after reloading the project. Reload the project now?"); + if (QuestionMessage::show(message, this) == QMessageBox::Yes) { + emit reloadProjectRequested(); + } + } } void PreferenceEditor::dialogButtonClicked(QAbstractButton *button) { From 440c0f9d5c106e64cbdd0cada540b6c1210389fd Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 20 Jun 2025 12:59:09 -0400 Subject: [PATCH 08/71] Better pan control --- src/editor.cpp | 40 ++++++++++++++++++--------------- src/mainwindow.cpp | 15 ++++--------- src/ui/connectionpixmapitem.cpp | 6 ++++- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 0d4f7482..150b21bc 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -210,10 +210,10 @@ void Editor::setEditAction(EditAction editAction) { // Update cursor static const QMap cursors = { {EditAction::Paint, QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10)}, - {EditAction::Select, QCursor()}, + {EditAction::Select, QCursor(Qt::ArrowCursor)}, {EditAction::Fill, QCursor(QPixmap(":/icons/fill_color_cursor.ico"), 10, 10)}, {EditAction::Pick, QCursor(QPixmap(":/icons/pipette_cursor.ico"), 10, 10)}, - {EditAction::Move, QCursor(QPixmap(":/icons/move.ico"), 7, 7)}, + {EditAction::Move, QCursor(Qt::OpenHandCursor)}, {EditAction::Shift, QCursor(QPixmap(":/icons/shift_cursor.ico"), 10, 10)}, }; this->settings->mapCursor = cursors.value(editAction); @@ -1354,7 +1354,7 @@ void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem * } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (event->buttons() & Qt::RightButton && (mapEditAction == EditAction::Paint || mapEditAction == EditAction::Fill)) { + if (event->buttons() & Qt::RightButton && (this->mapEditAction == EditAction::Paint || this->mapEditAction == EditAction::Fill)) { this->cursorMapTileRect->initRightClickSelectionAnchor(pos.x(), pos.y()); } else { this->cursorMapTileRect->initAnchor(pos.x(), pos.y()); @@ -1395,14 +1395,16 @@ void Editor::adjustStraightPathPos(QGraphicsSceneMouseEvent *event, LayoutPixmap } void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { - if (!item->getEditsEnabled()) { + auto editAction = getEditAction(); + if (!item->getEditsEnabled() || editAction == EditAction::Move) { + event->ignore(); return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (this->getEditingLayout()) { - if (mapEditAction == EditAction::Paint) { + if (editAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->buttons() & Qt::MiddleButton) { @@ -1420,9 +1422,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i adjustStraightPathPos(event, item, &pos); item->paint(event); } - } else if (mapEditAction == EditAction::Select) { + } else if (editAction == EditAction::Select) { item->select(event); - } else if (mapEditAction == EditAction::Fill) { + } else if (editAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->modifiers() & Qt::ControlModifier) { @@ -1430,18 +1432,18 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } else { item->floodFill(event); } - } else if (mapEditAction == EditAction::Pick) { + } else if (editAction == EditAction::Pick) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->type() != QEvent::GraphicsSceneMouseRelease) { item->pick(event); } - } else if (mapEditAction == EditAction::Shift) { + } else if (editAction == EditAction::Shift) { adjustStraightPathPos(event, item, &pos); item->shift(event); } } else if (this->editMode == EditMode::Events) { - if (eventEditAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { + if (editAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { // Right-clicking while in paint mode will change mode to select. if (event->buttons() & Qt::RightButton) { setEditAction(EditAction::Select); @@ -1456,12 +1458,12 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i if (event && event->getPixmapItem()) event->getPixmapItem()->moveTo(pos); } - } else if (eventEditAction == EditAction::Select && event->type() == QEvent::GraphicsSceneMousePress) { + } else if (editAction == EditAction::Select && event->type() == QEvent::GraphicsSceneMousePress) { if (!(event->modifiers() & Qt::ControlModifier) && this->selectedEvents.length() > 1) { // User is clearing group selection by clicking on the background selectMapEvent(this->selectedEvents.first()); } - } else if (eventEditAction == EditAction::Shift) { + } else if (editAction == EditAction::Shift) { static QPoint selection_origin; if (event->type() == QEvent::GraphicsSceneMouseRelease) { @@ -1484,13 +1486,15 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item) { - if (!item->getEditsEnabled()) { + auto editAction = getEditAction(); + if (!item->getEditsEnabled() || editAction == EditAction::Move) { + event->ignore(); return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (mapEditAction == EditAction::Paint) { + if (editAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMovementPermissionSelection(event); } else if (event->buttons() & Qt::MiddleButton) { @@ -1503,9 +1507,9 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm adjustStraightPathPos(event, item, &pos); item->paint(event); } - } else if (mapEditAction == EditAction::Select) { + } else if (editAction == EditAction::Select) { item->select(event); - } else if (mapEditAction == EditAction::Fill) { + } else if (editAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { item->pick(event); } else if (event->modifiers() & Qt::ControlModifier) { @@ -1513,9 +1517,9 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } else { item->floodFill(event); } - } else if (mapEditAction == EditAction::Pick) { + } else if (editAction == EditAction::Pick) { item->pick(event); - } else if (mapEditAction == EditAction::Shift) { + } else if (editAction == EditAction::Shift) { adjustStraightPathPos(event, item, &pos); item->shift(event); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a7b7d165..2f56efdc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2579,17 +2579,10 @@ void MainWindow::on_toolButton_Move_clicked() { editor->setEditAction(Editor: void MainWindow::on_toolButton_Shift_clicked() { editor->setEditAction(Editor::EditAction::Shift); } void MainWindow::setEditActionUi(Editor::EditAction editAction) { - if (editAction == Editor::EditAction::Move) { - ui->graphicsView_Map->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui->graphicsView_Map->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - QScroller::grabGesture(ui->graphicsView_Map, QScroller::LeftMouseButtonGesture); - ui->graphicsView_Map->setViewportUpdateMode(QGraphicsView::ViewportUpdateMode::FullViewportUpdate); - } else { - ui->graphicsView_Map->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->graphicsView_Map->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - QScroller::ungrabGesture(ui->graphicsView_Map); - ui->graphicsView_Map->setViewportUpdateMode(QGraphicsView::ViewportUpdateMode::MinimalViewportUpdate); - } + // TODO: Viewport's hand cursor is not reliably cleared when changing to QGraphicsView::NoDrag. + auto dragMode = (editAction == Editor::EditAction::Move) ? QGraphicsView::ScrollHandDrag : QGraphicsView::NoDrag; + ui->graphicsView_Connections->setDragMode(dragMode); + ui->graphicsView_Map->setDragMode(dragMode); ui->graphicsView_Map->setFocus(); ui->toolButton_Paint->setChecked(editAction == Editor::EditAction::Paint); diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index c6e606b0..b4fbfa12 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -126,7 +126,11 @@ void ConnectionPixmapItem::setSelected(bool selected) { emit selectionChanged(selected); } -void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { +void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if (!this->getEditable()) { + event->ignore(); + return; + } this->setSelected(true); } From 1ab07cf3c76dd86a9a2adf74cdd14cbf7733af59 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 21 Jun 2025 21:33:52 -0400 Subject: [PATCH 09/71] Fix drag mode cursor icon not updating properly --- include/settings.h | 1 - include/ui/layoutpixmapitem.h | 5 ---- src/editor.cpp | 44 ++++++++++++++++++++++++---------- src/mainwindow.cpp | 6 ----- src/ui/collisionpixmapitem.cpp | 6 ----- src/ui/layoutpixmapitem.cpp | 8 +------ 6 files changed, 32 insertions(+), 38 deletions(-) diff --git a/include/settings.h b/include/settings.h index d37283cd..6e64fd2a 100644 --- a/include/settings.h +++ b/include/settings.h @@ -10,7 +10,6 @@ public: Settings(); bool smartPathsEnabled; bool betterCursors; - QCursor mapCursor; bool playerViewRectEnabled; bool cursorTileRectEnabled; }; diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h index bed0ce8b..51f850d9 100644 --- a/include/ui/layoutpixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -86,9 +86,6 @@ public: void lockNondominantAxis(QGraphicsSceneMouseEvent *event); QPoint adjustCoords(QPoint pos); - void setEditsEnabled(bool enabled) { this->editsEnabled = enabled; } - bool getEditsEnabled() { return this->editsEnabled; } - private: void paintSmartPath(int x, int y, bool fromScriptCall = false); static QList smartPathTable; @@ -96,8 +93,6 @@ private: unsigned actionId_ = 0; - bool editsEnabled = true; - signals: void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); diff --git a/src/editor.cpp b/src/editor.cpp index 150b21bc..b7e73b09 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -155,7 +155,6 @@ void Editor::setEditMode(EditMode editMode) { break; } - map_item->setEditsEnabled(this->editMode != EditMode::Connections); map_item->draw(); collision_item->draw(); @@ -207,16 +206,35 @@ void Editor::setEditAction(EditAction editAction) { // The tile cursor can only grow while painting metatiles this->cursorMapTileRect->setSingleTileMode(!(editAction == EditAction::Paint && this->editMode == EditMode::Metatiles)); + auto dragMode = (editAction == EditAction::Move) ? QGraphicsView::ScrollHandDrag : QGraphicsView::NoDrag; + ui->graphicsView_Map->setDragMode(dragMode); + ui->graphicsView_Connections->setDragMode(dragMode); + // Update cursor - static const QMap cursors = { - {EditAction::Paint, QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10)}, - {EditAction::Select, QCursor(Qt::ArrowCursor)}, - {EditAction::Fill, QCursor(QPixmap(":/icons/fill_color_cursor.ico"), 10, 10)}, - {EditAction::Pick, QCursor(QPixmap(":/icons/pipette_cursor.ico"), 10, 10)}, - {EditAction::Move, QCursor(Qt::OpenHandCursor)}, - {EditAction::Shift, QCursor(QPixmap(":/icons/shift_cursor.ico"), 10, 10)}, - }; - this->settings->mapCursor = cursors.value(editAction); + if (this->settings->betterCursors) { + static const QMap cursors = { + {EditAction::Paint, QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10)}, + {EditAction::Fill, QCursor(QPixmap(":/icons/fill_color_cursor.ico"), 10, 10)}, + {EditAction::Pick, QCursor(QPixmap(":/icons/pipette_cursor.ico"), 10, 10)}, + {EditAction::Shift, QCursor(QPixmap(":/icons/shift_cursor.ico"), 10, 10)}, + }; + + // Paint tools don't apply on the Connections tab, so don't show the cursor. + // We specifically unset the cursor for Move rather than explicitly set Qt::OpenHandCursor + // because otherwise the cursor may persist outside the map after the tool changes. + if (this->editMode == EditMode::Connections || editAction == EditAction::Move) { + if (this->map_item) + this->map_item->unsetCursor(); + if (this->collision_item) + this->collision_item->unsetCursor(); + } else { + auto cursor = cursors.value(editAction); + if (this->map_item) + this->map_item->setCursor(cursor); + if (this->collision_item) + this->collision_item->setCursor(cursor); + } + } emit editActionSet(editAction); } @@ -1396,14 +1414,14 @@ void Editor::adjustStraightPathPos(QGraphicsSceneMouseEvent *event, LayoutPixmap void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { auto editAction = getEditAction(); - if (!item->getEditsEnabled() || editAction == EditAction::Move) { + if (editAction == EditAction::Move) { event->ignore(); return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (this->getEditingLayout()) { + if (this->editMode == EditMode::Metatiles) { if (editAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); @@ -1487,7 +1505,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item) { auto editAction = getEditAction(); - if (!item->getEditsEnabled() || editAction == EditAction::Move) { + if (this->editMode != EditMode::Collision || editAction == EditAction::Move) { event->ignore(); return; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2f56efdc..7622b8b2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2579,12 +2579,6 @@ void MainWindow::on_toolButton_Move_clicked() { editor->setEditAction(Editor: void MainWindow::on_toolButton_Shift_clicked() { editor->setEditAction(Editor::EditAction::Shift); } void MainWindow::setEditActionUi(Editor::EditAction editAction) { - // TODO: Viewport's hand cursor is not reliably cleared when changing to QGraphicsView::NoDrag. - auto dragMode = (editAction == Editor::EditAction::Move) ? QGraphicsView::ScrollHandDrag : QGraphicsView::NoDrag; - ui->graphicsView_Connections->setDragMode(dragMode); - ui->graphicsView_Map->setDragMode(dragMode); - ui->graphicsView_Map->setFocus(); - ui->toolButton_Paint->setChecked(editAction == Editor::EditAction::Paint); ui->toolButton_Select->setChecked(editAction == Editor::EditAction::Select); ui->toolButton_Fill->setChecked(editAction == Editor::EditAction::Fill); diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index 7774ee16..9fcfb7de 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -8,9 +8,6 @@ void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { this->previousPos = pos; emit this->hoverChanged(pos); } - if (this->settings->betterCursors && this->getEditsEnabled()) { - setCursor(this->settings->mapCursor); - } } void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { @@ -20,9 +17,6 @@ void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { } void CollisionPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { - if (this->settings->betterCursors && this->getEditsEnabled()){ - unsetCursor(); - } this->has_mouse = false; emit this->hoverCleared(); } diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index 915cfad7..9f8a8c04 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -697,21 +697,15 @@ void LayoutPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { this->metatilePos = pos; emit this->hoverChanged(pos); } - if (this->settings->betterCursors && this->editsEnabled) { - setCursor(this->settings->mapCursor); - } } -void LayoutPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { +void LayoutPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { this->has_mouse = true; this->metatilePos = Metatile::coordFromPixmapCoord(event->pos()); emit this->hoverEntered(this->metatilePos); } void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { - if (this->settings->betterCursors && this->editsEnabled) { - unsetCursor(); - } this->has_mouse = false; emit this->hoverCleared(); } From f42e9ed3cf06d015bc959845c94cced9b7a338f1 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 21 Jun 2025 23:26:35 -0400 Subject: [PATCH 10/71] Fix Move tool affecting cursor anchor --- include/editor.h | 7 ++++--- src/editor.cpp | 10 +++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/editor.h b/include/editor.h index 10891e4c..414356d1 100644 --- a/include/editor.h +++ b/include/editor.h @@ -172,7 +172,7 @@ public: void setEditMode(EditMode editMode); EditMode getEditMode() const { return this->editMode; } - bool getEditingLayout(); + bool getEditingLayout() const; void setMapEditingButtonsEnabled(bool enabled); @@ -256,10 +256,11 @@ private: static bool startDetachedProcess(const QString &command, const QString &workingDirectory = QString(), qint64 *pid = nullptr); - -private slots: + bool canPaintMetatiles() const; void onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void onMapEndPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); + +private slots: void setSmartPathCursorMode(QGraphicsSceneMouseEvent *event); void mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); diff --git a/src/editor.cpp b/src/editor.cpp index b7e73b09..f64c4b40 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -125,7 +125,7 @@ void Editor::closeProject() { delete this->project; } -bool Editor::getEditingLayout() { +bool Editor::getEditingLayout() const { return this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision; } @@ -1366,8 +1366,12 @@ bool Editor::setLayout(QString layoutId) { return true; } +bool Editor::canPaintMetatiles() const { + return this->editMode == EditMode::Metatiles && this->mapEditAction != EditAction::Select && this->mapEditAction != EditAction::Move; +} + void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *) { - if (!this->getEditingLayout()) { + if (!canPaintMetatiles()) { return; } @@ -1380,7 +1384,7 @@ void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem * } void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *) { - if (!this->getEditingLayout()) { + if (!canPaintMetatiles()) { return; } this->cursorMapTileRect->stopRightClickSelectionAnchor(); From 37ccc82bf4dbb6c94546216c4c7775c6b7d7d7d8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 21 Jun 2025 23:51:37 -0400 Subject: [PATCH 11/71] Fix incorrect EditMode limits --- src/editor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index f64c4b40..4f41cddb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1196,7 +1196,6 @@ void Editor::updateCursorRectVisibility() { auto editAction = getEditAction(); bool visible = this->settings->cursorTileRectEnabled && mouseInMap - && getEditingLayout() // Only show the tile cursor for tools that apply at a specific tile && editAction != EditAction::Select && editAction != EditAction::Move; @@ -2224,7 +2223,7 @@ bool Editor::canAddEvents(const QList &events) { } void Editor::duplicateSelectedEvents() { - if (this->selectedEvents.isEmpty() || !project || !map || !current_view || this->getEditingLayout()) + if (this->selectedEvents.isEmpty() || !project || !map || !current_view || this->editMode != EditMode::Events) return; QList duplicatedEvents; From 0da6ae9b375f5a6598c95b55f9334b43ce6dab70 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 22 Jun 2025 00:20:16 -0400 Subject: [PATCH 12/71] Fix dragging events to negative coordinates --- src/core/metatile.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 073fd867..73ff9c86 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -38,6 +38,8 @@ int Metatile::getIndexInTileset(int metatileId) { QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { int x = static_cast(pixelCoord.x()) / 16; int y = static_cast(pixelCoord.y()) / 16; + if (pixelCoord.x() < 0) x--; + if (pixelCoord.y() < 0) y--; return QPoint(x, y); } From bbe34e4983f8f644d1ab4e7a5cdf0ad1699edd9e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 23 Jun 2025 15:27:19 -0400 Subject: [PATCH 13/71] Sync Connections tab map view position --- CHANGELOG.md | 3 +++ include/ui/graphicsview.h | 23 +++++++++++++++++++---- include/ui/mapview.h | 6 +++--- src/editor.cpp | 14 ++++++++++++-- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aa05f7b..47ac8dc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project somewhat adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The MAJOR version number is bumped when there are **"Breaking Changes"** in the pret projects. For more on this, see [the manual page on breaking changes](https://huderlem.github.io/porymap/manual/breaking-changes.html). ## [Unreleased] +### Changed +- The scroll position of the map view now remains the same between the Connections tab and the Map/Events tabs. + ### Fixed - Fix warning not appearing when the log file exceeds maximum size. - Fix unnecessary resources being used to watch files. diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index c587d2a9..bf3a7e42 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -4,11 +4,26 @@ #include #include -class NoScrollGraphicsView : public QGraphicsView +// For general utility features that we add to QGraphicsView +class GraphicsView : public QGraphicsView { Q_OBJECT public: - NoScrollGraphicsView(QWidget *parent = nullptr) : QGraphicsView(parent) {} + GraphicsView(QWidget *parent = nullptr) : QGraphicsView(parent) {} + + void centerOn(const QGraphicsView *other) { + if (other && other->viewport()) { + QPoint center = other->viewport()->rect().center(); + QGraphicsView::centerOn(other->mapToScene(center)); + } + } +}; + +class NoScrollGraphicsView : public GraphicsView +{ + Q_OBJECT +public: + NoScrollGraphicsView(QWidget *parent = nullptr) : GraphicsView(parent) {} protected: void wheelEvent(QWheelEvent *event) { @@ -32,11 +47,11 @@ signals: void clicked(QMouseEvent *event); }; -class ConnectionsView : public QGraphicsView +class ConnectionsView : public GraphicsView { Q_OBJECT public: - ConnectionsView(QWidget *parent = nullptr) : QGraphicsView(parent) {} + ConnectionsView(QWidget *parent = nullptr) : GraphicsView(parent) {} signals: void pressedDelete(); diff --git a/include/ui/mapview.h b/include/ui/mapview.h index 5520ec80..023e1cac 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -8,13 +8,13 @@ class Editor; -class MapView : public QGraphicsView +class MapView : public GraphicsView { Q_OBJECT public: - MapView() : QGraphicsView() {} - MapView(QWidget *parent) : QGraphicsView(parent) {} + MapView() : GraphicsView() {} + MapView(QWidget *parent) : GraphicsView(parent) {} Editor *editor; diff --git a/src/editor.cpp b/src/editor.cpp index 5fb3ef80..3073e66f 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -177,10 +177,20 @@ void Editor::setEditMode(EditMode editMode) { this->ui->pushButton_ChangeDimensions->setEnabled(editingLayout); this->ui->checkBox_smartPaths->setEnabled(editingLayout); - if (this->editMode == EditMode::Events || oldEditMode == EditMode::Events) { + if (this->editMode != oldEditMode) { + // When switching to or from the Connections tab we sync up the two separate map graphics views. + if (this->editMode == EditMode::Connections) { + ui->graphicsView_Connections->centerOn(ui->graphicsView_Map); + } else if (oldEditMode == EditMode::Connections) { + ui->graphicsView_Map->centerOn(ui->graphicsView_Connections); + } + // When switching to or from the Events tab the opacity of the events changes. Redraw the events to reflect that change. - redrawAllEvents(); + if (this->editMode == EditMode::Events || oldEditMode == EditMode::Events) { + redrawAllEvents(); + } } + if (this->editMode == EditMode::Events){ updateWarpEventWarnings(); } From 56d1a0d57009e95f0d397d52a6ba5aebffbf0e5c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 26 Jun 2025 14:07:04 -0400 Subject: [PATCH 14/71] Fix exported metatile image scaling, remove hardcoded cell sizes --- forms/tileseteditor.ui | 18 ++++------ include/config.h | 2 +- include/ui/metatilelayersitem.h | 9 +---- include/ui/tilemaptileselector.h | 2 +- src/ui/metatilelayersitem.cpp | 42 ++++++++++++++++-------- src/ui/metatileselector.cpp | 4 +-- src/ui/regionmapentriespixmapitem.cpp | 6 ++-- src/ui/regionmaplayoutpixmapitem.cpp | 4 +-- src/ui/tilemaptileselector.cpp | 6 ++-- src/ui/tileseteditormetatileselector.cpp | 2 +- src/ui/tileseteditortileselector.cpp | 26 ++++++++------- 11 files changed, 62 insertions(+), 59 deletions(-) diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index d1817859..6c696bf3 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -6,7 +6,7 @@ 0 0 - 733 + 748 784 @@ -267,16 +267,10 @@ 0 - - - 66 - 34 - - - 96 - 34 + 1 + 1 @@ -561,8 +555,8 @@ 0 0 - 445 - 237 + 499 + 241 @@ -623,7 +617,7 @@ 0 0 - 733 + 748 37 diff --git a/include/config.h b/include/config.h index 171e3850..234fa76d 100644 --- a/include/config.h +++ b/include/config.h @@ -82,7 +82,7 @@ public: this->collisionOpacity = 50; this->collisionZoom = 30; this->metatilesZoom = 30; - this->tilesetEditorMetatilesZoom = 30; + this->tilesetEditorMetatilesZoom = 45; this->tilesetEditorTilesZoom = 30; this->showPlayerView = false; this->showCursorTile = true; diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h index ef05a77d..1ca27c1c 100644 --- a/include/ui/metatilelayersitem.h +++ b/include/ui/metatilelayersitem.h @@ -9,14 +9,7 @@ class MetatileLayersItem: public SelectablePixmapItem { Q_OBJECT public: - MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset): SelectablePixmapItem(16, 16, 6, 2) { - this->metatile = metatile; - this->primaryTileset = primaryTileset; - this->secondaryTileset = secondaryTileset; - this->clearLastModifiedCoords(); - this->clearLastHoveredCoords(); - setAcceptHoverEvents(true); - } + MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset); void draw(); void setTilesets(Tileset*, Tileset*); void setMetatile(Metatile*); diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 155957a6..558b6dc6 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -136,7 +136,7 @@ public: this->palette = PaletteUtil::parse(palFilepath, &err); } this->setPixmap(QPixmap::fromImage(this->tileset)); - this->numTilesWide = this->tileset.width() / 8; + this->numTilesWide = this->tileset.width() / this->cellWidth; this->selectedTile = 0x00; setAcceptHoverEvents(true); } diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index fabb49a4..a26ed14b 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -3,6 +3,17 @@ #include "imageproviders.h" #include +MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset) + : SelectablePixmapItem(16, 16, 2 * projectConfig.getNumLayersInMetatile(), 2), + metatile(metatile), + primaryTileset(primaryTileset), + secondaryTileset(secondaryTileset) +{ + clearLastModifiedCoords(); + clearLastHoveredCoords(); + setAcceptHoverEvents(true); +} + static const QList tilePositions = { QPoint(0, 0), QPoint(1, 0), @@ -20,23 +31,31 @@ static const QList tilePositions = { void MetatileLayersItem::draw() { const int numLayers = projectConfig.getNumLayersInMetatile(); - QPixmap pixmap(numLayers * 32, 32); + const int layerWidth = this->cellWidth * 2; + const int layerHeight = this->cellHeight * 2; + QPixmap pixmap(numLayers * layerWidth, layerHeight); QPainter painter(&pixmap); // Draw tile images int numTiles = qMin(projectConfig.getNumTilesInMetatile(), this->metatile ? this->metatile->tiles.length() : 0); for (int i = 0; i < numTiles; i++) { Tile tile = this->metatile->tiles.at(i); - QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(16, 16); + QImage tileImage = getPalettedTileImage(tile.tileId, + this->primaryTileset, + this->secondaryTileset, + tile.palette, + true + ).scaled(this->cellWidth, this->cellHeight); tile.flip(&tileImage); - painter.drawImage(tilePositions.at(i) * 16, tileImage); + QPoint pos = tilePositions.at(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 * 32; - painter.drawLine(x, 0, x, 32); + int x = i * layerWidth; + painter.drawLine(x, 0, x, layerHeight); } } @@ -127,13 +146,8 @@ void MetatileLayersItem::clearLastHoveredCoords() { } QPoint MetatileLayersItem::getBoundedPos(const QPointF &pos) { - int x, y; - int maxX = (projectConfig.getNumLayersInMetatile() * 2) - 1; - x = static_cast(pos.x()) / 16; - y = static_cast(pos.y()) / 16; - if (x < 0) x = 0; - if (y < 0) y = 0; - if (x > maxX) x = maxX; - if (y > 1) y = 1; - return QPoint(x, y); + int x = static_cast(pos.x()) / this->cellWidth; + int y = static_cast(pos.y()) / this->cellHeight; + return QPoint( qMax(0, qMin(x, this->maxSelectionWidth - 1)), + qMax(0, qMin(y, this->maxSelectionHeight - 1)) ); } diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 012b6609..828c73cd 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -21,7 +21,7 @@ void MetatileSelector::updateBasePixmap() { if (length_ % this->numMetatilesWide != 0) { height_++; } - QImage image(this->numMetatilesWide * 16, height_ * 16, QImage::Format_RGBA8888); + QImage image(this->numMetatilesWide * this->cellWidth, height_ * this->cellHeight, QImage::Format_RGBA8888); image.fill(Qt::magenta); QPainter painter(&image); for (int i = 0; i < length_; i++) { @@ -32,7 +32,7 @@ void MetatileSelector::updateBasePixmap() { QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, layout->metatileLayerOrder, layout->metatileLayerOpacity); int map_y = i / this->numMetatilesWide; int map_x = i % this->numMetatilesWide; - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); + QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight); painter.drawImage(metatile_origin, metatile_image); } painter.end(); diff --git a/src/ui/regionmapentriespixmapitem.cpp b/src/ui/regionmapentriespixmapitem.cpp index 8ac888a0..5bf9a08f 100644 --- a/src/ui/regionmapentriespixmapitem.cpp +++ b/src/ui/regionmapentriespixmapitem.cpp @@ -17,12 +17,12 @@ void RegionMapEntriesPixmapItem::draw() { entry_w = entry.width, entry_h = entry.height; } - QImage image(region_map->tilemapWidth() * 8, region_map->tilemapHeight() * 8, QImage::Format_RGBA8888); + QImage image(region_map->tilemapWidth() * this->cellWidth, region_map->tilemapHeight() * this->cellHeight, QImage::Format_RGBA8888); QPainter painter(&image); for (int i = 0; i < region_map->tilemapSize(); i++) { QImage bottom_img = this->tile_selector->tileImg(region_map->getTile(i)); - QImage top_img(8, 8, QImage::Format_RGBA8888); + QImage top_img(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888); int x = i % region_map->tilemapWidth(); int y = i / region_map->tilemapWidth(); bool insideEntry = false; @@ -40,7 +40,7 @@ void RegionMapEntriesPixmapItem::draw() { } else { top_img.fill(Qt::black); } - QPoint pos = QPoint(x * 8, y * 8); + QPoint pos = QPoint(x * this->cellWidth, y * this->cellHeight); painter.setOpacity(1); painter.drawImage(pos, bottom_img); painter.save(); diff --git a/src/ui/regionmaplayoutpixmapitem.cpp b/src/ui/regionmaplayoutpixmapitem.cpp index eda35152..1895f8c8 100644 --- a/src/ui/regionmaplayoutpixmapitem.cpp +++ b/src/ui/regionmaplayoutpixmapitem.cpp @@ -8,7 +8,7 @@ void RegionMapLayoutPixmapItem::draw() { QPainter painter(&image); for (int i = 0; i < region_map->tilemapSize(); i++) { QImage bottom_img = this->tile_selector->tileImg(region_map->getTile(i)); - QImage top_img(8, 8, QImage::Format_RGBA8888); + QImage top_img(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888); if (region_map->squareHasMap(i)) { top_img.fill(Qt::gray); } else { @@ -16,7 +16,7 @@ void RegionMapLayoutPixmapItem::draw() { } int x = i % region_map->tilemapWidth(); int y = i / region_map->tilemapWidth(); - QPoint pos = QPoint(x * 8, y * 8); + QPoint pos = QPoint(x * this->cellWidth, y * this->cellHeight); painter.setOpacity(1); painter.drawImage(pos, bottom_img); painter.save(); diff --git a/src/ui/tilemaptileselector.cpp b/src/ui/tilemaptileselector.cpp index 45771ad2..35dd294a 100644 --- a/src/ui/tilemaptileselector.cpp +++ b/src/ui/tilemaptileselector.cpp @@ -7,9 +7,9 @@ void TilemapTileSelector::draw() { this->pixelWidth = width_; size_t height_ = this->tileset.height(); this->pixelHeight = height_; - size_t ntiles_ = (width_/8) * (height_/8); + size_t ntiles_ = (width_/this->cellWidth) * (height_/this->cellHeight); - this->numTilesWide = width_ / 8; + this->numTilesWide = width_ / this->cellWidth; this->numTiles = ntiles_; this->setPixmap(QPixmap::fromImage(this->setPalette(this->tile_palette))); @@ -92,7 +92,7 @@ QImage TilemapTileSelector::tileImg(shared_ptr tile) { QImage tilesetImage = setPalette(tile->palette()); // take a tile from the tileset - QImage img = tilesetImage.copy(pos.x() * 8, pos.y() * 8, 8, 8); + QImage img = tilesetImage.copy(pos.x() * this->cellWidth, pos.y() * this->cellHeight, this->cellWidth, this->cellHeight); // QImage::flip was introduced in 6.9.0 to replace QImage::mirrored. #if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 4e09cf34..5026a49b 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -4,7 +4,7 @@ #include TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) - : SelectablePixmapItem(32, 32, 1, 1) { + : SelectablePixmapItem(16, 16, 1, 1) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; this->numMetatilesWide = 8; diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 01e4008c..034a86a7 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -22,27 +22,27 @@ void TilesetEditorTileSelector::draw() { int secondaryLength = this->secondaryTileset->tiles.length(); int height = totalTiles / this->numTilesWide; QList palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset, true); - QImage image(this->numTilesWide * 16, height * 16, QImage::Format_RGBA8888); + QImage image(this->numTilesWide * this->cellWidth, height * this->cellHeight, QImage::Format_RGBA8888); QPainter painter(&image); for (uint16_t tile = 0; tile < totalTiles; tile++) { QImage tileImage; if (tile < primaryLength) { - tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(16, 16); + tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight); } else if (tile < Project::getNumTilesPrimary()) { - tileImage = QImage(16, 16, QImage::Format_RGBA8888); + tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888); tileImage.fill(palette.at(0)); } else if (tile < Project::getNumTilesPrimary() + secondaryLength) { - tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(16, 16); + tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight); } else { - tileImage = QImage(16, 16, QImage::Format_RGBA8888); + tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888); QPainter painter(&tileImage); - painter.fillRect(0, 0, 16, 16, palette.at(0)); + painter.fillRect(0, 0, this->cellWidth, this->cellHeight, palette.at(0)); } int y = tile / this->numTilesWide; int x = tile % this->numTilesWide; - QPoint origin = QPoint(x * 16, y * 16); + QPoint origin = QPoint(x * this->cellWidth, y * this->cellHeight); painter.drawImage(origin, tileImage); } @@ -52,9 +52,9 @@ void TilesetEditorTileSelector::draw() { // Round up height for incomplete last row row++; } - const int y = row * 16; + const int y = row * this->cellHeight; painter.setPen(Qt::white); - painter.drawLine(0, y, this->numTilesWide * 16, y); + painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y); } painter.end(); @@ -244,8 +244,10 @@ QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() { } QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) { + const int tileWidth = 8; + const int tileHeight = 8; int height = qCeil(numTiles / static_cast(this->numTilesWide)); - QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); + QImage image(this->numTilesWide * tileWidth, height * tileHeight, QImage::Format_RGBA8888); image.fill(0); QPainter painter(&image); @@ -253,7 +255,7 @@ QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) { QImage tileImage = getGreyscaleTileImage(tileIdStart + i, this->primaryTileset, this->secondaryTileset); int y = i / this->numTilesWide; int x = i % this->numTilesWide; - QPoint origin = QPoint(x * 8, y * 8); + QPoint origin = QPoint(x * tileWidth, y * tileHeight); painter.drawImage(origin, tileImage); } painter.end(); @@ -305,7 +307,7 @@ void TilesetEditorTileSelector::drawUnused() { for (int tile = 0; tile < this->usedTiles.size(); tile++) { if (!this->usedTiles[tile]) { - unusedPainter.drawPixmap((tile % 16) * 16, (tile / 16) * 16, redX); + unusedPainter.drawPixmap((tile % this->cellWidth) * this->cellWidth, (tile / this->cellWidth) * this->cellHeight, redX); } } From d2febd99e989a64d8fbc7b9e1515d76668968929 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 27 Jun 2025 00:34:03 -0400 Subject: [PATCH 15/71] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ac8dc4..d5b86437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,13 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ## [Unreleased] ### Changed - The scroll position of the map view now remains the same between the Connections tab and the Map/Events tabs. +- The Move tool now behaves more like a traditional pan tool (with no momentum). +- The Player View Rectangle is now visible on the Events tab, as is the Cursor Tile Outline for certain tools. ### Fixed +- Fix click-drag map selections behaving unexpectedly when the cursor is outside the map grid. +- Fix events being dragged in negative coordinates lagging behind the cursor. +- Fix the shortcut for duplicating events working while on the Connections tab. - Fix warning not appearing when the log file exceeds maximum size. - Fix unnecessary resources being used to watch files. From e7f91097b257afd88a3b5050933862067b1633d3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 30 Jun 2025 20:35:28 -0400 Subject: [PATCH 16/71] Fix Region Map Editor incorrectly displaying certain MAPSEC data --- CHANGELOG.md | 1 + src/project.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5b86437..5323e3fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix click-drag map selections behaving unexpectedly when the cursor is outside the map grid. - Fix events being dragged in negative coordinates lagging behind the cursor. - Fix the shortcut for duplicating events working while on the Connections tab. +- Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. - Fix warning not appearing when the log file exceeds maximum size. - Fix unnecessary resources being used to watch files. diff --git a/src/project.cpp b/src/project.cpp index 99ad5583..75b32474 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2641,7 +2641,10 @@ void Project::setRegionMapEntries(const QHash &entries QHash Project::getRegionMapEntries() const { QHash entries; for (auto it = this->locationData.constBegin(); it != this->locationData.constEnd(); it++) { - entries[it.key()] = it.value().map; + const MapSectionEntry regionMapData = it.value().map; + if (regionMapData.valid) { + entries[it.key()] = regionMapData; + } } return entries; } From 6ab6a13f2f20ba22778f5717794776a434b3dac0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 30 Jun 2025 21:35:03 -0400 Subject: [PATCH 17/71] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5323e3fa..d4feb60d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - The Player View Rectangle is now visible on the Events tab, as is the Cursor Tile Outline for certain tools. ### Fixed +- Fix metatile images exporting at 2x scale. - Fix click-drag map selections behaving unexpectedly when the cursor is outside the map grid. - Fix events being dragged in negative coordinates lagging behind the cursor. - Fix the shortcut for duplicating events working while on the Connections tab. From 17e4cbfa30e5d4765d460c8759f0e3081abe76b5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 1 Jul 2025 16:10:10 -0400 Subject: [PATCH 18/71] Fix paste for Wild Pokemon not updating data in memory --- CHANGELOG.md | 1 + include/ui/montabwidget.h | 3 +++ src/ui/montabwidget.cpp | 15 +++++++-------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4feb60d..34a9b266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ### Fixed - Fix metatile images exporting at 2x scale. +- Fix pasting Wild Pokémon data then changing maps resetting the pasted data. - Fix click-drag map selections behaving unexpectedly when the cursor is outside the map grid. - Fix events being dragged in negative coordinates lagging behind the cursor. - Fix the shortcut for duplicating events working while on the Connections tab. diff --git a/include/ui/montabwidget.h b/include/ui/montabwidget.h index 8292d28e..004fa475 100644 --- a/include/ui/montabwidget.h +++ b/include/ui/montabwidget.h @@ -33,6 +33,9 @@ public slots: void setTabActive(int index, bool active = true); void deactivateTab(int tabIndex); +signals: + void edited(); + private: void actionCopyTab(int index); void actionAddDeleteTab(int index); diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp index 3a04c1b7..e2291f11 100644 --- a/src/ui/montabwidget.cpp +++ b/src/ui/montabwidget.cpp @@ -11,6 +11,9 @@ static WildMonInfo encounterClipboard; MonTabWidget::MonTabWidget(Editor *editor, QWidget *parent) : QTabWidget(parent) { this->editor = editor; + connect(this, &MonTabWidget::edited, this->editor, &Editor::saveEncounterTabData); + connect(this, &MonTabWidget::edited, this->editor, &Editor::wildMonTableEdited); + populate(); this->tabBar()->installEventFilter(new WheelFilter(this)); } @@ -64,7 +67,7 @@ void MonTabWidget::paste(int index) { WildMonInfo newInfo = getDefaultMonInfo(this->editor->project->wildMonFields.at(index)); combineEncounters(newInfo, encounterClipboard); populateTab(index, newInfo); - emit editor->wildMonTableEdited(); + emit edited(); } void MonTabWidget::actionCopyTab(int index) { @@ -88,15 +91,12 @@ void MonTabWidget::actionAddDeleteTab(int index) { if (activeTabs[index]) { // delete tab deactivateTab(index); - editor->saveEncounterTabData(); - } - else { + } else { // add tab populateTab(index, getDefaultMonInfo(editor->project->wildMonFields.at(index))); - editor->saveEncounterTabData(); setCurrentIndex(index); } - emit editor->wildMonTableEdited(); + emit edited(); } void MonTabWidget::clearTableAt(int tabIndex) { @@ -123,8 +123,7 @@ void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo) { QTableView *speciesTable = tableAt(tabIndex); EncounterTableModel *model = new EncounterTableModel(monInfo, editor->project->wildMonFields[tabIndex], this); - connect(model, &EncounterTableModel::edited, editor, &Editor::saveEncounterTabData); - connect(model, &EncounterTableModel::edited, editor, &Editor::wildMonTableEdited); + connect(model, &EncounterTableModel::edited, this, &MonTabWidget::edited); speciesTable->setModel(model); speciesTable->setItemDelegateForColumn(EncounterTableModel::ColumnType::Species, new SpeciesComboDelegate(editor->project, this)); From b2798e77d42008101ae00102f747ffa330e27bb2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 1 Jul 2025 17:53:07 -0400 Subject: [PATCH 19/71] Allow rendering individual metatile layers --- docsrc/manual/scripting-capabilities.rst | 80 +++++++++++++++++-- include/core/map.h | 3 - include/core/maplayout.h | 20 ++++- include/editor.h | 1 - include/mainwindow.h | 4 + include/scripting.h | 4 +- include/scriptutility.h | 6 +- include/ui/imageproviders.h | 4 + include/ui/metatileselector.h | 9 +-- resources/text/script_template.txt | 4 +- src/core/map.cpp | 2 - src/core/maplayout.cpp | 26 +++++- src/core/tileset.cpp | 17 +++- src/editor.cpp | 18 ++--- src/mainwindow.cpp | 7 +- src/scriptapi/apimap.cpp | 33 +++++++- src/scriptapi/apioverlay.cpp | 6 +- src/scriptapi/apiutility.cpp | 42 ++++------ src/scriptapi/scripting.cpp | 9 ++- src/ui/bordermetatilespixmapitem.cpp | 7 +- src/ui/currentselectedmetatilespixmapitem.cpp | 7 +- src/ui/imageproviders.cpp | 65 +++++++++------ src/ui/metatileselector.cpp | 37 ++++----- src/ui/tileseteditormetatileselector.cpp | 8 +- 24 files changed, 272 insertions(+), 147 deletions(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index 0651f7d9..4e016f6d 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -1222,6 +1222,58 @@ All tileset functions are callable via the global ``map`` object. :returns: the pixel data :rtype: array +.. |describe-metatile-layer-order| + replace:: where ``0`` is the bottom layer, ``1`` is the middle layer, and ``2`` is the top layer. The default order is ``[0, 1, 2]`` + +.. |describe-metatile-layer-order-handling| + replace:: If no elements are provided the layer order will be reset to the default. Any layer not listed in the provided ``order`` will not be rendered. Any additional elements after the first 3 are ignored + +.. js:function:: map.getMetatileLayerOrder() + + Gets the order that metatile layers are rendered for the current layout, |describe-metatile-layer-order|. + + If you'd like to get the default metatile layer order for all layouts, see :js:func:`utility.getMetatileLayerOrder` instead. + + :returns: array of layers + :rtype: array + +.. js:function:: map.setMetatileLayerOrder(order) + + Sets the order that metatile layers are rendered for the current layout, |describe-metatile-layer-order|. + + |describe-metatile-layer-order-handling|. + + If you'd like to set the default metatile layer order for all layouts, see :js:func:`utility.setMetatileLayerOrder` instead. + + :param order: array of layers + :type order: array + +.. |describe-metatile-layer-opacity| + replace:: where the first element is the bottom layer, the second element is the middle layer, and the third element is the top layer. The default opacities are ``[1.0, 1.0, 1.0]`` + +.. |describe-metatile-layer-opacity-handling| + replace:: Any additional elements after the first 3 are ignored. Any elements not provided will be rendered with opacity ``1.0`` + +.. js:function:: map.getMetatileLayerOpacity() + + Gets the opacities that metatile layers are rendered with for the current layout, |describe-metatile-layer-opacity|. + + If you'd like to get the default metatile layer opacities for all layouts, see :js:func:`utility.getMetatileLayerOpacity` instead. + + :returns: array of opacities for each layer + :rtype: array + +.. js:function:: map.setMetatileLayerOpacity(opacities) + + Sets the opacities that metatile layers are rendered with for the current layout, |describe-metatile-layer-opacity|. + + |describe-metatile-layer-opacity-handling|. + + If you'd like to set the default metatile layer opacities for all layouts, see :js:func:`utility.setMetatileLayerOpacity` instead. + + :param opacities: array of opacities for each layer + :type opacities: array + Overlay Functions ^^^^^^^^^^^^^^^^^ @@ -1811,30 +1863,42 @@ All settings functions are callable via the global ``utility`` object. .. js:function:: utility.getMetatileLayerOrder() - Gets the order that metatile layers are rendered. + Gets the order that metatile layers are rendered by default, |describe-metatile-layer-order|. - :returns: array of layers. The bottom layer is represented as 0. + If you'd like to get the metatile layer order for only the current layout, see :js:func:`map.getMetatileLayerOrder` instead. + + :returns: array of layers :rtype: array .. js:function:: utility.setMetatileLayerOrder(order) - Sets the order that metatile layers are rendered. + Sets the order that metatile layers are rendered by default, |describe-metatile-layer-order|. - :param order: array of layers. The bottom layer is represented as 0. + |describe-metatile-layer-order-handling|. + + If you'd like to set the metatile layer order for only the current layout, see :js:func:`map.setMetatileLayerOrder` instead. + + :param order: array of layers :type order: array .. js:function:: utility.getMetatileLayerOpacity() - Gets the opacities that metatile layers are rendered with. + Gets the opacities that metatile layers are rendered with by default, |describe-metatile-layer-opacity|. - :returns: array of opacities for each layer. The bottom layer is the first element. + If you'd like to get the metatile layer opacities for only the current layout, see :js:func:`map.getMetatileLayerOpacity` instead. + + :returns: array of opacities for each layer :rtype: array .. js:function:: utility.setMetatileLayerOpacity(opacities) - Sets the opacities that metatile layers are rendered with. + Sets the opacities that metatile layers are rendered with by default, |describe-metatile-layer-opacity|. - :param opacities: array of opacities for each layer. The bottom layer is the first element. + |describe-metatile-layer-opacity-handling|. + + If you'd like to set the metatile layer opacities for only the current layout, see :js:func:`map.setMetatileLayerOpacity` instead. + + :param opacities: array of opacities for each layer :type opacities: array diff --git a/include/core/map.h b/include/core/map.h index d5e28075..f5cc1ef1 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -133,9 +133,6 @@ private: QMap> m_events; QSet m_ownedEvents; // for memory management - QList m_metatileLayerOrder; - QList m_metatileLayerOpacity; - void trackConnection(MapConnection*); // MapConnections in 'ownedConnections' but not 'connections' persist in the edit history. diff --git a/include/core/maplayout.h b/include/core/maplayout.h index c7790ad3..280f3006 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -17,7 +17,7 @@ class BorderMetatilesPixmapItem; class Layout : public QObject { Q_OBJECT public: - Layout() {} + Layout() {}; Layout(const Layout &other); static QString layoutConstantFromName(const QString &name); @@ -64,8 +64,15 @@ public: QSize borderDimensions; } lastCommitBlocks; // to track map changes - QList metatileLayerOrder; - QList metatileLayerOpacity; + void setMetatileLayerOrder(const QList &layerOrder) { m_metatileLayerOrder = layerOrder; } + QList metatileLayerOrder() const; + static void setDefaultMetatileLayerOrder(const QList &layerOrder) { s_defaultMetatileLayerOrder = layerOrder; } + static QList defaultMetatileLayerOrder(); + + void setMetatileLayerOpacity(const QList &layerOpacity) { m_metatileLayerOpacity = layerOpacity; } + QList metatileLayerOpacity() const; + static void setDefaultMetatileLayerOpacity(const QList &layerOpacity) { s_defaultMetatileLayerOpacity = layerOpacity; } + static QList defaultMetatileLayerOpacity(); LayoutPixmapItem *layoutItem = nullptr; CollisionPixmapItem *collisionItem = nullptr; @@ -147,6 +154,8 @@ public: void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } + bool metatileIsValid(uint16_t metatileId) { return Tileset::metatileIsValid(metatileId, this->tileset_primary, this->tileset_secondary); } + private: void setNewDimensionsBlockdata(int newWidth, int newHeight); void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); @@ -155,6 +164,11 @@ private: static int getBorderDrawDistance(int dimension, qreal minimum); + QList m_metatileLayerOrder; + QList m_metatileLayerOpacity; + static QList s_defaultMetatileLayerOrder; + static QList s_defaultMetatileLayerOpacity; + signals: void dimensionsChanged(const QSize &size); void needsRedrawing(); diff --git a/include/editor.h b/include/editor.h index 414356d1..a4620af6 100644 --- a/include/editor.h +++ b/include/editor.h @@ -284,7 +284,6 @@ signals: void wildMonTableEdited(); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); - void tilesetUpdated(QString); void gridToggled(bool); void editActionSet(EditAction newEditAction); }; diff --git a/include/mainwindow.h b/include/mainwindow.h index 3b3003aa..8441babb 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -145,6 +145,10 @@ public: Q_INVOKABLE void setMetatileTiles(int metatileId, QJSValue tilesObj, int tileStart = 0, int tileEnd = -1, bool forceRedraw = true); Q_INVOKABLE void setMetatileTiles(int metatileId, int tileId, bool xflip, bool yflip, int palette, int tileStart = 0, int tileEnd = -1, bool forceRedraw = true); Q_INVOKABLE QJSValue getTilePixels(int tileId); + Q_INVOKABLE QList getMetatileLayerOrder() const; + Q_INVOKABLE void setMetatileLayerOrder(const QList &order); + Q_INVOKABLE QList getMetatileLayerOpacity() const; + Q_INVOKABLE void setMetatileLayerOpacity(const QList &opacities); Q_INVOKABLE QString getSong(); Q_INVOKABLE void setSong(QString song); Q_INVOKABLE QString getLocation(); diff --git a/include/scripting.h b/include/scripting.h index b85e9e26..aaa2bd02 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -23,7 +23,7 @@ enum CallbackType { OnMapResized, OnBorderResized, OnMapShifted, - OnTilesetUpdated, + OnTilesetsChanged, OnMainTabChanged, OnMapViewTabChanged, OnBorderVisibilityToggled, @@ -51,7 +51,7 @@ public: static void cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta); static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_MapShifted(int xDelta, int yDelta); - static void cb_TilesetUpdated(QString tilesetName); + static void cb_TilesetsChanged(const QString &primaryTilesetName, const QString &secondaryTilesetName); static void cb_MainTabChanged(int oldTab, int newTab); static void cb_MapViewTabChanged(int oldTab, int newTab); static void cb_BorderVisibilityToggled(bool visible); diff --git a/include/scriptutility.h b/include/scriptutility.h index 78272eec..09bfaae6 100644 --- a/include/scriptutility.h +++ b/include/scriptutility.h @@ -38,9 +38,9 @@ public: Q_INVOKABLE bool getSmartPathsEnabled(); Q_INVOKABLE QList getCustomScripts(); Q_INVOKABLE QList getMetatileLayerOrder(); - Q_INVOKABLE void setMetatileLayerOrder(QList order); + Q_INVOKABLE void setMetatileLayerOrder(const QList &order); Q_INVOKABLE QList getMetatileLayerOpacity(); - Q_INVOKABLE void setMetatileLayerOpacity(QList order); + Q_INVOKABLE void setMetatileLayerOpacity(const QList &order); Q_INVOKABLE QList getMapNames(); Q_INVOKABLE QList getMapConstants(); Q_INVOKABLE QList getLayoutNames(); @@ -57,6 +57,8 @@ public: Q_INVOKABLE bool isPrimaryTileset(QString tilesetName); Q_INVOKABLE bool isSecondaryTileset(QString tilesetName); + static bool validateMetatileLayerOrder(const QList &order); + private: void callTimeoutFunction(QJSValue callback); void runMessageBox(QString text, QString informativeText, QString detailedText, QMessageBox::Icon icon); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index 806ffd5b..d3136580 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -6,8 +6,12 @@ #include #include +class Layout; + QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); +QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false); +QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false); QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); QImage getTileImage(uint16_t, Tileset*, Tileset*); diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index 7bb3f131..f5faa1e0 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -36,8 +36,6 @@ public: this->prefabSelection = false; this->numMetatilesWide = numMetatilesWide; this->layout = layout; - this->primaryTileset = layout->tileset_primary; - this->secondaryTileset = layout->tileset_secondary; this->selection = MetatileSelection{}; this->cellPos = QPoint(-1, -1); setAcceptHoverEvents(true); @@ -45,10 +43,10 @@ public: QPoint getSelectionDimensions() override; void draw() override; + void refresh(); bool select(uint16_t metatile); void selectFromMap(uint16_t metatileId, uint16_t collision, uint16_t elevation); - void setTilesets(Tileset*, Tileset*); MetatileSelection getMetatileSelection(); void setPrefabSelection(MetatileSelection selection); void setExternalSelection(int, int, QList, QList>); @@ -56,8 +54,9 @@ public: void setLayout(Layout *layout); bool isInternalSelection() const { return (!this->externalSelection && !this->prefabSelection); } - Tileset *primaryTileset; - Tileset *secondaryTileset; + Tileset *primaryTileset() const { return this->layout->tileset_primary; } + Tileset *secondaryTileset() const { return this->layout->tileset_secondary; } + protected: void mousePressEvent(QGraphicsSceneMouseEvent*) override; void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; diff --git a/resources/text/script_template.txt b/resources/text/script_template.txt index bee6e56e..8135a853 100644 --- a/resources/text/script_template.txt +++ b/resources/text/script_template.txt @@ -53,8 +53,8 @@ export function onMapShifted(xDelta, yDelta) { } -// Called when the currently loaded tileset is changed by switching to a new one or by saving changes to it in the Tileset Editor. -export function onTilesetUpdated(tilesetName) { +// Called when a currently loaded tileset is changed by switching to a new one or by saving changes in the Tileset Editor. +export function onTilesetsChanged(primaryTilesetName, secondaryTilesetName) { } diff --git a/src/core/map.cpp b/src/core/map.cpp index 6110a26f..03fee13e 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -30,8 +30,6 @@ Map::Map(const Map &other, QObject *parent) : Map(parent) { *m_header = *other.m_header; m_layout = other.m_layout; m_isPersistedToFile = false; - m_metatileLayerOrder = other.m_metatileLayerOrder; - m_metatileLayerOpacity = other.m_metatileLayerOpacity; // Copy events for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 4ddb3b75..a9885042 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -380,8 +380,8 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { metatileId, fromLayout ? fromLayout->tileset_primary : this->tileset_primary, fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary, - metatileLayerOrder, - metatileLayerOpacity + metatileLayerOrder(), + metatileLayerOpacity() ); imageCache.insert(metatileId, metatileImage); } @@ -457,7 +457,7 @@ QPixmap Layout::renderBorder(bool ignoreCache) { changed_any = true; Block block = this->border.at(i); uint16_t metatileId = block.metatileId(); - QImage metatile_image = getMetatileImage(metatileId, this->tileset_primary, this->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); + QImage metatile_image = getMetatileImage(metatileId, this); int map_y = width_ ? i / width_ : 0; int map_x = width_ ? i % width_ : 0; painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); @@ -612,3 +612,23 @@ Blockdata Layout::readBlockdata(const QString &path, QString *error) { return blockdata; } + +QList Layout::metatileLayerOrder() const { + return !m_metatileLayerOrder.isEmpty() ? m_metatileLayerOrder : Layout::defaultMetatileLayerOrder(); +} + +QList Layout::s_defaultMetatileLayerOrder; +QList Layout::defaultMetatileLayerOrder() { + static const QList initialDefault = {0, 1, 2}; + return !s_defaultMetatileLayerOrder.isEmpty() ? s_defaultMetatileLayerOrder : initialDefault; +} + +QList Layout::metatileLayerOpacity() const { + return !m_metatileLayerOpacity.isEmpty() ? m_metatileLayerOpacity : Layout::defaultMetatileLayerOpacity(); +} + +QList Layout::s_defaultMetatileLayerOpacity; +QList Layout::defaultMetatileLayerOpacity() { + static const QList initialDefault = {1.0, 1.0, 1.0}; + return !s_defaultMetatileLayerOpacity.isEmpty() ? s_defaultMetatileLayerOpacity : initialDefault; +} diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index cc7efb55..0ae8512f 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -220,14 +220,23 @@ bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tile QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) { QList> palettes; - auto primaryPalettes = useTruePalettes ? primaryTileset->palettes : primaryTileset->palettePreviews; + + QList> primaryPalettes; + if (primaryTileset) { + primaryPalettes = useTruePalettes ? primaryTileset->palettes : primaryTileset->palettePreviews; + } for (int i = 0; i < Project::getNumPalettesPrimary(); i++) { - palettes.append(primaryPalettes.at(i)); + palettes.append(primaryPalettes.value(i)); + } + + QList> secondaryPalettes; + if (secondaryTileset) { + secondaryPalettes = useTruePalettes ? secondaryTileset->palettes : secondaryTileset->palettePreviews; } - auto secondaryPalettes = useTruePalettes ? secondaryTileset->palettes : secondaryTileset->palettePreviews; for (int i = Project::getNumPalettesPrimary(); i < Project::getNumPalettesTotal(); i++) { - palettes.append(secondaryPalettes.at(i)); + palettes.append(secondaryPalettes.value(i)); } + return palettes; } diff --git a/src/editor.cpp b/src/editor.cpp index 5ad15a84..bd15f4c5 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1357,12 +1357,13 @@ bool Editor::setLayout(QString layoutId) { map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); connect(this->layout, &Layout::dimensionsChanged, map_ruler, &MapRuler::setMapDimensions); - ui->comboBox_PrimaryTileset->blockSignals(true); - ui->comboBox_SecondaryTileset->blockSignals(true); + QString prevPrimaryTileset = ui->comboBox_PrimaryTileset->currentText(); + QString prevSecondaryTileset = ui->comboBox_SecondaryTileset->currentText(); + + const QSignalBlocker b_PrimaryTilest(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTilest(ui->comboBox_SecondaryTileset); ui->comboBox_PrimaryTileset->setTextItem(this->layout->tileset_primary_label); ui->comboBox_SecondaryTileset->setTextItem(this->layout->tileset_secondary_label); - ui->comboBox_PrimaryTileset->blockSignals(false); - ui->comboBox_SecondaryTileset->blockSignals(false); const QSignalBlocker b0(this->ui->comboBox_LayoutSelector); int index = this->ui->comboBox_LayoutSelector->findText(layoutId); @@ -1371,6 +1372,8 @@ bool Editor::setLayout(QString layoutId) { if (this->layout->name != prevLayoutName) Scripting::cb_LayoutOpened(this->layout->name); + if (this->layout->tileset_primary_label != prevPrimaryTileset || this->layout->tileset_secondary_label != prevSecondaryTileset) + Scripting::cb_TilesetsChanged(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); return true; } @@ -1651,13 +1654,6 @@ void Editor::displayMetatileSelector() { metatile_selector_item->select(0); } else { metatile_selector_item->setLayout(this->layout); - if (metatile_selector_item->primaryTileset - && metatile_selector_item->primaryTileset != this->layout->tileset_primary) - emit tilesetUpdated(this->layout->tileset_primary->name); - if (metatile_selector_item->secondaryTileset - && metatile_selector_item->secondaryTileset != this->layout->tileset_secondary) - emit tilesetUpdated(this->layout->tileset_secondary->name); - metatile_selector_item->setTilesets(this->layout->tileset_primary, this->layout->tileset_secondary); } scene_metatiles->addItem(metatile_selector_item); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 238d9706..8d44895e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -367,7 +367,6 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::wildMonTableEdited, [this] { markMapEdited(this->editor->map); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); - connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(this->editor, &Editor::editActionSet, this, &MainWindow::setEditActionUi); connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); @@ -2604,20 +2603,20 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT bool updated = false; if (primaryTilesetLabel == this->editor->layout->tileset_primary_label) { this->editor->updatePrimaryTileset(primaryTilesetLabel, true); - Scripting::cb_TilesetUpdated(primaryTilesetLabel); updated = true; } else { this->editor->project->getTileset(primaryTilesetLabel, true); } if (secondaryTilesetLabel == this->editor->layout->tileset_secondary_label) { this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); - Scripting::cb_TilesetUpdated(secondaryTilesetLabel); updated = true; } else { this->editor->project->getTileset(secondaryTilesetLabel, true); } - if (updated) + if (updated) { redrawMapScene(); + Scripting::cb_TilesetsChanged(primaryTilesetLabel, secondaryTilesetLabel); + } } void MainWindow::onMapRulerStatusChanged(const QString &status) { diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 08bfc9c5..c7b6d386 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -4,6 +4,7 @@ #include "editcommands.h" #include "config.h" #include "imageproviders.h" +#include "scriptutility.h" // TODO: "tilesetNeedsRedraw" is used when redrawing the map after // changing a metatile's tiles via script. It is unnecessarily @@ -26,7 +27,7 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) { if (this->tilesetEditor) this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label); if (this->editor->metatile_selector_item) - this->editor->metatile_selector_item->draw(); + this->editor->metatile_selector_item->refresh(); if (this->editor->selected_border_metatiles_item) this->editor->selected_border_metatiles_item->draw(); if (this->editor->current_metatile_selection_item) @@ -332,7 +333,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { if (this->tilesetEditor) { this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label); } - this->editor->metatile_selector_item->draw(); + this->editor->metatile_selector_item->refresh(); this->editor->selected_border_metatiles_item->draw(); this->editor->map_item->draw(true); this->editor->updateMapBorder(); @@ -444,7 +445,7 @@ QJSValue MainWindow::getSecondaryTilesetPalettes() { } void MainWindow::refreshAfterPalettePreviewChange() { - this->editor->metatile_selector_item->draw(); + this->editor->metatile_selector_item->refresh(); this->editor->selected_border_metatiles_item->draw(); this->editor->map_item->draw(true); this->editor->updateMapBorder(); @@ -807,6 +808,32 @@ QJSValue MainWindow::getTilePixels(int tileId) { return pixelArray; } +QList MainWindow::getMetatileLayerOrder() const { + if (!this->editor || !this->editor->layout) + return QList(); + return this->editor->layout->metatileLayerOrder(); +} + +void MainWindow::setMetatileLayerOrder(const QList &order) { + if (!this->editor || !this->editor->layout || !ScriptUtility::validateMetatileLayerOrder(order)) + return; + this->editor->layout->setMetatileLayerOrder(order); + this->refreshAfterPalettePreviewChange(); +} + +QList MainWindow::getMetatileLayerOpacity() const { + if (!this->editor || !this->editor->layout) + return QList(); + return this->editor->layout->metatileLayerOpacity(); +} + +void MainWindow::setMetatileLayerOpacity(const QList &opacities) { + if (!this->editor || !this->editor->layout) + return; + this->editor->layout->setMetatileLayerOpacity(opacities); + this->refreshAfterPalettePreviewChange(); +} + //===================== // Editing map header //===================== diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index 4e183659..ffe7743a 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -294,11 +294,7 @@ void MapView::addTileImage(int x, int y, const Tile &tile, bool setTransparency, void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparency, int layer) { if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; - QImage image = getMetatileImage(static_cast(metatileId), - this->editor->layout->tileset_primary, - this->editor->layout->tileset_secondary, - this->editor->layout->metatileLayerOrder, - this->editor->layout->metatileLayerOpacity); + QImage image = getMetatileImage(static_cast(metatileId), this->editor->layout); if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index dfc65617..900c8701 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -201,46 +201,36 @@ QList ScriptUtility::getCustomScripts() { } QList ScriptUtility::getMetatileLayerOrder() { - if (!window || !window->editor || !window->editor->layout) - return QList(); - return window->editor->layout->metatileLayerOrder; + return Layout::defaultMetatileLayerOrder(); } -void ScriptUtility::setMetatileLayerOrder(QList order) { - if (!window || !window->editor || !window->editor->layout) - return; - +bool ScriptUtility::validateMetatileLayerOrder(const QList &order) { const int numLayers = 3; - int size = order.size(); - if (size < numLayers) { - logError(QString("Metatile layer order has insufficient elements (%1), needs at least %2.").arg(size).arg(numLayers)); - return; - } - bool invalid = false; - for (int i = 0; i < numLayers; i++) { + bool valid = true; + for (int i = 0; i < qMin(order.length(), numLayers); i++) { int layer = order.at(i); if (layer < 0 || layer >= numLayers) { logError(QString("'%1' is not a valid metatile layer order value, must be in range 0-%2.").arg(layer).arg(numLayers - 1)); - invalid = true; + valid = false; } } - if (invalid) return; + return valid; +} - window->editor->layout->metatileLayerOrder = order; - window->refreshAfterPalettePreviewChange(); +void ScriptUtility::setMetatileLayerOrder(const QList &order) { + if (!validateMetatileLayerOrder(order)) + return; + Layout::setDefaultMetatileLayerOrder(order); + if (window) window->refreshAfterPalettePreviewChange(); } QList ScriptUtility::getMetatileLayerOpacity() { - if (!window || !window->editor || !window->editor->layout) - return QList(); - return window->editor->layout->metatileLayerOpacity; + return Layout::defaultMetatileLayerOpacity(); } -void ScriptUtility::setMetatileLayerOpacity(QList order) { - if (!window || !window->editor || !window->editor->layout) - return; - window->editor->layout->metatileLayerOpacity = order; - window->refreshAfterPalettePreviewChange(); +void ScriptUtility::setMetatileLayerOpacity(const QList &opacities) { + Layout::setDefaultMetatileLayerOpacity(opacities); + if (window) window->refreshAfterPalettePreviewChange(); } QList ScriptUtility::getMapNames() { diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 93823de2..5de63221 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -16,7 +16,7 @@ const QMap callbackFunctions = { {OnMapResized, "onMapResized"}, {OnBorderResized, "onBorderResized"}, {OnMapShifted, "onMapShifted"}, - {OnTilesetUpdated, "onTilesetUpdated"}, + {OnTilesetsChanged, "onTilesetsChanged"}, {OnMainTabChanged, "onMainTabChanged"}, {OnMapViewTabChanged, "onMapViewTabChanged"}, {OnBorderVisibilityToggled, "onBorderVisibilityToggled"}, @@ -301,13 +301,14 @@ void Scripting::cb_MapShifted(int xDelta, int yDelta) { instance->invokeCallback(OnMapShifted, args); } -void Scripting::cb_TilesetUpdated(QString tilesetName) { +void Scripting::cb_TilesetsChanged(const QString &primaryTilesetName, const QString &secondaryTilesetName) { if (!instance) return; QJSValueList args { - tilesetName, + primaryTilesetName, + secondaryTilesetName }; - instance->invokeCallback(OnTilesetUpdated, args); + instance->invokeCallback(OnTilesetsChanged, args); } void Scripting::cb_MainTabChanged(int oldTab, int newTab) { diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index e793e466..9d6ab07f 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -46,12 +46,7 @@ void BorderMetatilesPixmapItem::draw() { for (int j = 0; j < height; j++) { int x = i * 16; int y = j * 16; - QImage metatile_image = getMetatileImage( - layout->getBorderMetatileId(i, j), - layout->tileset_primary, - layout->tileset_secondary, - layout->metatileLayerOrder, - layout->metatileLayerOpacity); + QImage metatile_image = getMetatileImage(layout->getBorderMetatileId(i, j), layout); QPoint metatile_origin = QPoint(x, y); painter.drawImage(metatile_origin, metatile_image); } diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp index e8b16f49..dcc3cb24 100644 --- a/src/ui/currentselectedmetatilespixmapitem.cpp +++ b/src/ui/currentselectedmetatilespixmapitem.cpp @@ -17,12 +17,7 @@ QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) { int index = j * selection.dimensions.x() + i; MetatileSelectionItem item = selection.metatileItems.at(index); if (item.enabled) { - QImage metatile_image = getMetatileImage( - item.metatileId, - layout->tileset_primary, - layout->tileset_secondary, - layout->metatileLayerOrder, - layout->metatileLayerOpacity); + QImage metatile_image = getMetatileImage(item.metatileId, layout); painter.drawImage(metatile_origin, metatile_image); } } diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 3f7b8fb8..4072baf8 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -13,6 +13,25 @@ QImage getCollisionMetatileImage(int collision, int elevation) { return image ? *image : QImage(); } +QImage getMetatileImage(uint16_t metatileId, Layout *layout, bool useTruePalettes) { + Metatile* metatile = Tileset::getMetatile(metatileId, + layout ? layout->tileset_primary : nullptr, + layout ? layout->tileset_secondary : nullptr); + return getMetatileImage(metatile, layout, useTruePalettes); +} + +QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes) { + if (!layout) { + return getMetatileImage(metatile, nullptr, nullptr, {}, {}, useTruePalettes); + } + return getMetatileImage(metatile, + layout->tileset_primary, + layout->tileset_secondary, + layout->metatileLayerOrder(), + layout->metatileLayerOpacity(), + useTruePalettes); +} + QImage getMetatileImage( uint16_t metatileId, Tileset *primaryTileset, @@ -21,13 +40,12 @@ QImage getMetatileImage( const QList &layerOpacity, bool useTruePalettes) { - Metatile* metatile = Tileset::getMetatile(metatileId, primaryTileset, secondaryTileset); - if (!metatile) { - QImage metatile_image(16, 16, QImage::Format_RGBA8888); - metatile_image.fill(Qt::magenta); - return metatile_image; - } - return getMetatileImage(metatile, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes); + return getMetatileImage(Tileset::getMetatile(metatileId, primaryTileset, secondaryTileset), + primaryTileset, + secondaryTileset, + layerOrder, + layerOpacity, + useTruePalettes); } QImage getMetatileImage( @@ -38,7 +56,9 @@ QImage getMetatileImage( const QList &layerOpacity, bool useTruePalettes) { - QImage metatile_image(16, 16, QImage::Format_RGBA8888); + const int numTilesWide = 2; + const int numTilesTall = 2; + QImage metatile_image(8 * numTilesWide, 8 * numTilesTall, QImage::Format_RGBA8888); if (!metatile) { metatile_image.fill(Qt::magenta); return metatile_image; @@ -54,18 +74,17 @@ QImage getMetatileImage( metatile_image.fill(projectConfig.setTransparentPixelsBlack ? QColor("black") : QColor(palettes.value(0).value(0))); QPainter metatile_painter(&metatile_image); - const int numLayers = 3; // When rendering, metatiles always have 3 layers - uint32_t layerType = metatile->layerType(); - for (int layer = 0; layer < numLayers; layer++) - for (int y = 0; y < 2; y++) - for (int x = 0; x < 2; x++) { - int l = layerOrder.size() >= numLayers ? layerOrder[layer] : layer; + uint32_t layerType = metatile->layerType(); + const int numTilesPerLayer = numTilesWide * numTilesTall; + for (const auto &layer : layerOrder) + for (int y = 0; y < numTilesTall; y++) + for (int x = 0; x < numTilesWide; x++) { // Get the tile to render next Tile tile; - int tileOffset = (y * 2) + x; + int tileOffset = (y * numTilesWide) + x; if (projectConfig.tripleLayerMetatilesEnabled) { - tile = metatile->tiles.value(tileOffset + (l * 4)); + tile = metatile->tiles.value(tileOffset + (layer * numTilesPerLayer)); } else { // "Vanilla" metatiles only have 8 tiles, but render 12. // The remaining 4 tiles are rendered using user-specified tiles depending on layer type. @@ -73,22 +92,22 @@ QImage getMetatileImage( { default: case Metatile::LayerType::Normal: - if (l == 0) + if (layer == 0) tile = Tile(projectConfig.unusedTileNormal); else // Tiles are on layers 1 and 2 - tile = metatile->tiles.value(tileOffset + ((l - 1) * 4)); + tile = metatile->tiles.value(tileOffset + ((layer - 1) * numTilesPerLayer)); break; case Metatile::LayerType::Covered: - if (l == 2) + if (layer == 2) tile = Tile(projectConfig.unusedTileCovered); else // Tiles are on layers 0 and 1 - tile = metatile->tiles.value(tileOffset + (l * 4)); + tile = metatile->tiles.value(tileOffset + (layer * numTilesPerLayer)); break; case Metatile::LayerType::Split: - if (l == 1) + if (layer == 1) tile = Tile(projectConfig.unusedTileSplit); else // Tiles are on layers 0 and 2 - tile = metatile->tiles.value(tileOffset + ((l == 0 ? 0 : 1) * 4)); + tile = metatile->tiles.value(tileOffset + ((layer == 0 ? 0 : 1) * numTilesPerLayer)); break; } } @@ -112,7 +131,7 @@ QImage getMetatileImage( } QPoint origin = QPoint(x*8, y*8); - float opacity = layerOpacity.size() >= numLayers ? layerOpacity[l] : 1.0; + float opacity = layerOpacity.value(layer, 1.0); if (opacity < 1.0) { int alpha = 255 * opacity; for (int c = 0; c < tile_image.colorCount(); c++) { diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 828c73cd..321f2925 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -11,12 +11,12 @@ QPoint MetatileSelector::getSelectionDimensions() { int MetatileSelector::numPrimaryMetatilesRounded() const { // We round up the number of primary metatiles to keep the tilesets on separate rows. - return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; + return ceil((double)this->primaryTileset()->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; } void MetatileSelector::updateBasePixmap() { int primaryLength = this->numPrimaryMetatilesRounded(); - int length_ = primaryLength + this->secondaryTileset->numMetatiles(); + int length_ = primaryLength + this->secondaryTileset()->numMetatiles(); int height_ = length_ / this->numMetatilesWide; if (length_ % this->numMetatilesWide != 0) { height_++; @@ -25,11 +25,11 @@ void MetatileSelector::updateBasePixmap() { image.fill(Qt::magenta); QPainter painter(&image); for (int i = 0; i < length_; i++) { - int tile = i; + int metatileId = i; if (i >= primaryLength) { - tile += Project::getNumMetatilesPrimary() - primaryLength; + metatileId += Project::getNumMetatilesPrimary() - primaryLength; } - QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, layout->metatileLayerOrder, layout->metatileLayerOpacity); + QImage metatile_image = getMetatileImage(metatileId, this->layout); int map_y = i / this->numMetatilesWide; int map_x = i % this->numMetatilesWide; QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight); @@ -53,7 +53,7 @@ void MetatileSelector::drawSelection() { } bool MetatileSelector::select(uint16_t metatileId) { - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) return false; + if (!this->layout->metatileIsValid(metatileId)) return false; this->externalSelection = false; this->prefabSelection = false; this->selection = MetatileSelection{ @@ -73,9 +73,8 @@ void MetatileSelector::selectFromMap(uint16_t metatileId, uint16_t collision, ui this->setExternalSelection(1, 1, {metatileId}, {movePermissions}); } -void MetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { - this->primaryTileset = primaryTileset; - this->secondaryTileset = secondaryTileset; +void MetatileSelector::setLayout(Layout *layout) { + this->layout = layout; if (this->externalSelection) this->updateExternalSelectedMetatiles(); else @@ -85,6 +84,10 @@ void MetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTi draw(); } +void MetatileSelector::refresh() { + setLayout(this->layout); +} + MetatileSelection MetatileSelector::getMetatileSelection() { return selection; } @@ -104,7 +107,7 @@ void MetatileSelector::setExternalSelection(int width, int height, QListselection.collisionItems.append(CollisionSelectionItem{true, collision.first, collision.second}); uint16_t metatileId = metatiles.at(i); this->externalSelectedMetatiles.append(metatileId); - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + if (!this->layout->metatileIsValid(metatileId)) metatileId = 0; this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId}); } @@ -127,7 +130,7 @@ void MetatileSelector::setPrefabSelection(MetatileSelection selection) { } bool MetatileSelector::positionIsValid(const QPoint &pos) const { - return Tileset::metatileIsValid(getMetatileId(pos.x(), pos.y()), this->primaryTileset, this->secondaryTileset); + return this->layout->metatileIsValid(getMetatileId(pos.x(), pos.y())); } void MetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { @@ -188,7 +191,7 @@ void MetatileSelector::updateSelectedMetatiles() { for (int j = 0; j < this->selection.dimensions.y(); j++) { for (int i = 0; i < this->selection.dimensions.x(); i++) { uint16_t metatileId = this->getMetatileId(origin.x() + i, origin.y() + j); - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + if (!this->layout->metatileIsValid(metatileId)) metatileId = 0; this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId}); } @@ -201,7 +204,7 @@ void MetatileSelector::updateExternalSelectedMetatiles() { this->selection.dimensions = QPoint(this->externalSelectionWidth, this->externalSelectionHeight); for (int i = 0; i < this->externalSelectedMetatiles.count(); ++i) { uint16_t metatileId = this->externalSelectedMetatiles.at(i); - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + if (!this->layout->metatileIsValid(metatileId)) metatileId = 0; this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId}); } @@ -219,9 +222,7 @@ uint16_t MetatileSelector::getMetatileId(int x, int y) const { } QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) { - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) - { - // Invalid metatile id. + if (!this->layout->metatileIsValid(metatileId)) { return QPoint(0, 0); } @@ -237,7 +238,3 @@ QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) { pos.ry() = (pos.y() * this->cellHeight) + (this->cellHeight / 2); return pos; } - -void MetatileSelector::setLayout(Layout *layout) { - this->layout = layout; -} diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 5026a49b..79888681 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -60,8 +60,8 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet metatileId, this->primaryTileset, this->secondaryTileset, - this->layout->metatileLayerOrder, - this->layout->metatileLayerOpacity, + this->layout->metatileLayerOrder(), + this->layout->metatileLayerOpacity(), true) .scaled(this->cellWidth, this->cellHeight); int map_y = i / this->numMetatilesWide; @@ -81,8 +81,8 @@ void TilesetEditorMetatileSelector::drawMetatile(uint16_t metatileId) { metatileId, this->primaryTileset, this->secondaryTileset, - this->layout->metatileLayerOrder, - this->layout->metatileLayerOpacity, + this->layout->metatileLayerOrder(), + this->layout->metatileLayerOpacity(), true) .scaled(this->cellWidth, this->cellHeight); painter.drawImage(QPoint(pos.x() * this->cellWidth, pos.y() * this->cellHeight), metatile_image); From 06b22e0c2e792d9f87268ad4318ecdaac8d386cf Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Jul 2025 16:14:31 -0400 Subject: [PATCH 20/71] Add direct link to manual under Help --- CHANGELOG.md | 3 +++ forms/mainwindow.ui | 6 ++++++ include/mainwindow.h | 1 + src/mainwindow.cpp | 5 +++++ 4 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a9b266..bec4738a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project somewhat adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The MAJOR version number is bumped when there are **"Breaking Changes"** in the pret projects. For more on this, see [the manual page on breaking changes](https://huderlem.github.io/porymap/manual/breaking-changes.html). ## [Unreleased] +### Added +- A link to Porymap's manual is now available under `Help`. + ### Changed - The scroll position of the map view now remains the same between the Connections tab and the Map/Events tabs. - The Move tool now behaves more like a traditional pan tool (with no momentum). diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 996dff3b..30520979 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2926,6 +2926,7 @@ Help + @@ -3267,6 +3268,11 @@ Alt+Right + + + Open Manual + + diff --git a/include/mainwindow.h b/include/mainwindow.h index 3b3003aa..c28e78f3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -286,6 +286,7 @@ private slots: void on_spinBox_SelectedCollision_valueChanged(int collision); void on_actionRegion_Map_Editor_triggered(); void on_actionPreferences_triggered(); + void on_actionOpen_Manual_triggered(); void on_actionCheck_for_Updates_triggered(); void togglePreferenceSpecificUi(); void on_actionProject_Settings_triggered(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 238d9706..7a316459 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2905,6 +2905,11 @@ void MainWindow::on_actionOpen_Config_Folder_triggered() { QDesktopServices::openUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } +void MainWindow::on_actionOpen_Manual_triggered() { + static const QUrl url("https://huderlem.github.io/porymap/"); + QDesktopServices::openUrl(url); +} + void MainWindow::on_actionPreferences_triggered() { if (!preferenceEditor) { preferenceEditor = new PreferenceEditor(this); From a2bb2efa3e4bad05cdb2d3c035616c0d0d6aeaf9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Jul 2025 22:24:01 -0400 Subject: [PATCH 21/71] Fix UIntSpinBox disallowing intermediate inputs outside of range --- src/ui/uintspinbox.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp index 53f6df78..095d754e 100644 --- a/src/ui/uintspinbox.cpp +++ b/src/ui/uintspinbox.cpp @@ -113,11 +113,16 @@ void UIntSpinBox::onEditFinished() { // Valid input newValue = this->valueFromText(input); } else if (state == QValidator::Intermediate) { - // User has deleted all the number text. - // If they did this by selecting all text and then hitting delete - // make sure to put the cursor back in front of the prefix. - newValue = m_minimum; - this->lineEdit()->setCursorPosition(m_prefix.length()); + if (input == m_prefix) { + // User has deleted all the number text. + // If they did this by selecting all text and then hitting delete + // make sure to put the cursor back in front of the prefix. + newValue = m_minimum; + this->lineEdit()->setCursorPosition(m_prefix.length()); + } else { + // Other intermediate inputs (values outside of acceptable range) should be ignored. + return; + } } if (newValue != m_value) { m_value = newValue; @@ -160,10 +165,14 @@ QValidator::State UIntSpinBox::validate(QString &input, int &pos) const { bool ok; uint32_t num = copy.toUInt(&ok, m_displayIntegerBase); - if (!ok || num < m_minimum || num > m_maximum) + if (!ok) return QValidator::Invalid; input += copy.toUpper(); + + if (num < m_minimum || num > m_maximum) + return QValidator::Intermediate; + return QValidator::Acceptable; } From ef86103f78c4b318a435e327ac3937c5383cc295 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 8 Jul 2025 14:16:31 -0400 Subject: [PATCH 22/71] Add setting to autocomplete with common scripts but not all scripts --- forms/preferenceeditor.ui | 69 +++++++++++++++++++++++---------- include/config.h | 10 ++++- include/core/map.h | 2 +- include/editor.h | 3 +- include/project.h | 6 ++- include/ui/preferenceeditor.h | 3 +- src/config.cpp | 9 ++++- src/editor.cpp | 34 ++++++++++------- src/project.cpp | 72 +++++++++++++++++++++++------------ src/ui/eventframes.cpp | 4 +- src/ui/preferenceeditor.cpp | 25 +++++++++--- 11 files changed, 163 insertions(+), 74 deletions(-) diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index d3ec7cea..63017212 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -104,7 +104,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -159,7 +159,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -209,7 +209,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -253,13 +253,42 @@ - - - <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. Enabling this setting will make Porymap's startup slower.</p></body></html> - - - Autocomplete Script labels using all possible scripts + + + Script label autocomplete + + + + + <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. This is the slowest option for Porymap's project opening.</p></body></html> + + + All possible scripts + + + + + + + <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include script labels from the current map's scripts file, scripts in-use by the map's other events, and all script files in the <span style=" font-family:'SFMono-Regular','Menlo','Monaco','Consolas','Liberation Mono','Courier New','Courier','monospace'; font-size:11px; color:#e74c3c; background-color:#ffffff;">data_scripts_folders </span>folder.</p></body></html> + + + Current map, and global script files + + + + + + + <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will only include script labels from the current map's scripts file and scripts in-use by the map's other events. This is the fastest option for Porymap's project opening.</p></body></html> + + + Current map only + + + + @@ -294,7 +323,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -323,7 +352,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -333,13 +362,13 @@ 0 0 - 476 - 343 + 495 + 376 - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize @@ -347,7 +376,7 @@ <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop true @@ -380,7 +409,7 @@ <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop true @@ -410,10 +439,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -426,7 +455,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -447,7 +476,7 @@ - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/include/config.h b/include/config.h index 234fa76d..6e9bbfca 100644 --- a/include/config.h +++ b/include/config.h @@ -27,6 +27,12 @@ extern const QVersionNumber porymapVersion; #define CONFIG_BACKWARDS_COMPATABILITY +enum ScriptAutocompleteMode { + MapOnly, + MapAndCommon, + All, +}; + class KeyValueConfigBase { public: @@ -101,7 +107,7 @@ public: this->textEditorGotoLine = ""; this->paletteEditorBitDepth = 24; this->projectSettingsTab = 0; - this->loadAllEventScripts = false; + this->scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly; this->warpBehaviorWarningDisabled = false; this->eventDeleteWarningDisabled = false; this->eventOverlayEnabled = false; @@ -168,7 +174,7 @@ public: QString textEditorGotoLine; int paletteEditorBitDepth; int projectSettingsTab; - bool loadAllEventScripts; + ScriptAutocompleteMode scriptAutocompleteMode; bool warpBehaviorWarningDisabled; bool eventDeleteWarningDisabled; bool eventOverlayEnabled; diff --git a/include/core/map.h b/include/core/map.h index d5e28075..1d460cf1 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -149,7 +149,7 @@ signals: void modified(); void scriptsModified(); void mapDimensionsChanged(const QSize &size); - void openScriptRequested(QString label); + void openScriptRequested(const QString &label); void connectionAdded(MapConnection*); void connectionRemoved(MapConnection*); void layoutChanged(); diff --git a/include/editor.h b/include/editor.h index 414356d1..9de1bc94 100644 --- a/include/editor.h +++ b/include/editor.h @@ -209,7 +209,8 @@ public: public slots: void openMapScripts() const; - void openScript(const QString &scriptLabel) const; + bool openScript(const QString &scriptLabel) const; + bool openScriptInFile(const QString &scriptLabel, const QString &filepath) const; void openProjectInTextEditor() const; void maskNonVisibleConnectionTiles(); void onBorderMetatilesChanged(); diff --git a/include/project.h b/include/project.h index ca7d9bbe..45df8143 100644 --- a/include/project.h +++ b/include/project.h @@ -215,7 +215,11 @@ public: static QString getScriptFileExtension(bool usePoryScript); QString getScriptDefaultString(bool usePoryScript, QString mapName) const; - QStringList getEventScriptsFilepaths() const; + + QStringList getAllEventScriptsFilepaths() const; + QStringList getMapScriptsFilepaths() const; + QStringList getCommonEventScriptsFilepaths() const; + QStringList findScriptsFiles(const QString &searchDir, const QStringList &fileNames = {"*"}) const; void insertGlobalScriptLabels(QStringList &scriptLabels) const; QString getDefaultPrimaryTilesetLabel() const; diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h index 74181c37..24b6253f 100644 --- a/include/ui/preferenceeditor.h +++ b/include/ui/preferenceeditor.h @@ -2,6 +2,7 @@ #define PREFERENCES_H #include +#include "config.h" class NoScrollComboBox; class QAbstractButton; @@ -23,7 +24,7 @@ public: signals: void preferencesSaved(); void themeChanged(const QString &theme); - void scriptSettingsChanged(bool on); + void scriptSettingsChanged(ScriptAutocompleteMode mode); void reloadProjectRequested(); private: diff --git a/src/config.cpp b/src/config.cpp index 4f5188bd..666ec12c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -428,8 +428,13 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "project_settings_tab") { this->projectSettingsTab = getConfigInteger(key, value, 0); +#ifdef CONFIG_BACKWARDS_COMPATABILITY + // Old setting replaced by script_autocomplete_mode } else if (key == "load_all_event_scripts") { - this->loadAllEventScripts = getConfigBool(key, value); + this->scriptAutocompleteMode = getConfigBool(key, value) ? ScriptAutocompleteMode::All : ScriptAutocompleteMode::MapOnly; +#endif + } else if (key == "script_autocomplete_mode") { + this->scriptAutocompleteMode = static_cast(getConfigInteger(key, value, ScriptAutocompleteMode::MapOnly, ScriptAutocompleteMode::All)); } else if (key == "warp_behavior_warning_disabled") { this->warpBehaviorWarningDisabled = getConfigBool(key, value); } else if (key == "event_delete_warning_disabled") { @@ -551,7 +556,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("text_editor_goto_line", this->textEditorGotoLine); map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth)); map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); - map.insert("load_all_event_scripts", QString::number(this->loadAllEventScripts)); + map.insert("script_autocomplete_mode", QString::number(this->scriptAutocompleteMode)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); map.insert("event_overlay_enabled", QString::number(this->eventOverlayEnabled)); diff --git a/src/editor.cpp b/src/editor.cpp index 5ad15a84..a0635d38 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2355,21 +2355,29 @@ void Editor::openMapScripts() const { openInTextEditor(map->getScriptsFilepath()); } -void Editor::openScript(const QString &scriptLabel) const { +bool Editor::openScript(const QString &scriptLabel) const { // Find the location of scriptLabel. - QStringList scriptPaths(map->getScriptsFilepath()); - scriptPaths << project->getEventScriptsFilepaths(); - int lineNum = 0; - QString scriptPath = scriptPaths.first(); - for (const auto &path : scriptPaths) { - lineNum = ParseUtil::getScriptLineNumber(path, scriptLabel); - if (lineNum != 0) { - scriptPath = path; - break; - } - } + // First, try the current map's scripts file. + if (openScriptInFile(scriptLabel, map->getScriptsFilepath())) + return true; - openInTextEditor(scriptPath, lineNum); + // Script is not in the current map's scripts file. + // Search all possible script files. + const QStringList paths = project->getAllEventScriptsFilepaths(); + for (const auto &path : paths) { + if (openScriptInFile(scriptLabel, path)) + return true; + } + return false; +} + +bool Editor::openScriptInFile(const QString &scriptLabel, const QString &filepath) const { + int lineNum = ParseUtil::getScriptLineNumber(filepath, scriptLabel); + if (lineNum == 0) + return false; + + openInTextEditor(filepath, lineNum); + return true; } void Editor::openMapJson(const QString &mapName) const { diff --git a/src/project.cpp b/src/project.cpp index 75b32474..7188b5ef 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2970,13 +2970,19 @@ bool Project::readGlobalConstants() { bool Project::readEventScriptLabels() { this->globalScriptLabels.clear(); - if (porymapConfig.loadAllEventScripts) { - for (const auto &filePath : getEventScriptsFilepaths()) - this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); - - this->globalScriptLabels.sort(Qt::CaseInsensitive); - this->globalScriptLabels.removeDuplicates(); + QStringList paths; + if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::All) { + paths = getAllEventScriptsFilepaths(); + } else if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::MapAndCommon) { + paths = getCommonEventScriptsFilepaths(); } + + for (const auto &path : paths) { + this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(path); + } + this->globalScriptLabels.sort(Qt::CaseInsensitive); + this->globalScriptLabels.removeDuplicates(); + emit eventScriptLabelsRead(); return true; } @@ -3012,30 +3018,46 @@ QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) con return QString("%1_MapScripts::\n\t.byte 0\n").arg(mapName); } -QStringList Project::getEventScriptsFilepaths() const { - QStringList filePaths(QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts))); - const QString scriptsDir = QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_scripts_folders)); - const QString mapsDir = QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_map_folders)); +QStringList Project::getAllEventScriptsFilepaths() const { + return getMapScriptsFilepaths() + getCommonEventScriptsFilepaths(); +} - if (projectConfig.usePoryScript) { - QDirIterator it_pory_shared(scriptsDir, {"*.pory"}, QDir::Files); - while (it_pory_shared.hasNext()) - filePaths << it_pory_shared.next(); +// Get the paths for all "scripts.inc" / "scripts.pory" files in the data/maps/*/ folders. +QStringList Project::getMapScriptsFilepaths() const { + return findScriptsFiles(projectConfig.getFilePath(ProjectFilePath::data_map_folders), {"scripts"}); +} - QDirIterator it_pory_maps(mapsDir, {"scripts.pory"}, QDir::Files, QDirIterator::Subdirectories); - while (it_pory_maps.hasNext()) - filePaths << it_pory_maps.next(); +// Get the paths for all "*.inc" / "*.pory" files in the data/scripts/ folder, + data/event_scripts.s. +QStringList Project::getCommonEventScriptsFilepaths() const { + QStringList paths = { QDir::cleanPath(this->root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts)) }; + return paths + findScriptsFiles(projectConfig.getFilePath(ProjectFilePath::data_scripts_folders)); +} + +QStringList Project::findScriptsFiles(const QString &searchDir, const QStringList &fileNames) const { + QStringList paths; + + QString dir = searchDir; + if (!dir.startsWith(this->root)) { + dir = QDir::cleanPath(this->root + "/" + dir); } - QDirIterator it_inc_shared(scriptsDir, {"*.inc"}, QDir::Files); - while (it_inc_shared.hasNext()) - filePaths << it_inc_shared.next(); + QStringList filters; + for (const auto &s : fileNames) { + filters.append(s + getScriptFileExtension(false)); + if (projectConfig.usePoryScript) { + filters.append(s + getScriptFileExtension(true)); + } + } - QDirIterator it_inc_maps(mapsDir, {"scripts.inc"}, QDir::Files, QDirIterator::Subdirectories); - while (it_inc_maps.hasNext()) - filePaths << it_inc_maps.next(); - - return filePaths; + // TODO: Filter out .inc files that are generated by a .pory file. + // They won't cause problems for the user, but it'll create extra parsing work later. + if (!filters.isEmpty()) { + QDirIterator it(dir, filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (it.hasNext()) { + paths.append(it.next()); + } + } + return paths; } void Project::loadEventPixmap(Event *event, bool forceLoad) { diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 6457e564..a1af9e97 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -201,8 +201,8 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj QStringList scripts = map->getScriptLabels(this->event->getEventGroup()); populateDropdown(combo, scripts); - // Depending on the settings, the autocomplete may also contain all global scripts. - if (project && porymapConfig.loadAllEventScripts) { + // Depending on the settings, the autocomplete may also contain scripts from outside the map. + if (project && porymapConfig.scriptAutocompleteMode != ScriptAutocompleteMode::MapOnly) { project->insertGlobalScriptLabels(scripts); } diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index 4f4b0929..5765ba01 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -1,6 +1,5 @@ #include "preferenceeditor.h" #include "ui_preferenceeditor.h" -#include "config.h" #include "noscrollcombobox.h" #include "message.h" @@ -76,7 +75,14 @@ void PreferenceEditor::updateFields() { ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch); ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates); ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled); - ui->checkBox_AutocompleteAllScripts->setChecked(porymapConfig.loadAllEventScripts); + + if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::MapOnly) { + ui->radioButton_AutocompleteMapScripts->setChecked(true); + } else if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::MapAndCommon) { + ui->radioButton_AutocompleteCommonScripts->setChecked(true); + } else if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::All) { + ui->radioButton_AutocompleteAllScripts->setChecked(true); + } auto logTypeEnd = porymapConfig.statusBarLogTypes.end(); ui->checkBox_StatusErrors->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_ERROR) != logTypeEnd); @@ -95,11 +101,18 @@ void PreferenceEditor::saveFields() { porymapConfig.theme = themeSelector->currentText(); changedTheme = true; } - bool loadAllEventScripts = ui->checkBox_AutocompleteAllScripts->isChecked(); - if (loadAllEventScripts != porymapConfig.loadAllEventScripts) { - porymapConfig.loadAllEventScripts = loadAllEventScripts; - emit scriptSettingsChanged(loadAllEventScripts); + + auto scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly; + if (ui->radioButton_AutocompleteCommonScripts->isChecked()) { + scriptAutocompleteMode = ScriptAutocompleteMode::MapAndCommon; + } else if (ui->radioButton_AutocompleteAllScripts->isChecked()) { + scriptAutocompleteMode = ScriptAutocompleteMode::All; } + if (scriptAutocompleteMode != porymapConfig.scriptAutocompleteMode) { + porymapConfig.scriptAutocompleteMode = scriptAutocompleteMode; + emit scriptSettingsChanged(scriptAutocompleteMode); + } + porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape; porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text(); porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text(); From 38e27722137f85a38bc0907928bb48529f36f8ec Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 2 Jul 2025 14:23:57 -0400 Subject: [PATCH 23/71] Add new metatile image export window --- forms/metatileimageexporter.ui | 526 +++++++++++++++++++++ forms/tileseteditor.ui | 185 ++++---- include/config.h | 9 +- include/core/maplayout.h | 26 +- include/core/metatile.h | 8 + include/core/tile.h | 5 + include/core/tileset.h | 1 + include/core/utility.h | 2 +- include/ui/imageproviders.h | 6 +- include/ui/metatileimageexporter.h | 101 ++++ include/ui/tileseteditor.h | 20 +- include/ui/tileseteditormetatileselector.h | 4 - include/ui/uintspinbox.h | 3 + porymap.pro | 3 + src/config.cpp | 16 +- src/core/maplayout.cpp | 23 +- src/core/metatile.cpp | 9 +- src/core/tileset.cpp | 10 +- src/core/utility.cpp | 2 +- src/editor.cpp | 4 +- src/main.cpp | 2 + src/project.cpp | 8 +- src/scriptapi/apiutility.cpp | 8 +- src/ui/imageproviders.cpp | 85 +++- src/ui/metatileimageexporter.cpp | 321 +++++++++++++ src/ui/metatilelayersitem.cpp | 4 +- src/ui/metatileselector.cpp | 23 +- src/ui/movablerect.cpp | 4 +- src/ui/projectsettingseditor.cpp | 7 +- src/ui/resizelayoutpopup.cpp | 2 +- src/ui/selectablepixmapitem.cpp | 4 +- src/ui/tileseteditor.cpp | 90 ++-- src/ui/tileseteditormetatileselector.cpp | 54 +-- src/ui/uintspinbox.cpp | 25 +- src/ui/wildmonchart.cpp | 2 +- 35 files changed, 1276 insertions(+), 326 deletions(-) create mode 100644 forms/metatileimageexporter.ui create mode 100644 include/ui/metatileimageexporter.h create mode 100644 src/ui/metatileimageexporter.cpp diff --git a/forms/metatileimageexporter.ui b/forms/metatileimageexporter.ui new file mode 100644 index 00000000..7c6bfe49 --- /dev/null +++ b/forms/metatileimageexporter.ui @@ -0,0 +1,526 @@ + + + MetatileImageExporter + + + + 0 + 0 + 649 + 601 + + + + Export Metatiles Image + + + true + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::ClickFocus + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Shape::NoFrame + + + true + + + + + 0 + 0 + 304 + 532 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Tilesets + + + + 6 + + + 6 + + + + + <html><head/><body><p>If checked, automatically update the metatile range to include the full secondary tileset.</p></body></html> + + + Secondary Tileset + + + + + + + false + + + false + + + + + + + false + + + false + + + + + + + <html><head/><body><p>If checked, automatically update the metatile range to include the full primary tileset.</p></body></html> + + + Primary Tileset + + + true + + + + + + + Qt::Orientation::Horizontal + + + + 1 + 20 + + + + + + + + + + + false + + + Metatile Range + + + + 6 + + + 6 + + + + + Start + + + + + + + <html><head/><body><p>The metatile ID to start the rendered image at.</p></body></html> + + + + + + + End + + + + + + + <html><head/><body><p>The metatile ID to end the rendered image at.</p></body></html> + + + + + + + + + + <html><head/><body><p>Each metatile consists of 3 layers of tiles. These layers can be toggled here by clicking the checkbox, or rearranged by clicking and dragging them up or down in the list.</p></body></html> + + + Layers + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + true + + + QAbstractItemView::DragDropMode::InternalMove + + + Qt::DropAction::MoveAction + + + QListView::ResizeMode::Adjust + + + Qt::AlignmentFlag::AlignVCenter + + + + + + + + + + Transparency + + + + + + <html><head/><body><p>If checked, transparent pixels in the image will be rendered with alpha of 0.</p></body></html> + + + Normal + + + + + + + <html><head/><body><p>If checked, transparent pixels in the image will be rendered as black. This is the default in-game behavior.</p></body></html> + + + Black + + + + + + + <html><head/><body><p>If checked, transparent pixels in the image will be rendered using the first color in tileset palette 0. This is the default behavior of the GBA.</p></body></html> + + + First palette color + + + + + + + + + + Miscellaneous + + + + 6 + + + 6 + + + + + <html><head/><body><p>If checked, display the placeholder tiles that are rendered for the unused layer in-game. For a given metatile only 2 of the 3 tile layers are used, and the 3rd layer is filled with these placeholder tiles. The unused layer and placeholder tile change depending on the metatile's layer type.</p></body></html> + + + Render placeholder metatiles + + + + + + + Width (metatiles) + + + + + + + <html><head/><body><p>Width of the output image in metatiles.</p></body></html> + + + + + + + Width (pixels) + + + + + + + <html><head/><body><p>Width of the output image in pixels. Automatically rounded up to a multiple of a metatile's pixel width.</p></body></html> + + + 16 + + + 128 + + + 16 + + + 128 + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + + + + + + + + + Reset + + + false + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + false + + + + + + + Save + + + false + + + + + + + + + + + + Preview + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents + + + true + + + Qt::AlignmentFlag::AlignCenter + + + + + 0 + 0 + 285 + 551 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + false + + + false + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored + + + QGraphicsView::DragMode::NoDrag + + + + + + + + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + UIntSpinBox + QAbstractSpinBox +
uintspinbox.h
+
+ + UIntHexSpinBox + UIntSpinBox +
uintspinbox.h
+
+ + ReorderableListWidget + QListWidget +
metatileimageexporter.h
+
+
+ + +
diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 6c696bf3..36aa92f7 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -11,7 +11,7 @@
- Qt::FocusPolicy::ClickFocus + Qt::ClickFocus Tileset Editor @@ -21,7 +21,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal false @@ -34,10 +34,10 @@ - QFrame::Shape::NoFrame + QFrame::NoFrame - QFrame::Shadow::Plain + QFrame::Plain @@ -58,14 +58,14 @@ true - Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop + Qt::AlignHCenter|Qt::AlignTop 0 0 - 239 + 253 659 @@ -88,17 +88,17 @@ - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff - Qt::Orientation::Vertical + Qt::Vertical @@ -121,7 +121,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -129,10 +129,10 @@ - QFrame::Shape::NoFrame + QFrame::NoFrame - QFrame::Shadow::Raised + QFrame::Raised @@ -162,14 +162,14 @@
- QFrame::Shape::NoFrame + QFrame::NoFrame - QFrame::Shadow::Raised + QFrame::Raised - QLayout::SizeConstraint::SetMinimumSize + QLayout::SetMinimumSize 0 @@ -255,7 +255,7 @@ - QComboBox::InsertPolicy::NoInsert + QComboBox::NoInsert
@@ -274,10 +274,10 @@
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff
@@ -290,17 +290,17 @@
- QComboBox::InsertPolicy::NoInsert + QComboBox::NoInsert
- Qt::Orientation::Horizontal + Qt::Horizontal - QSizePolicy::Policy::Maximum + QSizePolicy::Maximum @@ -319,7 +319,7 @@ - QComboBox::InsertPolicy::NoInsert + QComboBox::NoInsert @@ -361,7 +361,7 @@ - Qt::Orientation::Vertical + Qt::Vertical @@ -412,7 +412,7 @@ - Qt::LayoutDirection::LeftToRight + Qt::LeftToRight @@ -436,10 +436,10 @@ - Qt::Orientation::Vertical + Qt::Vertical - QSizePolicy::Policy::Fixed + QSizePolicy::Fixed @@ -464,10 +464,10 @@ - QFrame::Shape::NoFrame + QFrame::NoFrame - QFrame::Shadow::Plain + QFrame::Plain @@ -510,13 +510,13 @@ - QFrame::Shape::StyledPanel + QFrame::StyledPanel - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff @@ -526,7 +526,7 @@ - Qt::Orientation::Vertical + Qt::Vertical @@ -548,14 +548,14 @@ true - Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop + Qt::AlignHCenter|Qt::AlignTop 0 0 - 499 + 446 241 @@ -575,17 +575,17 @@ - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff - Qt::ScrollBarPolicy::ScrollBarAlwaysOff + Qt::ScrollBarAlwaysOff - Qt::Orientation::Vertical + Qt::Vertical @@ -602,7 +602,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -625,18 +625,34 @@ File + + + Export Tiles Image + + + + + + + Import Metatiles from Advance Map 1.92 + + + + + + + Import Tiles Image + + + + - - + + - - - - - - - + + @@ -685,16 +701,6 @@ Ctrl+S - - - Import Primary Tiles Image... - - - - - Import Secondary Tiles Image... - - Change Number of Metatiles... @@ -745,36 +751,6 @@ Ctrl+Y - - - Export Primary Tiles Image... - - - - - Export Secondary Tiles Image... - - - - - Import Primary Metatiles from Advance Map 1.92... - - - - - Import Secondary Metatiles from Advance Map 1.92... - - - - - Export Primary Metatiles Image... - - - - - Export Secondary Metatiles Image... - - Cut @@ -834,6 +810,41 @@ Show Raw Metatile Attributes + + + Primary... + + + + + Secondary... + + + + + Primary... + + + + + Secondary... + + + + + Primary... + + + + + Secondary... + + + + + Export Metatiles Image... + + diff --git a/include/config.h b/include/config.h index 234fa76d..51c11a74 100644 --- a/include/config.h +++ b/include/config.h @@ -54,7 +54,8 @@ protected: static bool getConfigBool(const QString &key, const QString &value); static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); - static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = Qt::black); + static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = QColor(Qt::black)); + static QString toConfigColor(const QColor &color); QString m_root; QString m_filename; @@ -82,7 +83,7 @@ public: this->collisionOpacity = 50; this->collisionZoom = 30; this->metatilesZoom = 30; - this->tilesetEditorMetatilesZoom = 45; + this->tilesetEditorMetatilesZoom = 30; this->tilesetEditorTilesZoom = 30; this->showPlayerView = false; this->showCursorTile = true; @@ -351,7 +352,7 @@ public: this->prefabImportPrompted = false; this->tilesetsHaveCallback = true; this->tilesetsHaveIsCompressed = true; - this->setTransparentPixelsBlack = true; + this->transparencyColor = QColor(Qt::black); this->preserveMatchingOnlyData = false; this->filePaths.clear(); this->eventIconPaths.clear(); @@ -426,7 +427,7 @@ public: bool prefabImportPrompted; bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; - bool setTransparentPixelsBlack; + QColor transparencyColor; bool preserveMatchingOnlyData; int metatileAttributesSize; uint32_t metatileBehaviorMask; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 280f3006..8091469a 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -65,14 +65,24 @@ public: } lastCommitBlocks; // to track map changes void setMetatileLayerOrder(const QList &layerOrder) { m_metatileLayerOrder = layerOrder; } - QList metatileLayerOrder() const; - static void setDefaultMetatileLayerOrder(const QList &layerOrder) { s_defaultMetatileLayerOrder = layerOrder; } - static QList defaultMetatileLayerOrder(); + const QList &metatileLayerOrder() const { + return !m_metatileLayerOrder.isEmpty() ? m_metatileLayerOrder : Layout::globalMetatileLayerOrder(); + } + static void setGlobalMetatileLayerOrder(const QList &layerOrder) { s_globalMetatileLayerOrder = layerOrder; } + static const QList &globalMetatileLayerOrder() { + static const QList defaultLayerOrder = {0, 1, 2}; + return !s_globalMetatileLayerOrder.isEmpty() ? s_globalMetatileLayerOrder : defaultLayerOrder; + } void setMetatileLayerOpacity(const QList &layerOpacity) { m_metatileLayerOpacity = layerOpacity; } - QList metatileLayerOpacity() const; - static void setDefaultMetatileLayerOpacity(const QList &layerOpacity) { s_defaultMetatileLayerOpacity = layerOpacity; } - static QList defaultMetatileLayerOpacity(); + const QList &metatileLayerOpacity() const { + return !m_metatileLayerOpacity.isEmpty() ? m_metatileLayerOpacity : Layout::globalMetatileLayerOpacity(); + } + static void setGlobalMetatileLayerOpacity(const QList &layerOpacity) { s_globalMetatileLayerOpacity = layerOpacity; } + static const QList &globalMetatileLayerOpacity() { + static const QList defaultLayerOpacity = {1.0, 1.0, 1.0}; + return !s_globalMetatileLayerOpacity.isEmpty() ? s_globalMetatileLayerOpacity : defaultLayerOpacity; + } LayoutPixmapItem *layoutItem = nullptr; CollisionPixmapItem *collisionItem = nullptr; @@ -166,8 +176,8 @@ private: QList m_metatileLayerOrder; QList m_metatileLayerOpacity; - static QList s_defaultMetatileLayerOrder; - static QList s_defaultMetatileLayerOpacity; + static QList s_globalMetatileLayerOrder; + static QList s_globalMetatileLayerOpacity; signals: void dimensionsChanged(const QSize &size); diff --git a/include/core/metatile.h b/include/core/metatile.h index a1c805d1..a57717fe 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -62,6 +62,14 @@ public: static void setLayout(Project*); static QString getMetatileIdString(uint16_t metatileId); static QString getMetatileIdStrings(const QList metatileIds); + static QString getLayerName(int layerNum); + + static int tileWidth() { return 2; } + static int tileHeight() { return 2; } + static int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); } + static int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); } + static int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); } + static QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); } inline bool operator==(const Metatile &other) { return this->tiles == other.tiles && this->attributes == other.attributes; diff --git a/include/core/tile.h b/include/core/tile.h index 1ae7e23d..d7ae5061 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -3,6 +3,7 @@ #define TILE_H #include +#include class Tile { @@ -24,6 +25,10 @@ public: static int getIndexInTileset(int); static const uint16_t maxValue; + + static int pixelWidth() { return 8; } + static int pixelHeight() { return 8; } + static QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); } }; inline bool operator==(const Tile &a, const Tile &b) { diff --git a/include/core/tileset.h b/include/core/tileset.h index abb31ab1..80fef000 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -38,6 +38,7 @@ public: QList> palettes; QList> palettePreviews; + static QString stripPrefix(const QString &fullName); static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); diff --git a/include/core/utility.h b/include/core/utility.h index f7f5da61..41c1dcde 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -7,7 +7,7 @@ namespace Util { void numericalModeSort(QStringList &list); - int roundUp(int numToRound, int multiple); + int roundUpToMultiple(int numToRound, int multiple); QString toDefineCase(QString input); QString toHexString(uint32_t value, int minLength = 0); QString toHtmlParagraph(const QString &text); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index d3136580..0cfe8511 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -12,8 +12,10 @@ QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false); QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false); -QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); -QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); +QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false); +QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false); +QImage getMetatileSheetImage(Layout *, int, bool useTruePalettes = false); +QImage getMetatileSheetImage(Tileset *, Tileset *, uint16_t, int, int, const QList &, const QList & = {}, const QSize &size = Metatile::pixelSize(), bool useTruePalettes = false); QImage getTileImage(uint16_t, Tileset*, Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset); diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h new file mode 100644 index 00000000..a6b8f6cd --- /dev/null +++ b/include/ui/metatileimageexporter.h @@ -0,0 +1,101 @@ +#ifndef METATILEIMAGEEXPORTER_H +#define METATILEIMAGEEXPORTER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +class Tileset; + +namespace Ui { +class MetatileImageExporter; +} + +class ReorderableListWidget : public QListWidget +{ + Q_OBJECT +public: + explicit ReorderableListWidget(QWidget *parent = nullptr) : QListWidget(parent) { + setDragEnabled(true); + setDragDropMode(QAbstractItemView::InternalMove); + setDefaultDropAction(Qt::MoveAction); + }; + +signals: + void reordered(); + +protected: + virtual void dropEvent(QDropEvent *event) override { + QListWidget::dropEvent(event); + if (event->isAccepted()) { + emit reordered(); + } + } +}; + +class MetatileImageExporter : public QDialog +{ + Q_OBJECT + +public: + struct Settings { + OrderedMap layerOrder = { + {2, true}, + {1, true}, + {0, true}, + }; + uint16_t metatileStart = 0; + uint16_t metatileEnd = 0xFFFF; + uint16_t numMetatilesWide = 8; + bool usePrimaryTileset = true; + bool useSecondaryTileset = false; + bool renderPlaceholders = false; + int transparencyMode = 0; + }; + + explicit MetatileImageExporter(QWidget *parent, Tileset *primaryTileset, Tileset *secondaryTileset, Settings *savedSettings = nullptr); + ~MetatileImageExporter(); + +protected: + virtual void showEvent(QShowEvent *) override; + virtual void closeEvent(QCloseEvent *) override; + +private: + Ui::MetatileImageExporter *ui; + + Tileset *m_primaryTileset; + Tileset *m_secondaryTileset; + Settings *m_savedSettings; + + QGraphicsScene *m_scene = nullptr; + QGraphicsPixmapItem *m_preview = nullptr; + bool m_previewUpdateQueued = false; + QList m_layerOrder; + ProjectConfig m_savedConfig; + QList m_transparencyButtons; + + void applySettings(const Settings &settings); + void updatePreview(); + void tryUpdatePreview(); + void queuePreviewUpdate(); + void updateTilesetUI(); + void syncPixelWidth(); + void syncMetatileWidth(); + void validateMetatileStart(); + void validateMetatileEnd(); + uint16_t getExpectedMetatileStart(); + uint16_t getExpectedMetatileEnd(); + void updateMetatileRange(); + void copyRenderSettings(); + void restoreRenderSettings(); + void saveImage(); + void reset(); +}; + +#endif // METATILEIMAGEEXPORTER_H diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index b6a60a61..b89709df 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -8,6 +8,7 @@ #include "tileseteditormetatileselector.h" #include "tileseteditortileselector.h" #include "metatilelayersitem.h" +#include "metatileimageexporter.h" class NoScrollComboBox; class Layout; @@ -71,10 +72,6 @@ private slots: void on_spinBox_paletteSelector_valueChanged(int arg1); - void on_actionImport_Primary_Tiles_triggered(); - - void on_actionImport_Secondary_Tiles_triggered(); - void on_actionChange_Metatiles_Count_triggered(); void on_actionChange_Palettes_triggered(); @@ -91,14 +88,6 @@ private slots: void on_lineEdit_metatileLabel_editingFinished(); - void on_actionExport_Primary_Tiles_Image_triggered(); - void on_actionExport_Secondary_Tiles_Image_triggered(); - void on_actionExport_Primary_Metatiles_Image_triggered(); - void on_actionExport_Secondary_Metatiles_Image_triggered(); - - void on_actionImport_Primary_Metatiles_triggered(); - void on_actionImport_Secondary_Metatiles_triggered(); - void on_copyButton_metatileLabel_clicked(); void on_actionCut_triggered(); @@ -122,8 +111,10 @@ private: void drawSelectedTiles(); void redrawTileSelector(); void redrawMetatileSelector(); - void importTilesetTiles(Tileset*, bool); - void importTilesetMetatiles(Tileset*, bool); + void importTilesetTiles(Tileset*); + void importAdvanceMapMetatiles(Tileset*); + void exportTilesImage(Tileset*); + void exportMetatilesImage(); void refresh(); void commitMetatileLabel(); void closeEvent(QCloseEvent*); @@ -170,6 +161,7 @@ private: QGraphicsScene *metatileLayersScene = nullptr; bool lockSelection = false; QSet metatileReloadQueue; + MetatileImageExporter::Settings *metatileImageExportSettings = nullptr; bool save(); diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 8a3fdfa7..77a5c4e4 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -21,8 +21,6 @@ public: uint16_t getSelectedMetatileId(); void updateSelectedMetatile(); QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId); - QImage buildPrimaryMetatilesImage(); - QImage buildSecondaryMetatilesImage(); QVector usedMetatiles; bool selectorShowUnused = false; @@ -56,8 +54,6 @@ private: void drawFilters(); void drawUnused(); void drawCounts(); - QImage buildAllMetatilesImage(); - QImage buildImage(int metatileIdStart, int numMetatiles); int numPrimaryMetatilesRounded() const; signals: diff --git a/include/ui/uintspinbox.h b/include/ui/uintspinbox.h index bc217ec2..aa8bcb77 100644 --- a/include/ui/uintspinbox.h +++ b/include/ui/uintspinbox.h @@ -21,6 +21,7 @@ public: uint32_t value() const { return m_value; } uint32_t minimum() const { return m_minimum; } uint32_t maximum() const { return m_maximum; } + uint32_t singleStep() const { return m_singleStep; } QString prefix() const { return m_prefix; } int displayIntegerBase() const { return m_displayIntegerBase; } bool hasPadding() const { return m_hasPadding; } @@ -28,6 +29,7 @@ public: void setMinimum(uint32_t min); void setMaximum(uint32_t max); void setRange(uint32_t min, uint32_t max); + void setSingleStep(uint32_t val); void setPrefix(const QString &prefix); void setDisplayIntegerBase(int base); void setHasPadding(bool enabled); @@ -36,6 +38,7 @@ private: uint32_t m_minimum; uint32_t m_maximum; uint32_t m_value; + uint32_t m_singleStep; QString m_prefix; int m_displayIntegerBase; bool m_hasPadding; diff --git a/porymap.pro b/porymap.pro index 4045e230..982fbd37 100644 --- a/porymap.pro +++ b/porymap.pro @@ -124,6 +124,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/regionmapeditor.cpp \ src/ui/newmapdialog.cpp \ src/ui/mapimageexporter.cpp \ + src/ui/metatileimageexporter.cpp \ src/ui/newtilesetdialog.cpp \ src/ui/flowlayout.cpp \ src/ui/mapruler.cpp \ @@ -240,6 +241,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/regionmapeditor.h \ include/ui/newmapdialog.h \ include/ui/mapimageexporter.h \ + include/ui/metatileimageexporter.h \ include/ui/newtilesetdialog.h \ include/ui/overlay.h \ include/ui/flowlayout.h \ @@ -289,6 +291,7 @@ FORMS += forms/mainwindow.ui \ forms/aboutporymap.ui \ forms/newtilesetdialog.ui \ forms/mapimageexporter.ui \ + forms/metatileimageexporter.ui \ forms/shortcutseditor.ui \ forms/preferenceeditor.ui \ forms/regionmappropertiesdialog.ui \ diff --git a/src/config.cpp b/src/config.cpp index 4f5188bd..8b2f24a9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -283,7 +283,7 @@ int KeyValueConfigBase::getConfigInteger(const QString &key, const QString &valu logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); result = defaultValue; } - return qMin(max, qMax(min, result)); + return qBound(min, result, max); } uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString &value, uint32_t min, uint32_t max, uint32_t defaultValue) { @@ -293,7 +293,7 @@ uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString & logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); result = defaultValue; } - return qMin(max, qMax(min, result)); + return qBound(min, result, max); } QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &value, const QColor &defaultValue) { @@ -305,6 +305,10 @@ QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &val return color; } +QString KeyValueConfigBase::toConfigColor(const QColor &color) { + return color.name().remove("#"); // Our text config treats '#' as the start of a comment. +} + PorymapConfig porymapConfig; PorymapConfig::PorymapConfig() : KeyValueConfigBase(QStringLiteral("porymap.cfg")) { @@ -571,7 +575,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("grid_x", QString::number(this->gridSettings.offsetX)); map.insert("grid_y", QString::number(this->gridSettings.offsetY)); map.insert("grid_style", GridSettings::getStyleName(this->gridSettings.style)); - map.insert("grid_color", this->gridSettings.color.name().remove("#")); // Our text config treats '#' as the start of a comment. + map.insert("grid_color", toConfigColor(this->gridSettings.color)); QStringList logTypesStrings; for (const auto &type : this->statusBarLogTypes) { @@ -898,8 +902,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveCallback = getConfigBool(key, value); } else if (key == "tilesets_have_is_compressed") { this->tilesetsHaveIsCompressed = getConfigBool(key, value); - } else if (key == "set_transparent_pixels_black") { - this->setTransparentPixelsBlack = getConfigBool(key, value); + } else if (key == "transparency_color") { + this->transparencyColor = getConfigColor(key, value); } else if (key == "preserve_matching_only_data") { this->preserveMatchingOnlyData = getConfigBool(key, value); } else if (key == "event_icon_path_object") { @@ -1005,7 +1009,7 @@ QMap ProjectConfig::getKeyValueMap() { } map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); - map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); + map.insert("transparency_color", toConfigColor(this->transparencyColor)); map.insert("preserve_matching_only_data", QString::number(this->preserveMatchingOnlyData)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index a9885042..7ab13ec7 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -6,6 +6,9 @@ #include "imageproviders.h" #include "utility.h" +QList Layout::s_globalMetatileLayerOrder; +QList Layout::s_globalMetatileLayerOpacity; + Layout::Layout(const Layout &other) : Layout() { copyFrom(&other); } @@ -612,23 +615,3 @@ Blockdata Layout::readBlockdata(const QString &path, QString *error) { return blockdata; } - -QList Layout::metatileLayerOrder() const { - return !m_metatileLayerOrder.isEmpty() ? m_metatileLayerOrder : Layout::defaultMetatileLayerOrder(); -} - -QList Layout::s_defaultMetatileLayerOrder; -QList Layout::defaultMetatileLayerOrder() { - static const QList initialDefault = {0, 1, 2}; - return !s_defaultMetatileLayerOrder.isEmpty() ? s_defaultMetatileLayerOrder : initialDefault; -} - -QList Layout::metatileLayerOpacity() const { - return !m_metatileLayerOpacity.isEmpty() ? m_metatileLayerOpacity : Layout::defaultMetatileLayerOpacity(); -} - -QList Layout::s_defaultMetatileLayerOpacity; -QList Layout::defaultMetatileLayerOpacity() { - static const QList initialDefault = {1.0, 1.0, 1.0}; - return !s_defaultMetatileLayerOpacity.isEmpty() ? s_defaultMetatileLayerOpacity : initialDefault; -} diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 73ff9c86..f6c0d382 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -36,8 +36,8 @@ int Metatile::getIndexInTileset(int metatileId) { } QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { - int x = static_cast(pixelCoord.x()) / 16; - int y = static_cast(pixelCoord.y()) / 16; + int x = static_cast(pixelCoord.x()) / pixelWidth(); + int y = static_cast(pixelCoord.y()) / pixelHeight(); if (pixelCoord.x() < 0) x--; if (pixelCoord.y() < 0) y--; return QPoint(x, y); @@ -55,6 +55,11 @@ QString Metatile::getMetatileIdStrings(const QList metatileIds) { return metatiles.join(","); }; +QString Metatile::getLayerName(int layerNum) { + static const QStringList layerTitles = { "Bottom", "Middle", "Top"}; + return layerTitles.value(layerNum); +} + // Read and pack together this metatile's attributes. uint32_t Metatile::getAttributes() const { uint32_t data = 0; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 0ae8512f..75c9c362 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -200,9 +200,8 @@ QString Tileset::getMetatileLabelPrefix() QString Tileset::getMetatileLabelPrefix(const QString &name) { // Default is "gTileset_Name" --> "METATILE_Name_" - const QString tilesetPrefix = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix); const QString labelPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix); - return QString("%1%2_").arg(labelPrefix).arg(QString(name).replace(tilesetPrefix, "")); + return QString("%1%2_").arg(labelPrefix).arg(Tileset::stripPrefix(name)); } bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { @@ -382,8 +381,7 @@ QString Tileset::getExpectedDir(QString tilesetName, bool isSecondary) : projectConfig.getFilePath(ProjectFilePath::data_primary_tilesets_folders); static const QRegularExpression re("([a-z])([A-Z0-9])"); - const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix); - return basePath + tilesetName.replace(prefix, "").replace(re, "\\1_\\2").toLower(); + return basePath + Tileset::stripPrefix(tilesetName).replace(re, "\\1_\\2").toLower(); } // Get the expected positions of the members in struct Tileset. @@ -600,3 +598,7 @@ bool Tileset::save() { if (!savePalettes()) success = false; return success; } + +QString Tileset::stripPrefix(const QString &fullName) { + return QString(fullName).replace(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix), ""); +} diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 0ecfd13e..ff06838f 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -14,7 +14,7 @@ void Util::numericalModeSort(QStringList &list) { std::sort(list.begin(), list.end(), collator); } -int Util::roundUp(int numToRound, int multiple) { +int Util::roundUpToMultiple(int numToRound, int multiple) { if (multiple <= 0) return numToRound; diff --git a/src/editor.cpp b/src/editor.cpp index bd15f4c5..d301d60e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1176,8 +1176,8 @@ void Editor::setPlayerViewRect(const QRectF &rect) { } void Editor::setCursorRectPos(const QPoint &pos) { - int x = qMax(0, qMin(pos.x(), this->layout ? this->layout->getWidth() - 1 : 0)); - int y = qMax(0, qMin(pos.y(), this->layout ? this->layout->getHeight() - 1 : 0)); + int x = qBound(0, pos.x(), this->layout ? this->layout->getWidth() - 1 : 0); + int y = qBound(0, pos.y(), this->layout ? this->layout->getHeight() - 1 : 0); if (this->playerViewRect) this->playerViewRect->updateLocation(x, y); diff --git a/src/main.cpp b/src/main.cpp index d85c1b38..dfd3bff2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); + QCoreApplication::setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true); + QApplication a(argc, argv); a.setStyle("fusion"); diff --git a/src/project.cpp b/src/project.cpp index 75b32474..9a65ac38 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1618,7 +1618,7 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles)); } ignoreWatchedFilesTemporarily({headersFilepath, graphicsFilepath, metatilesFilepath}); - name.remove(0, prefix.length()); // Strip prefix from name to get base name for use in other symbols. + name = Tileset::stripPrefix(name); tileset->appendToHeaders(headersFilepath, name, this->usingAsmTilesets); tileset->appendToGraphics(graphicsFilepath, name, this->usingAsmTilesets); tileset->appendToMetatiles(metatilesFilepath, name, this->usingAsmTilesets); @@ -3462,12 +3462,12 @@ void Project::applyParsedLimits() { Block::setLayout(); Metatile::setLayout(this); - Project::num_metatiles_primary = qMin(qMax(Project::num_metatiles_primary, 1), Block::getMaxMetatileId() + 1); + Project::num_metatiles_primary = qBound(1, Project::num_metatiles_primary, Block::getMaxMetatileId() + 1); projectConfig.defaultMetatileId = qMin(projectConfig.defaultMetatileId, Block::getMaxMetatileId()); projectConfig.defaultElevation = qMin(projectConfig.defaultElevation, Block::getMaxElevation()); projectConfig.defaultCollision = qMin(projectConfig.defaultCollision, Block::getMaxCollision()); - projectConfig.collisionSheetSize.setHeight(qMin(qMax(projectConfig.collisionSheetSize.height(), 1), Block::getMaxElevation() + 1)); - projectConfig.collisionSheetSize.setWidth(qMin(qMax(projectConfig.collisionSheetSize.width(), 1), Block::getMaxCollision() + 1)); + projectConfig.collisionSheetSize.setHeight(qBound(1, projectConfig.collisionSheetSize.height(), Block::getMaxElevation() + 1)); + projectConfig.collisionSheetSize.setWidth(qBound(1, projectConfig.collisionSheetSize.width(), Block::getMaxCollision() + 1)); } bool Project::hasUnsavedChanges() { diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index 900c8701..5058e2de 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -201,7 +201,7 @@ QList ScriptUtility::getCustomScripts() { } QList ScriptUtility::getMetatileLayerOrder() { - return Layout::defaultMetatileLayerOrder(); + return Layout::globalMetatileLayerOrder(); } bool ScriptUtility::validateMetatileLayerOrder(const QList &order) { @@ -220,16 +220,16 @@ bool ScriptUtility::validateMetatileLayerOrder(const QList &order) { void ScriptUtility::setMetatileLayerOrder(const QList &order) { if (!validateMetatileLayerOrder(order)) return; - Layout::setDefaultMetatileLayerOrder(order); + Layout::setGlobalMetatileLayerOrder(order); if (window) window->refreshAfterPalettePreviewChange(); } QList ScriptUtility::getMetatileLayerOpacity() { - return Layout::defaultMetatileLayerOpacity(); + return Layout::globalMetatileLayerOpacity(); } void ScriptUtility::setMetatileLayerOpacity(const QList &opacities) { - Layout::setDefaultMetatileLayerOpacity(opacities); + Layout::setGlobalMetatileLayerOpacity(opacities); if (window) window->refreshAfterPalettePreviewChange(); } diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 4072baf8..0997c1e1 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -56,11 +56,9 @@ QImage getMetatileImage( const QList &layerOpacity, bool useTruePalettes) { - const int numTilesWide = 2; - const int numTilesTall = 2; - QImage metatile_image(8 * numTilesWide, 8 * numTilesTall, QImage::Format_RGBA8888); + QImage metatile_image(Metatile::pixelWidth(), Metatile::pixelHeight(), QImage::Format_RGBA8888); if (!metatile) { - metatile_image.fill(Qt::magenta); + metatile_image.fill(projectConfig.transparencyColor == QColor(Qt::transparent) ? projectConfig.transparencyColor : QColor(Qt::magenta)); return metatile_image; } @@ -70,21 +68,20 @@ QImage getMetatileImage( // tile pixels line up across layers we will still have something to render. // The GBA renders transparent pixels using palette 0 color 0. We have this color, // but all 3 games actually overwrite it with black when loading the tileset palettes, - // so we have a setting to choose between these two behaviors. - metatile_image.fill(projectConfig.setTransparentPixelsBlack ? QColor("black") : QColor(palettes.value(0).value(0))); + // so we have a setting to specify an override transparency color. + metatile_image.fill(projectConfig.transparencyColor.isValid() ? projectConfig.transparencyColor : QColor(palettes.value(0).value(0))); QPainter metatile_painter(&metatile_image); uint32_t layerType = metatile->layerType(); - const int numTilesPerLayer = numTilesWide * numTilesTall; for (const auto &layer : layerOrder) - for (int y = 0; y < numTilesTall; y++) - for (int x = 0; x < numTilesWide; x++) { + for (int y = 0; y < Metatile::tileHeight(); y++) + for (int x = 0; x < Metatile::tileWidth(); x++) { // Get the tile to render next Tile tile; - int tileOffset = (y * numTilesWide) + x; + int tileOffset = (y * Metatile::tileWidth()) + x; if (projectConfig.tripleLayerMetatilesEnabled) { - tile = metatile->tiles.value(tileOffset + (layer * numTilesPerLayer)); + tile = metatile->tiles.value(tileOffset + (layer * Metatile::tilesPerLayer())); } else { // "Vanilla" metatiles only have 8 tiles, but render 12. // The remaining 4 tiles are rendered using user-specified tiles depending on layer type. @@ -95,19 +92,19 @@ QImage getMetatileImage( if (layer == 0) tile = Tile(projectConfig.unusedTileNormal); else // Tiles are on layers 1 and 2 - tile = metatile->tiles.value(tileOffset + ((layer - 1) * numTilesPerLayer)); + tile = metatile->tiles.value(tileOffset + ((layer - 1) * Metatile::tilesPerLayer())); break; case Metatile::LayerType::Covered: if (layer == 2) tile = Tile(projectConfig.unusedTileCovered); else // Tiles are on layers 0 and 1 - tile = metatile->tiles.value(tileOffset + (layer * numTilesPerLayer)); + tile = metatile->tiles.value(tileOffset + (layer * Metatile::tilesPerLayer())); break; case Metatile::LayerType::Split: if (layer == 1) tile = Tile(projectConfig.unusedTileSplit); else // Tiles are on layers 0 and 2 - tile = metatile->tiles.value(tileOffset + ((layer == 0 ? 0 : 1) * numTilesPerLayer)); + tile = metatile->tiles.value(tileOffset + ((layer == 0 ? 0 : 1) * Metatile::tilesPerLayer())); break; } } @@ -130,7 +127,7 @@ QImage getMetatileImage( logWarn(QString("Tile '%1' is referring to invalid palette number: '%2'").arg(tile.tileId).arg(tile.palette)); } - QPoint origin = QPoint(x*8, y*8); + QPoint origin = QPoint(x * Tile::pixelWidth(), y * Tile::pixelHeight()); float opacity = layerOpacity.value(layer, 1.0); if (opacity < 1.0) { int alpha = 255 * opacity; @@ -165,9 +162,9 @@ QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondary QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) { QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset); if (tileImage.isNull()) { - tileImage = QImage(8, 8, QImage::Format_RGBA8888); + tileImage = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_RGBA8888); QPainter painter(&tileImage); - painter.fillRect(0, 0, 8, 8, palette.at(0)); + painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.at(0)); } else { for (int i = 0; i < 16; i++) { tileImage.setColor(i, palette.at(i)); @@ -194,3 +191,57 @@ void flattenTo4bppImage(QImage * image) { for (int i = 0; i < image->sizeInBytes(); i++, pixel++) *pixel %= 16; } + +QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) { + return getMetatileSheetImage(layout->tileset_primary, + layout->tileset_secondary, + 0, + -1, + numMetatilesWide, + layout->metatileLayerOrder(), + layout->metatileLayerOpacity(), + Metatile::pixelSize(), + useTruePalettes); +} + +QImage getMetatileSheetImage(Tileset *primaryTileset, + Tileset *secondaryTileset, + uint16_t metatileIdStart, + int numMetatilesToDraw, + int numMetatilesWide, + const QList &layerOrder, + const QList &layerOpacity, + const QSize &metatileSize, + bool useTruePalettes) +{ + // We round up the number of primary metatiles to keep the tilesets on separate rows. + int numPrimary = Util::roundUpToMultiple(primaryTileset ? primaryTileset->numMetatiles() : 0, numMetatilesWide); + int maxPrimary = Project::getNumMetatilesPrimary(); + bool includesPrimary = metatileIdStart < maxPrimary; + + // Negative values are used to indicate 'draw all metatiles' + if (numMetatilesToDraw < 0) { + numMetatilesToDraw = numPrimary + (secondaryTileset ? secondaryTileset->numMetatiles() : 0) - metatileIdStart; + } + + // Round up height for incomplete last row + int numMetatilesTall = ceil((double)numMetatilesToDraw / numMetatilesWide); + + QImage image(numMetatilesWide * metatileSize.width(), numMetatilesTall * metatileSize.height(), QImage::Format_RGBA8888); + image.fill(projectConfig.transparencyColor == QColor(Qt::transparent) ? projectConfig.transparencyColor : QColor(Qt::magenta)); + + QPainter painter(&image); + for (int i = 0; i < numMetatilesToDraw; i++) { + uint16_t metatileId = i + metatileIdStart; + if (includesPrimary && metatileId >= numPrimary) + metatileId += maxPrimary - numPrimary; // Skip over unused region of primary tileset + QImage metatile_image = getMetatileImage(metatileId, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes) + .scaled(metatileSize.width(), metatileSize.height()); + int map_y = i / numMetatilesWide; + int map_x = i % numMetatilesWide; + QPoint metatile_origin = QPoint(map_x * metatileSize.width(), map_y * metatileSize.height()); + painter.drawImage(metatile_origin, metatile_image); + } + painter.end(); + return image; +} diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp new file mode 100644 index 00000000..d5e94f13 --- /dev/null +++ b/src/ui/metatileimageexporter.cpp @@ -0,0 +1,321 @@ +#include "metatileimageexporter.h" +#include "ui_metatileimageexporter.h" +#include "filedialog.h" +#include "imageproviders.h" +#include "utility.h" +#include "project.h" +#include "metatile.h" + +#include + +MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTileset, Tileset *secondaryTileset, Settings *savedSettings) : + QDialog(parent), + ui(new Ui::MetatileImageExporter), + m_primaryTileset(primaryTileset), + m_secondaryTileset(secondaryTileset), + m_savedSettings(savedSettings) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + m_transparencyButtons = { + ui->radioButton_TransparencyNormal, + ui->radioButton_TransparencyBlack, + ui->radioButton_TransparencyFirst, + }; + + m_scene = new QGraphicsScene(this); + m_preview = m_scene->addPixmap(QPixmap()); + ui->graphicsView_Preview->setScene(m_scene); + + if (projectConfig.tripleLayerMetatilesEnabled) { + // When triple-layer metatiles are enabled there is no unused layer, + // so this setting becomes pointless. Disable it. + ui->checkBox_Placeholders->setVisible(false); + } + + uint16_t maxMetatileId = Block::getMaxMetatileId(); + ui->spinBox_MetatileStart->setMaximum(maxMetatileId); + ui->spinBox_MetatileEnd->setMaximum(maxMetatileId); + ui->spinBox_WidthMetatiles->setRange(1, maxMetatileId); + ui->spinBox_WidthPixels->setRange(1 * Metatile::pixelWidth(), maxMetatileId * Metatile::pixelWidth()); + + if (m_primaryTileset) { + ui->comboBox_PrimaryTileset->setTextItem(m_primaryTileset->name); + } + if (m_secondaryTileset) { + ui->comboBox_SecondaryTileset->setTextItem(m_secondaryTileset->name); + } + + if (m_savedSettings) { + applySettings(*m_savedSettings); + } else { + applySettings({}); + } + + connect(ui->listWidget_Layers, &ReorderableListWidget::itemChanged, this, &MetatileImageExporter::updatePreview); + connect(ui->listWidget_Layers, &ReorderableListWidget::reordered, this, &MetatileImageExporter::updatePreview); + + connect(ui->pushButton_Save, &QPushButton::pressed, this, &MetatileImageExporter::saveImage); + connect(ui->pushButton_Close, &QPushButton::pressed, this, &MetatileImageExporter::close); + connect(ui->pushButton_Reset, &QPushButton::pressed, this, &MetatileImageExporter::reset); + + connect(ui->spinBox_WidthMetatiles, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::syncPixelWidth); + connect(ui->spinBox_WidthMetatiles, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate); + connect(ui->spinBox_WidthMetatiles, &UIntSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview); + + connect(ui->spinBox_WidthPixels, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::syncMetatileWidth); + connect(ui->spinBox_WidthPixels, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate); + connect(ui->spinBox_WidthPixels, &UIntSpinBox::editingFinished, this, &MetatileImageExporter::syncPixelWidth); // Round pixel width to multiple of 16 + connect(ui->spinBox_WidthPixels, &UIntSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview); + + connect(ui->spinBox_MetatileStart, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::validateMetatileEnd); + connect(ui->spinBox_MetatileStart, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate); + connect(ui->spinBox_MetatileStart, &UIntHexSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview); + + connect(ui->spinBox_MetatileEnd, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::validateMetatileStart); + connect(ui->spinBox_MetatileEnd, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate); + connect(ui->spinBox_MetatileEnd, &UIntHexSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview); + + // If we used toggled instead of clicked we'd get two preview updates instead of one when the setting changes. + connect(ui->radioButton_TransparencyNormal, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview); + connect(ui->radioButton_TransparencyBlack, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview); + connect(ui->radioButton_TransparencyFirst, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview); + + connect(ui->checkBox_Placeholders, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview); + connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updateTilesetUI); + connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview); + connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updateTilesetUI); + connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview); + + ui->graphicsView_Preview->setFocus(); +} + +MetatileImageExporter::~MetatileImageExporter() { + delete ui; +} + +// Allow the window to open before displaying the preview. +// Metatile sheet image creation is generally quick, so this only +// really matters so that the graphics view can adjust the scale properly. +void MetatileImageExporter::showEvent(QShowEvent *event) { + QDialog::showEvent(event); + if (!event->spontaneous()) { + QTimer::singleShot(0, this, &MetatileImageExporter::updatePreview); + } +} + +void MetatileImageExporter::closeEvent(QCloseEvent *event) { + if (m_savedSettings) { + m_savedSettings->metatileStart = ui->spinBox_MetatileStart->value(); + m_savedSettings->metatileEnd = ui->spinBox_MetatileEnd->value(); + m_savedSettings->numMetatilesWide = ui->spinBox_WidthMetatiles->value(); + m_savedSettings->usePrimaryTileset = ui->checkBox_PrimaryTileset->isChecked(); + m_savedSettings->useSecondaryTileset = ui->checkBox_SecondaryTileset->isChecked(); + m_savedSettings->renderPlaceholders = ui->checkBox_Placeholders->isChecked(); + for (int i = 0; i < m_transparencyButtons.length(); i++) { + if (m_transparencyButtons.at(i)->isChecked()) { + m_savedSettings->transparencyMode = i; + break; + } + } + m_savedSettings->layerOrder.clear(); + for (int i = 0; i < ui->listWidget_Layers->count(); i++) { + auto item = ui->listWidget_Layers->item(i); + int layerNum = item->data(Qt::UserRole).toInt(); + m_savedSettings->layerOrder[layerNum] = (item->checkState() == Qt::Checked); + } + } + QDialog::closeEvent(event); +} + +void MetatileImageExporter::applySettings(const Settings &settings) { + ui->spinBox_MetatileStart->setValue(settings.metatileStart); + ui->spinBox_MetatileEnd->setValue(settings.metatileEnd); + ui->spinBox_WidthMetatiles->setValue(settings.numMetatilesWide); + ui->spinBox_WidthPixels->setValue(settings.numMetatilesWide * Metatile::pixelWidth()); + ui->checkBox_PrimaryTileset->setChecked(settings.usePrimaryTileset); + ui->checkBox_SecondaryTileset->setChecked(settings.useSecondaryTileset); + ui->checkBox_Placeholders->setChecked(settings.renderPlaceholders); + if (m_transparencyButtons.value(settings.transparencyMode)) { + m_transparencyButtons[settings.transparencyMode]->setChecked(true); + } + + // Build layer list from settings + ui->listWidget_Layers->clear(); + for (auto it = settings.layerOrder.cbegin(); it != settings.layerOrder.cend(); it++) { + int layerNum = it.key(); + bool enabled = it.value(); + + auto item = new QListWidgetItem(Metatile::getLayerName(layerNum)); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren); + item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole, layerNum); // Save the original index to identify the layer + ui->listWidget_Layers->addItem(item); + } + // Don't give extra unnecessary space to the list + ui->listWidget_Layers->setFixedHeight(ui->listWidget_Layers->sizeHintForRow(0) * ui->listWidget_Layers->count() + 4); + + updateTilesetUI(); +} + +void MetatileImageExporter::reset() { + applySettings({}); + updatePreview(); +} + +void MetatileImageExporter::saveImage() { + // Ensure the image in the preview is up-to-date before exporting. + updatePreview(); + + QString defaultFilename; + if (m_layerOrder.length() == 1) { + // Exporting a metatile layer image is an expected use case for Porytiles, which expects certain file names. + // We can make the process a little easier by setting the default file name to those expected file names. + static const QStringList layerFilenames = { "bottom", "middle", "top" }; + defaultFilename = (layerFilenames.at(m_layerOrder.constFirst())); + } else { + if (ui->checkBox_PrimaryTileset->isChecked() && m_primaryTileset) { + defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_primaryTileset->name))); + } + if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) { + defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_secondaryTileset->name))); + } + if (!m_layerOrder.isEmpty() && m_layerOrder != QList({0,1,2})) { + for (int i = m_layerOrder.length() - 1; i >= 0; i--) { + defaultFilename.append(Metatile::getLayerName(m_layerOrder.at(i))); + } + defaultFilename.append("_"); + } + defaultFilename.append("Metatiles"); + } + + QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultFilename); + QString filepath = FileDialog::getSaveFileName(this, windowTitle(), defaultFilepath, QStringLiteral("Image Files (*.png *.jpg *.bmp)")); + if (!filepath.isEmpty()) { + m_preview->pixmap().save(filepath); + } +} + +void MetatileImageExporter::queuePreviewUpdate() { + m_previewUpdateQueued = true; +} + +// For updating only when a change has been recorded. +// Useful for something that might happen often, like an input widget losing focus. +void MetatileImageExporter::tryUpdatePreview() { + if (m_preview->pixmap().isNull() || m_previewUpdateQueued) { + updatePreview(); + } +} + +void MetatileImageExporter::updatePreview() { + copyRenderSettings(); + + int numMetatilesWide = ui->spinBox_WidthMetatiles->value(); + int metatileStart = ui->spinBox_MetatileStart->value(); + int numMetatiles = Util::roundUpToMultiple(ui->spinBox_MetatileEnd->value() - metatileStart + 1, numMetatilesWide); + + m_layerOrder.clear(); + for (int i = 0; i < ui->listWidget_Layers->count(); i++) { + auto item = ui->listWidget_Layers->item(i); + if (item->checkState() == Qt::Checked) { + int layerNum = item->data(Qt::UserRole).toInt(); + m_layerOrder.prepend(qBound(0, layerNum, 2)); + } + } + + QImage previewImage = getMetatileSheetImage(m_primaryTileset, + m_secondaryTileset, + metatileStart, + numMetatiles, + numMetatilesWide, + m_layerOrder); + m_preview->setPixmap(QPixmap::fromImage(previewImage)); + m_scene->setSceneRect(m_scene->itemsBoundingRect()); + m_previewUpdateQueued = false; + + restoreRenderSettings(); +} + +void MetatileImageExporter::validateMetatileStart() { + const QSignalBlocker b(ui->spinBox_MetatileStart); + ui->spinBox_MetatileStart->setValue(qMin(ui->spinBox_MetatileStart->value(), + ui->spinBox_MetatileEnd->value())); +} + +void MetatileImageExporter::validateMetatileEnd() { + const QSignalBlocker b(ui->spinBox_MetatileEnd); + ui->spinBox_MetatileEnd->setValue(qMax(ui->spinBox_MetatileStart->value(), + ui->spinBox_MetatileEnd->value())); +} + +uint16_t MetatileImageExporter::getExpectedMetatileStart() { + if (ui->checkBox_PrimaryTileset->isChecked()) return 0; + if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary(); + return ui->spinBox_MetatileStart->value(); +} + +// TODO: Combining tilesets is not rendering the correct range of metatiles +uint16_t MetatileImageExporter::getExpectedMetatileEnd() { + if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary() + (m_secondaryTileset ? (m_secondaryTileset->numMetatiles() - 1) : 0); + if (ui->checkBox_PrimaryTileset->isChecked()) return m_primaryTileset ? (m_primaryTileset->numMetatiles() - 1) : 0; + return ui->spinBox_MetatileEnd->value(); +} + +void MetatileImageExporter::updateMetatileRange() { + const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart); + const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileEnd); + ui->spinBox_MetatileStart->setValue(getExpectedMetatileStart()); + ui->spinBox_MetatileEnd->setValue(getExpectedMetatileEnd()); +} + +void MetatileImageExporter::updateTilesetUI() { + // Users can either specify which tileset(s) to render, or specify a range of metatiles, but not both. + if (ui->checkBox_PrimaryTileset->isChecked() || ui->checkBox_SecondaryTileset->isChecked()) { + updateMetatileRange(); + ui->groupBox_MetatileRange->setDisabled(true); + } else { + ui->groupBox_MetatileRange->setDisabled(false); + } +} + +void MetatileImageExporter::syncPixelWidth() { + const QSignalBlocker b(ui->spinBox_WidthPixels); + ui->spinBox_WidthPixels->setValue(ui->spinBox_WidthMetatiles->value() * Metatile::pixelWidth()); +} + +void MetatileImageExporter::syncMetatileWidth() { + const QSignalBlocker b(ui->spinBox_WidthMetatiles); + ui->spinBox_WidthMetatiles->setValue(Util::roundUpToMultiple(ui->spinBox_WidthPixels->value(), Metatile::pixelWidth()) / Metatile::pixelWidth()); +} + +// These settings control some rendering behavior that make metatiles render accurately to their in-game appearance, +// which may be undesirable when exporting metatile images for editing. +// The settings are buried in getMetatileImage at the moment, to change them we'll temporarily overwrite them. +void MetatileImageExporter::copyRenderSettings() { + m_savedConfig.transparencyColor = projectConfig.transparencyColor; + m_savedConfig.unusedTileNormal = projectConfig.unusedTileNormal; + m_savedConfig.unusedTileCovered = projectConfig.unusedTileCovered; + m_savedConfig.unusedTileSplit = projectConfig.unusedTileSplit; + + if (ui->radioButton_TransparencyNormal->isChecked()) { + projectConfig.transparencyColor = QColor(Qt::transparent); + } else if (ui->radioButton_TransparencyBlack->isChecked()) { + projectConfig.transparencyColor = QColor(Qt::black); + } else { + projectConfig.transparencyColor = QColor(); + } + + if (!ui->checkBox_Placeholders->isChecked()) { + projectConfig.unusedTileNormal = 0; + projectConfig.unusedTileCovered = 0; + projectConfig.unusedTileSplit = 0; + } +} + +void MetatileImageExporter::restoreRenderSettings() { + projectConfig.transparencyColor = m_savedConfig.transparencyColor; + projectConfig.unusedTileNormal = m_savedConfig.unusedTileNormal; + projectConfig.unusedTileCovered = m_savedConfig.unusedTileCovered; + projectConfig.unusedTileSplit = m_savedConfig.unusedTileSplit; +} diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index a26ed14b..b0279e4e 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -148,6 +148,6 @@ void MetatileLayersItem::clearLastHoveredCoords() { QPoint MetatileLayersItem::getBoundedPos(const QPointF &pos) { int x = static_cast(pos.x()) / this->cellWidth; int y = static_cast(pos.y()) / this->cellHeight; - return QPoint( qMax(0, qMin(x, this->maxSelectionWidth - 1)), - qMax(0, qMin(y, this->maxSelectionHeight - 1)) ); + return QPoint(qBound(0, x, this->maxSelectionWidth - 1), + qBound(0, y, this->maxSelectionHeight - 1)); } diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 321f2925..d8f50410 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -15,28 +15,7 @@ int MetatileSelector::numPrimaryMetatilesRounded() const { } void MetatileSelector::updateBasePixmap() { - int primaryLength = this->numPrimaryMetatilesRounded(); - int length_ = primaryLength + this->secondaryTileset()->numMetatiles(); - int height_ = length_ / this->numMetatilesWide; - if (length_ % this->numMetatilesWide != 0) { - height_++; - } - QImage image(this->numMetatilesWide * this->cellWidth, height_ * this->cellHeight, QImage::Format_RGBA8888); - image.fill(Qt::magenta); - QPainter painter(&image); - for (int i = 0; i < length_; i++) { - int metatileId = i; - if (i >= primaryLength) { - metatileId += Project::getNumMetatilesPrimary() - primaryLength; - } - QImage metatile_image = getMetatileImage(metatileId, this->layout); - int map_y = i / this->numMetatilesWide; - int map_x = i % this->numMetatilesWide; - QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight); - painter.drawImage(metatile_origin, metatile_image); - } - painter.end(); - this->basePixmap = QPixmap::fromImage(image); + this->basePixmap = QPixmap::fromImage(getMetatileSheetImage(this->layout, this->numMetatilesWide)); } void MetatileSelector::draw() { diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 3fc73934..70e08e46 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -114,8 +114,8 @@ void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) { } void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - int dx = Util::roundUp(event->scenePos().x() - this->clickedPos.x(), 16); - int dy = Util::roundUp(event->scenePos().y() - this->clickedPos.y(), 16); + int dx = Util::roundUpToMultiple(event->scenePos().x() - this->clickedPos.x(), 16); + int dy = Util::roundUpToMultiple(event->scenePos().y() - this->clickedPos.y(), 16); QRect resizedRect = this->clickedRect; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index c4becaa5..f9f820ab 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -475,10 +475,11 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData); // Radio buttons - if (projectConfig.setTransparentPixelsBlack) + // TODO: Replace + /*if (projectConfig.setTransparentPixelsBlack) ui->radioButton_RenderBlack->setChecked(true); else - ui->radioButton_RenderFirstPalColor->setChecked(true); + ui->radioButton_RenderFirstPalColor->setChecked(true);*/ // Set spin box values ui->spinBox_Elevation->setValue(projectConfig.defaultElevation); @@ -574,7 +575,7 @@ void ProjectSettingsEditor::save() { projectConfig.tilesetsHaveCallback = ui->checkBox_OutputCallback->isChecked(); projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked(); porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked(); - projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); + //projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); // TODO projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked(); // Save spin box settings diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 7f11836f..e6b814b1 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -61,7 +61,7 @@ void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionChange && scene()) { QPointF newPos = value.toPointF(); - return QPointF(Util::roundUp(newPos.x(), 16), Util::roundUp(newPos.y(), 16)); + return QPointF(Util::roundUpToMultiple(newPos.x(), 16), Util::roundUpToMultiple(newPos.y(), 16)); } else return QGraphicsItem::itemChange(change, value); diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index e15824ad..3b212579 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -19,8 +19,8 @@ void SelectablePixmapItem::select(int x, int y, int width, int height) { this->selectionInitialX = x; this->selectionInitialY = y; - this->selectionOffsetX = qMax(0, qMin(width, this->maxSelectionWidth)); - this->selectionOffsetY = qMax(0, qMin(height, this->maxSelectionHeight)); + this->selectionOffsetX = qBound(0, width, this->maxSelectionWidth); + this->selectionOffsetY = qBound(0, height, this->maxSelectionHeight); this->draw(); emit this->selectionChanged(x, y, width, height); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 6fe96f0f..55545616 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -24,8 +24,10 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) hasUnsavedChanges(false) { setAttribute(Qt::WA_DeleteOnClose); - setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); ui->setupUi(this); + + setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); + connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip); connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip); @@ -35,6 +37,17 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save); + connect(ui->actionImport_Primary_Tiles_Image, &QAction::triggered, [this] { importTilesetTiles(this->primaryTileset); }); + connect(ui->actionImport_Secondary_Tiles_Image, &QAction::triggered, [this] { importTilesetTiles(this->secondaryTileset); }); + + connect(ui->actionImport_Primary_AdvanceMap_Metatiles, &QAction::triggered, [this] { importAdvanceMapMetatiles(this->primaryTileset); }); + connect(ui->actionImport_Secondary_AdvanceMap_Metatiles, &QAction::triggered, [this] { importAdvanceMapMetatiles(this->secondaryTileset); }); + + connect(ui->actionExport_Primary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->primaryTileset); }); + connect(ui->actionExport_Secondary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->secondaryTileset); }); + + connect(ui->actionExport_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); }); + ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes); @@ -74,6 +87,7 @@ TilesetEditor::~TilesetEditor() delete selectedTileScene; delete metatileLayersScene; delete copiedMetatile; + delete metatileImageExportSettings; this->metatileHistory.clear(); } @@ -694,17 +708,8 @@ bool TilesetEditor::save() { return success; } -void TilesetEditor::on_actionImport_Primary_Tiles_triggered() -{ - this->importTilesetTiles(this->primaryTileset, true); -} - -void TilesetEditor::on_actionImport_Secondary_Tiles_triggered() -{ - this->importTilesetTiles(this->secondaryTileset, false); -} - -void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { +void TilesetEditor::importTilesetTiles(Tileset *tileset) { + bool primary = !tileset->is_secondary; QString descriptor = primary ? "primary" : "secondary"; QString descriptorCaps = primary ? "Primary" : "Secondary"; @@ -968,62 +973,27 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel) this->commitMetatileAndLabelChange(prevMetatile, prevLabel); } -void TilesetEditor::on_actionExport_Primary_Tiles_Image_triggered() -{ - QString defaultName = QString("%1_Tiles_Pal%2").arg(this->primaryTileset->name).arg(this->paletteId); - QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); - QString filepath = FileDialog::getSaveFileName(this, "Export Primary Tiles Image", defaultFilepath, "Image Files (*.png)"); +void TilesetEditor::exportTilesImage(Tileset *tileset) { + bool primary = !tileset->is_secondary; + QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId); + QString filepath = FileDialog::getSaveFileName(this, QString("Export %1 Tiles Image").arg(primary ? "Primary" : "Secondary"), defaultFilepath, "Image Files (*.png)"); if (!filepath.isEmpty()) { - QImage image = this->tileSelector->buildPrimaryTilesIndexedImage(); + QImage image = primary ? this->tileSelector->buildPrimaryTilesIndexedImage() : this->tileSelector->buildSecondaryTilesIndexedImage(); exportIndexed4BPPPng(image, filepath); } } -void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered() -{ - QString defaultName = QString("%1_Tiles_Pal%2").arg(this->secondaryTileset->name).arg(this->paletteId); - QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); - QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Tiles Image", defaultFilepath, "Image Files (*.png)"); - if (!filepath.isEmpty()) { - QImage image = this->tileSelector->buildSecondaryTilesIndexedImage(); - exportIndexed4BPPPng(image, filepath); +// There are many more options for exporting metatile images than tile images, so we open a separate dialog to ask the user for settings. +void TilesetEditor::exportMetatilesImage() { + if (!this->metatileImageExportSettings) { + this->metatileImageExportSettings = new MetatileImageExporter::Settings; } + auto dialog = new MetatileImageExporter(this, this->primaryTileset, this->secondaryTileset, this->metatileImageExportSettings); + dialog->open(); } -void TilesetEditor::on_actionExport_Primary_Metatiles_Image_triggered() -{ - QString defaultName = QString("%1_Metatiles").arg(this->primaryTileset->name); - QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); - QString filepath = FileDialog::getSaveFileName(this, "Export Primary Metatiles Image", defaultFilepath, "Image Files (*.png)"); - if (!filepath.isEmpty()) { - QImage image = this->metatileSelector->buildPrimaryMetatilesImage(); - image.save(filepath, "PNG"); - } -} - -void TilesetEditor::on_actionExport_Secondary_Metatiles_Image_triggered() -{ - QString defaultName = QString("%1_Metatiles").arg(this->secondaryTileset->name); - QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); - QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Metatiles Image", defaultFilepath, "Image Files (*.png)"); - if (!filepath.isEmpty()) { - QImage image = this->metatileSelector->buildSecondaryMetatilesImage(); - image.save(filepath, "PNG"); - } -} - -void TilesetEditor::on_actionImport_Primary_Metatiles_triggered() -{ - this->importTilesetMetatiles(this->primaryTileset, true); -} - -void TilesetEditor::on_actionImport_Secondary_Metatiles_triggered() -{ - this->importTilesetMetatiles(this->secondaryTileset, false); -} - -void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) -{ +void TilesetEditor::importAdvanceMapMetatiles(Tileset *tileset) { + bool primary = !tileset->is_secondary; QString descriptorCaps = primary ? "Primary" : "Secondary"; QString filepath = FileDialog::getOpenFileName(this, QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps), "", "Advance Map 1.92 Metatile Files (*.bvd)"); diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 79888681..60f4b0f5 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -4,7 +4,7 @@ #include TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) - : SelectablePixmapItem(16, 16, 1, 1) { + : SelectablePixmapItem(32, 32, 1, 1) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; this->numMetatilesWide = 8; @@ -31,48 +31,6 @@ int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const { return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; } -QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() { - return this->buildImage(0, this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles()); -} - -QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() { - return this->buildImage(0, this->primaryTileset->numMetatiles()); -} - -QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() { - return this->buildImage(Project::getNumMetatilesPrimary(), this->secondaryTileset->numMetatiles()); -} - -QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) { - int numMetatilesHigh = this->numRows(numMetatiles); - int numPrimary = this->numPrimaryMetatilesRounded(); - int maxPrimary = Project::getNumMetatilesPrimary(); - bool includesPrimary = metatileIdStart < maxPrimary; - - QImage image(this->numMetatilesWide * this->cellWidth, numMetatilesHigh * this->cellHeight, QImage::Format_RGBA8888); - image.fill(Qt::magenta); - QPainter painter(&image); - for (int i = 0; i < numMetatiles; i++) { - int metatileId = i + metatileIdStart; - if (includesPrimary && metatileId >= numPrimary) - metatileId += maxPrimary - numPrimary; // Skip over unused region of primary tileset - QImage metatile_image = getMetatileImage( - metatileId, - this->primaryTileset, - this->secondaryTileset, - this->layout->metatileLayerOrder(), - this->layout->metatileLayerOpacity(), - true) - .scaled(this->cellWidth, this->cellHeight); - int map_y = i / this->numMetatilesWide; - int map_x = i % this->numMetatilesWide; - QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight); - painter.drawImage(metatile_origin, metatile_image); - } - painter.end(); - return image; -} - void TilesetEditorMetatileSelector::drawMetatile(uint16_t metatileId) { QPoint pos = getMetatileIdCoords(metatileId); @@ -97,7 +55,15 @@ void TilesetEditorMetatileSelector::drawSelectedMetatile() { } void TilesetEditorMetatileSelector::updateBasePixmap() { - this->baseImage = buildAllMetatilesImage(); + this->baseImage = getMetatileSheetImage(this->primaryTileset, + this->secondaryTileset, + 0, + this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles(), + this->numMetatilesWide, + this->layout->metatileLayerOrder(), + this->layout->metatileLayerOpacity(), + QSize(this->cellWidth, this->cellHeight), + true); this->basePixmap = QPixmap::fromImage(this->baseImage); } diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp index 53f6df78..cfe19b72 100644 --- a/src/ui/uintspinbox.cpp +++ b/src/ui/uintspinbox.cpp @@ -2,24 +2,24 @@ #include UIntSpinBox::UIntSpinBox(QWidget *parent) - : QAbstractSpinBox(parent) + : QAbstractSpinBox(parent), + m_minimum(0), + m_maximum(99), + m_value(m_minimum), + m_singleStep(1), + m_displayIntegerBase(10), + m_hasPadding(false), + m_numChars(2) { // Don't let scrolling hijack focus. setFocusPolicy(Qt::StrongFocus); - m_minimum = 0; - m_maximum = 99; - m_value = m_minimum; - m_displayIntegerBase = 10; - m_numChars = 2; - m_hasPadding = false; - this->updateEdit(); connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onEditFinished())); }; void UIntSpinBox::setValue(uint32_t val) { - val = qMax(m_minimum, qMin(m_maximum, val)); + val = qBound(m_minimum, val, m_maximum); if (m_value != val) { m_value = val; emit valueChanged(m_value); @@ -69,6 +69,12 @@ void UIntSpinBox::setRange(uint32_t min, uint32_t max) { this->updateEdit(); } +void UIntSpinBox::setSingleStep(uint32_t val) { + if (m_singleStep != val) { + m_singleStep = val; + } +} + void UIntSpinBox::setPrefix(const QString &prefix) { if (m_prefix != prefix) { m_prefix = prefix; @@ -127,6 +133,7 @@ void UIntSpinBox::onEditFinished() { } void UIntSpinBox::stepBy(int steps) { + steps *= m_singleStep; auto newValue = m_value; if (steps < 0 && newValue + steps > newValue) { newValue = 0; diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index fd2d9216..ea5b4814 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -367,7 +367,7 @@ QChart* WildMonChart::createLevelDistributionChart() { series->attachAxis(axisY); // We round the y-axis max up to a multiple of 5. - axisY->setMax(Util::roundUp(qCeil(axisY->max()), 5)); + axisY->setMax(Util::roundUpToMultiple(qCeil(axisY->max()), 5)); return chart; } From 4ea92dd9b9ea5ebf6cc490f239a6e3f9da8741c4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Jul 2025 11:32:25 -0400 Subject: [PATCH 24/71] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bec4738a..2cfad804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ## [Unreleased] ### Added +- Add an option under `Preferences` to include common scripts in the autocomplete for Script labels. - A link to Porymap's manual is now available under `Help`. ### Changed From 5fa5638277090d71b77de4f09f282c0c77d7710d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Jul 2025 14:20:39 -0400 Subject: [PATCH 25/71] More usage of Tile/Metatile size constants --- include/config.h | 52 +--------------- include/core/map.h | 10 ++-- include/core/mapconnection.h | 2 +- include/core/maplayout.h | 5 +- include/core/metatile.h | 12 ++-- include/core/tile.h | 6 +- include/project.h | 1 + include/ui/connectionpixmapitem.h | 5 +- include/ui/mapruler.h | 5 +- include/ui/metatileselector.h | 2 +- include/ui/movablerect.h | 5 +- include/ui/resizelayoutpopup.h | 5 +- include/ui/selectablepixmapitem.h | 19 +++--- include/ui/tileseteditortileselector.h | 2 +- src/config.cpp | 57 +++++++++++++++++- src/core/map.cpp | 30 +++------- src/core/mapconnection.cpp | 10 ++-- src/core/maplayout.cpp | 60 ++++++++----------- src/core/tileset.cpp | 10 ++-- src/editor.cpp | 14 ++--- src/mainwindow.cpp | 4 +- src/project.cpp | 28 ++++++--- src/ui/bordermetatilespixmapitem.cpp | 6 +- src/ui/connectionpixmapitem.cpp | 4 +- src/ui/currentselectedmetatilespixmapitem.cpp | 8 +-- src/ui/cursortilerect.cpp | 4 +- src/ui/graphicsview.cpp | 2 +- src/ui/mapimageexporter.cpp | 32 +++++----- src/ui/mapruler.cpp | 10 +++- src/ui/metatilelayersitem.cpp | 6 +- src/ui/movablerect.cpp | 29 ++++----- src/ui/prefabcreationdialog.cpp | 8 +-- src/ui/resizelayoutpopup.cpp | 47 ++++++++------- src/ui/tileseteditor.cpp | 20 ++++--- src/ui/tileseteditortileselector.cpp | 11 ++-- 35 files changed, 274 insertions(+), 257 deletions(-) diff --git a/include/config.h b/include/config.h index 51c11a74..6e38ae4a 100644 --- a/include/config.h +++ b/include/config.h @@ -66,57 +66,7 @@ class PorymapConfig: public KeyValueConfigBase { public: PorymapConfig(); - virtual void reset() override { - setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - this->recentProjects.clear(); - this->projectManuallyClosed = false; - this->reopenOnLaunch = true; - this->mapListTab = 0; - this->mapListEditGroupsEnabled = false; - this->mapListHideEmptyEnabled.clear(); - this->prettyCursors = true; - this->mirrorConnectingMaps = true; - this->showDiveEmergeMaps = false; - this->diveEmergeMapOpacity = 30; - this->diveMapOpacity = 15; - this->emergeMapOpacity = 15; - this->collisionOpacity = 50; - this->collisionZoom = 30; - this->metatilesZoom = 30; - this->tilesetEditorMetatilesZoom = 30; - this->tilesetEditorTilesZoom = 30; - this->showPlayerView = false; - this->showCursorTile = true; - this->showBorder = true; - this->showGrid = false; - this->showTilesetEditorMetatileGrid = false; - this->showTilesetEditorLayerGrid = true; - this->showTilesetEditorDivider = false; - this->showTilesetEditorRawAttributes = false; - this->monitorFiles = true; - this->tilesetCheckerboardFill = true; - this->newMapHeaderSectionExpanded = false; - this->theme = "default"; - this->wildMonChartTheme = ""; - this->textEditorOpenFolder = ""; - this->textEditorGotoLine = ""; - this->paletteEditorBitDepth = 24; - this->projectSettingsTab = 0; - this->loadAllEventScripts = false; - this->warpBehaviorWarningDisabled = false; - this->eventDeleteWarningDisabled = false; - this->eventOverlayEnabled = false; - this->checkForUpdates = true; - this->lastUpdateCheckTime = QDateTime(); - this->lastUpdateCheckVersion = porymapVersion; - this->rateLimitTimes.clear(); - this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; - this->shownInGameReloadMessage = false; - this->gridSettings = GridSettings(); - this->statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN }; - this->applicationFont = QFont(); - this->mapListFont = PorymapConfig::defaultMapListFont(); - } + virtual void reset() override; void addRecentProject(QString project); void setRecentProjects(QStringList projects); QString getRecentProject(); diff --git a/include/core/map.h b/include/core/map.h index f5cc1ef1..3b843f2a 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -51,10 +51,12 @@ public: void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; } QString layoutId() const { return layout() ? layout()->id : m_layoutId; } - int getWidth() const; - int getHeight() const; - int getBorderWidth() const; - int getBorderHeight() const; + int getWidth() const { return m_layout ? m_layout->getWidth() : 0; } + int getHeight() const { return m_layout ? m_layout->getHeight() : 0; } + int getBorderWidth() const { return m_layout ? m_layout->getBorderWidth() : 0; } + int getBorderHeight() const { return m_layout ? m_layout->getBorderHeight() : 0; } + int pixelWidth() const { return m_layout ? m_layout->pixelWidth() : 0; } + int pixelHeight() const { return m_layout ? m_layout->pixelHeight() : 0; } void setHeader(const MapHeader &header) { *m_header = header; } MapHeader* header() const { return m_header; } diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 5df071bd..ba7c95a8 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -42,7 +42,7 @@ public: MapConnection* createMirror(); QPixmap render() const; - QPoint relativePos(bool clipped = false) const; + QPoint relativePixelPos(bool clipped = false) const; static QPointer project; static const QMap oppositeDirections; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 8091469a..2dc3cc24 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -32,6 +32,9 @@ public: int height; int border_width; int border_height; + int pixelWidth() const { return this->width * Metatile::pixelWidth(); } + int pixelHeight() const { return this->height * Metatile::pixelHeight(); } + QSize pixelSize() const { return QSize(pixelWidth(), pixelHeight()); } QString border_path; QString blockdata_path; @@ -153,7 +156,7 @@ public: void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); + QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, const QRect &bounds = QRect(0, 0, -1, -1)); QPixmap renderCollision(bool ignoreCache); // QPixmap renderConnection(MapConnection, Layout *); QPixmap renderBorder(bool ignoreCache = false); diff --git a/include/core/metatile.h b/include/core/metatile.h index a57717fe..e35e1fa3 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -64,12 +64,12 @@ public: static QString getMetatileIdStrings(const QList metatileIds); static QString getLayerName(int layerNum); - static int tileWidth() { return 2; } - static int tileHeight() { return 2; } - static int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); } - static int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); } - static int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); } - static QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); } + static constexpr int tileWidth() { return 2; } + static constexpr int tileHeight() { return 2; } + static constexpr int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); } + static constexpr int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); } + static constexpr int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); } + static constexpr QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); } inline bool operator==(const Metatile &other) { return this->tiles == other.tiles && this->attributes == other.attributes; diff --git a/include/core/tile.h b/include/core/tile.h index d7ae5061..585ceebf 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -26,9 +26,9 @@ public: static const uint16_t maxValue; - static int pixelWidth() { return 8; } - static int pixelHeight() { return 8; } - static QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); } + static constexpr int pixelWidth() { return 8; } + static constexpr int pixelHeight() { return 8; } + static constexpr QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); } }; inline bool operator==(const Tile &a, const Tile &b) { diff --git a/include/project.h b/include/project.h index ca7d9bbe..c5ef6338 100644 --- a/include/project.h +++ b/include/project.h @@ -251,6 +251,7 @@ public: static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); + static QMargins getPixelViewDistance(); static QMargins getMetatileViewDistance(); static int getNumTilesPrimary() { return num_tiles_primary; } static int getNumTilesTotal() { return num_tiles_total; } diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 32e309f9..5106ba8f 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -2,6 +2,7 @@ #define CONNECTIONPIXMAPITEM_H #include "mapconnection.h" +#include "metatile.h" #include #include #include @@ -31,8 +32,8 @@ private: bool selected = false; unsigned actionId = 0; - static const int mWidth = 16; - static const int mHeight = 16; + static const int mWidth = Metatile::pixelWidth(); + static const int mHeight = Metatile::pixelHeight(); void updatePos(); void updateOrigin(); diff --git a/include/ui/mapruler.h b/include/ui/mapruler.h index 151492e3..160978cc 100644 --- a/include/ui/mapruler.h +++ b/include/ui/mapruler.h @@ -3,6 +3,7 @@ #include #include +#include "metatile.h" class MapRuler : public QGraphicsObject, private QLine @@ -63,8 +64,8 @@ private: QPoint snapToWithinBounds(QPoint pos) const; void updateGeometry(); void updateStatus(Qt::Corner corner); - int pixWidth() const { return width() * 16; } - int pixHeight() const { return height() * 16; } + int pixWidth() const { return width() * Metatile::pixelWidth(); } + int pixHeight() const { return height() * Metatile::pixelHeight(); } }; #endif // MAPRULER_H diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index f5faa1e0..d7c6a40b 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -31,7 +31,7 @@ struct MetatileSelection class MetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(16, 16) { + MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(Metatile::pixelWidth(), Metatile::pixelHeight()) { this->externalSelection = false; this->prefabSelection = false; this->numMetatilesWide = numMetatilesWide; diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 552ad6f5..7940e592 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -10,7 +10,7 @@ class MovableRect : public QGraphicsRectItem { public: - MovableRect(const QRectF &rect, const QRgb &color); + MovableRect(const QRectF &rect, const QSize &cellSize, const QRgb &color); QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, @@ -31,6 +31,7 @@ public: protected: QRectF baseRect; + QSize cellSize; QRgb color; void updateVisibility(); @@ -43,7 +44,7 @@ class ResizableRect : public QObject, public MovableRect { Q_OBJECT public: - ResizableRect(QObject *parent, int width, int height, QRgb color); + ResizableRect(QObject *parent, const QSize &cellSize, const QSize &size, const QRgb &color); QRectF boundingRect() const override { return QRectF(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h index 89ce2e99..4f8482a7 100644 --- a/include/ui/resizelayoutpopup.h +++ b/include/ui/resizelayoutpopup.h @@ -45,10 +45,10 @@ private: /// PixmapItem subclass which allows for creating a boundary which determine whether /// the pixmap paints normally or with a black tint. -/// This item is movable and snaps on a 16x16 grid. +/// This item is movable and snaps on a 'cellSize' grid. class BoundedPixmapItem : public QGraphicsPixmapItem { public: - BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = nullptr); + BoundedPixmapItem(const QPixmap &pixmap, const QSize &cellSize, QGraphicsItem *parent = nullptr); void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override; void setBoundary(ResizableRect *rect) { this->boundary = rect; } @@ -59,6 +59,7 @@ protected: private: ResizableRect *boundary = nullptr; QPointF clickedPos = QPointF(); + QSize cellSize; }; diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h index 2681e485..d497e00d 100644 --- a/include/ui/selectablepixmapitem.h +++ b/include/ui/selectablepixmapitem.h @@ -7,13 +7,18 @@ class SelectablePixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - SelectablePixmapItem(int cellWidth, int cellHeight): SelectablePixmapItem(cellWidth, cellHeight, INT_MAX, INT_MAX) {} - SelectablePixmapItem(int cellWidth, int cellHeight, int maxSelectionWidth, int maxSelectionHeight) { - this->cellWidth = cellWidth; - this->cellHeight = cellHeight; - this->maxSelectionWidth = maxSelectionWidth; - this->maxSelectionHeight = maxSelectionHeight; - } + SelectablePixmapItem(const QSize &size, const QSize &maxSelectionSize = QSize(INT_MAX, INT_MAX)) + : SelectablePixmapItem(size.width(), size.height(), maxSelectionSize.width(), maxSelectionSize.height()) {} + SelectablePixmapItem(int cellWidth, int cellHeight, int maxSelectionWidth = INT_MAX, int maxSelectionHeight = INT_MAX) + : cellWidth(cellWidth), + cellHeight(cellHeight), + maxSelectionWidth(maxSelectionWidth), + maxSelectionHeight(maxSelectionHeight), + selectionInitialX(0), + selectionInitialY(0), + selectionOffsetX(0), + selectionOffsetY(0) + {} virtual QPoint getSelectionDimensions(); virtual void draw() = 0; diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index aa2a1923..bed655e1 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -8,7 +8,7 @@ class TilesetEditorTileSelector: public SelectablePixmapItem { Q_OBJECT public: TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, int numLayers) - : SelectablePixmapItem(16, 16, numLayers * 2, 2) { + : SelectablePixmapItem(16, 16, numLayers * Metatile::tileWidth(), Metatile::tileHeight()) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; this->numTilesWide = 16; diff --git a/src/config.cpp b/src/config.cpp index 8b2f24a9..f3457e9e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -4,6 +4,7 @@ #include "map.h" #include "validator.h" #include "utility.h" +#include "metatile.h" #include #include #include @@ -315,6 +316,60 @@ PorymapConfig::PorymapConfig() : KeyValueConfigBase(QStringLiteral("porymap.cfg" reset(); } +void PorymapConfig::reset() { + setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + this->recentProjects.clear(); + this->projectManuallyClosed = false; + this->reopenOnLaunch = true; + this->mapListTab = 0; + this->mapListEditGroupsEnabled = false; + this->mapListHideEmptyEnabled.clear(); + this->prettyCursors = true; + this->mirrorConnectingMaps = true; + this->showDiveEmergeMaps = false; + this->diveEmergeMapOpacity = 30; + this->diveMapOpacity = 15; + this->emergeMapOpacity = 15; + this->collisionOpacity = 50; + this->collisionZoom = 30; + this->metatilesZoom = 30; + this->tilesetEditorMetatilesZoom = 30; + this->tilesetEditorTilesZoom = 30; + this->showPlayerView = false; + this->showCursorTile = true; + this->showBorder = true; + this->showGrid = false; + this->showTilesetEditorMetatileGrid = false; + this->showTilesetEditorLayerGrid = true; + this->showTilesetEditorDivider = false; + this->showTilesetEditorRawAttributes = false; + this->monitorFiles = true; + this->tilesetCheckerboardFill = true; + this->newMapHeaderSectionExpanded = false; + this->theme = "default"; + this->wildMonChartTheme = ""; + this->textEditorOpenFolder = ""; + this->textEditorGotoLine = ""; + this->paletteEditorBitDepth = 24; + this->projectSettingsTab = 0; + this->loadAllEventScripts = false; + this->warpBehaviorWarningDisabled = false; + this->eventDeleteWarningDisabled = false; + this->eventOverlayEnabled = false; + this->checkForUpdates = true; + this->lastUpdateCheckTime = QDateTime(); + this->lastUpdateCheckVersion = porymapVersion; + this->rateLimitTimes.clear(); + this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; + this->shownInGameReloadMessage = false; + this->gridSettings = GridSettings(); + this->gridSettings.width = Metatile::pixelWidth(); + this->gridSettings.height = Metatile::pixelHeight(); + this->statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN }; + this->applicationFont = QFont(); + this->mapListFont = PorymapConfig::defaultMapListFont(); +} + void PorymapConfig::parseConfigKeyValue(QString key, QString value) { if (key == "recent_project") { this->recentProjects = value.split(",", Qt::SkipEmptyParts); @@ -1186,7 +1241,7 @@ int ProjectConfig::getNumLayersInMetatile() { } int ProjectConfig::getNumTilesInMetatile() { - return this->tripleLayerMetatilesEnabled ? 12 : 8; + return getNumLayersInMetatile() * Metatile::tilesPerLayer(); } void ProjectConfig::setEventIconPath(Event::Group group, const QString &path) { diff --git a/src/core/map.cpp b/src/core/map.cpp index 03fee13e..079f336a 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -62,44 +62,28 @@ QString Map::mapConstantFromName(const QString &name) { return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); } -int Map::getWidth() const { - return m_layout ? m_layout->getWidth() : 0; -} - -int Map::getHeight() const { - return m_layout ? m_layout->getHeight() : 0; -} - -int Map::getBorderWidth() const { - return m_layout ? m_layout->getBorderWidth() : 0; -} - -int Map::getBorderHeight() const { - return m_layout ? m_layout->getBorderHeight() : 0; -} - // Get the portion of the map that can be rendered when rendered as a map connection. // Cardinal connections render the nearest segment of their map and within the bounds of the border draw distance, // Dive/Emerge connections are rendered normally within the bounds of their parent map. QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) const { int x = 0, y = 0; - int w = getWidth(), h = getHeight(); + int w = pixelWidth(), h = pixelHeight(); - QMargins viewDistance = Project::getMetatileViewDistance(); + QMargins viewDistance = Project::getPixelViewDistance(); if (direction == "up") { h = qMin(h, viewDistance.top()); - y = getHeight() - h; + y = pixelHeight() - h; } else if (direction == "down") { h = qMin(h, viewDistance.bottom()); } else if (direction == "left") { w = qMin(w, viewDistance.left()); - x = getWidth() - w; + x = pixelWidth() - w; } else if (direction == "right") { w = qMin(w, viewDistance.right()); } else if (MapConnection::isDiving(direction)) { if (fromLayout) { - w = qMin(w, fromLayout->getWidth()); - h = qMin(h, fromLayout->getHeight()); + w = qMin(w, fromLayout->pixelWidth()); + h = qMin(h, fromLayout->pixelHeight()); } } else { // Unknown direction @@ -122,7 +106,7 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { fromLayout = nullptr; QPixmap connectionPixmap = m_layout->render(true, fromLayout, bounds); - return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); + return connectionPixmap.copy(bounds); } void Map::openScript(const QString &label) { diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index d37a8c74..3e5d82dd 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -72,20 +72,20 @@ QPixmap MapConnection::render() const { // For right/down connections this is offset by the dimensions of the parent map. // For left/up connections this is offset by the dimensions of the target map. // If 'clipped' is true, only the rendered dimensions of the target map will be used, rather than its full dimensions. -QPoint MapConnection::relativePos(bool clipped) const { +QPoint MapConnection::relativePixelPos(bool clipped) const { int x = 0, y = 0; if (m_direction == "right") { - if (m_parentMap) x = m_parentMap->getWidth(); + if (m_parentMap) x = m_parentMap->pixelWidth(); y = m_offset; } else if (m_direction == "down") { x = m_offset; - if (m_parentMap) y = m_parentMap->getHeight(); + if (m_parentMap) y = m_parentMap->pixelHeight(); } else if (m_direction == "left") { - if (targetMap()) x = !clipped ? -targetMap()->getWidth() : -targetMap()->getConnectionRect(m_direction).width(); + if (targetMap()) x = !clipped ? -targetMap()->pixelWidth() : -targetMap()->getConnectionRect(m_direction).width(); y = m_offset; } else if (m_direction == "up") { x = m_offset; - if (targetMap()) y = !clipped ? -targetMap()->getHeight() : -targetMap()->getConnectionRect(m_direction).height(); + if (targetMap()) y = !clipped ? -targetMap()->pixelHeight() : -targetMap()->getConnectionRect(m_direction).height(); } return QPoint(x, y); } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 7ab13ec7..a8d3934d 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -92,12 +92,11 @@ QMargins Layout::getBorderMargins() const { return distance; } -// Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border. -// At maximum, this is equal to the map size plus the border margins. -// If the border is large (and so beyond player the view) it may be smaller than that. +// Get a rectangle that represents (in pixels) the layout's map area + the distance the player can see. +// Note that this may be smaller than the map area + the size of the border for layouts with large border dimensions. QRect Layout::getVisibleRect() const { - QRect area = QRect(0, 0, this->width * 16, this->height * 16); - return area += (Project::getMetatileViewDistance() * 16); + QRect area = QRect(0, 0, this->pixelWidth(), this->pixelHeight()); + return area += Project::getPixelViewDistance(); } bool Layout::getBlock(int x, int y, Block *out) { @@ -344,15 +343,13 @@ void Layout::magicFillCollisionElevation(int initialX, int initialY, uint16_t co } } -QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { +QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, const QRect &bounds) { bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (this->image.isNull() || this->image.width() != width_ * 16 || this->image.height() != height_ * 16) { - this->image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + if (this->image.isNull() || this->image.width() != pixelWidth() || this->image.height() != pixelHeight()) { + this->image = QImage(pixelWidth(), pixelHeight(), QImage::Format_RGBA8888); changed_any = true; } - if (this->blockdata.isEmpty() || !width_ || !height_) { + if (this->blockdata.isEmpty() || this->width == 0 || this->height == 0) { this->pixmap = this->pixmap.fromImage(this->image); return this->pixmap; } @@ -368,9 +365,9 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { if (!ignoreCache && !layoutBlockChanged(i, this->blockdata, this->cached_blockdata)) { continue; } - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - if (bounds.isValid() && !bounds.contains(map_x, map_y)) { + int x = this->width ? ((i % this->width) * Metatile::pixelWidth()) : 0; + int y = this->width ? ((i / this->width) * Metatile::pixelHeight()) : 0; + if (bounds.isValid() && !bounds.contains(x, y)) { continue; } @@ -388,9 +385,7 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { ); imageCache.insert(metatileId, metatileImage); } - - QPoint metatileOrigin = QPoint(map_x * 16, map_y * 16); - painter.drawImage(metatileOrigin, metatileImage); + painter.drawImage(x, y, metatileImage); changed_any = true; } painter.end(); @@ -404,13 +399,11 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { QPixmap Layout::renderCollision(bool ignoreCache) { bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { - collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + if (collision_image.isNull() || collision_image.width() != pixelWidth() || collision_image.height() != pixelHeight()) { + collision_image = QImage(pixelWidth(), pixelHeight(), QImage::Format_RGBA8888); changed_any = true; } - if (this->blockdata.isEmpty() || !width_ || !height_) { + if (this->blockdata.isEmpty() || this->width == 0 || this->height == 0) { collision_pixmap = collision_pixmap.fromImage(collision_image); return collision_pixmap; } @@ -422,10 +415,9 @@ QPixmap Layout::renderCollision(bool ignoreCache) { changed_any = true; Block block = this->blockdata.at(i); QImage collision_metatile_image = getCollisionMetatileImage(block); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - painter.drawImage(metatile_origin, collision_metatile_image); + int x = this->width ? ((i % this->width) * Metatile::pixelWidth()) : 0; + int y = this->width ? ((i / this->width) * Metatile::pixelHeight()) : 0; + painter.drawImage(x, y, collision_metatile_image); } painter.end(); cacheCollision(); @@ -437,14 +429,14 @@ QPixmap Layout::renderCollision(bool ignoreCache) { QPixmap Layout::renderBorder(bool ignoreCache) { bool changed_any = false, border_resized = false; - int width_ = getBorderWidth(); - int height_ = getBorderHeight(); + int pixelWidth = this->border_width * Metatile::pixelWidth(); + int pixelHeight = this->border_height * Metatile::pixelHeight(); if (this->border_image.isNull()) { - this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + this->border_image = QImage(pixelWidth, pixelHeight, QImage::Format_RGBA8888); changed_any = true; } - if (this->border_image.width() != width_ * 16 || this->border_image.height() != height_ * 16) { - this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + if (this->border_image.width() != pixelWidth || this->border_image.height() != pixelHeight) { + this->border_image = QImage(pixelWidth, pixelHeight, QImage::Format_RGBA8888); border_resized = true; } if (this->border.isEmpty()) { @@ -461,9 +453,9 @@ QPixmap Layout::renderBorder(bool ignoreCache) { Block block = this->border.at(i); uint16_t metatileId = block.metatileId(); QImage metatile_image = getMetatileImage(metatileId, this); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); + int x = this->border_width ? ((i % this->border_width) * Metatile::pixelWidth()) : 0; + int y = this->border_width ? ((i / this->border_width) * Metatile::pixelHeight()) : 0; + painter.drawImage(x, y, metatile_image); } painter.end(); if (changed_any) { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 75c9c362..ca0650fe 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -501,7 +501,7 @@ bool Tileset::loadTilesImage(QImage *importedImage) { image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); } else { // Use default image - image = QImage(8, 8, QImage::Format_Indexed8); + image = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_Indexed8); } // Validate image contains 16 colors. @@ -517,11 +517,9 @@ bool Tileset::loadTilesImage(QImage *importedImage) { } QList tiles; - int w = 8; - int h = 8; - for (int y = 0; y < image.height(); y += h) - for (int x = 0; x < image.width(); x += w) { - QImage tile = image.copy(x, y, w, h); + for (int y = 0; y < image.height(); y += Tile::pixelHeight()) + for (int x = 0; x < image.width(); x += Tile::pixelWidth()) { + QImage tile = image.copy(x, y, Tile::pixelWidth(), Tile::pixelHeight()); tiles.append(tile); } this->tilesImage = image; diff --git a/src/editor.cpp b/src/editor.cpp index d301d60e..b043c942 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -30,7 +30,7 @@ Editor::Editor(Ui::MainWindow* ui) { this->ui = ui; this->settings = new Settings(); - this->cursorMapTileRect = new CursorTileRect(QSize(16,16), qRgb(255, 255, 255)); + this->cursorMapTileRect = new CursorTileRect(Metatile::pixelSize(), qRgb(255, 255, 255)); this->map_ruler = new MapRuler(4); connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged); @@ -1171,7 +1171,7 @@ bool Editor::isMouseInMap() const { void Editor::setPlayerViewRect(const QRectF &rect) { delete this->playerViewRect; - this->playerViewRect = new MovableRect(rect, qRgb(255, 255, 255)); + this->playerViewRect = new MovableRect(rect, Metatile::pixelSize(), qRgb(255, 255, 255)); updateCursorRectVisibility(); } @@ -1913,8 +1913,8 @@ void Editor::displayMapBorder() { for (int y = -borderMargins.top(); y < this->layout->getHeight() + borderMargins.bottom(); y += this->layout->getBorderHeight()) for (int x = -borderMargins.left(); x < this->layout->getWidth() + borderMargins.right(); x += this->layout->getBorderWidth()) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); - item->setX(x * 16); - item->setY(y * 16); + item->setX(x * Metatile::pixelWidth()); + item->setY(y * Metatile::pixelHeight()); item->setZValue(ZValue::MapBorder); scene->addItem(item); borderItems.append(item); @@ -1962,8 +1962,8 @@ void Editor::displayMapGrid() { // elements of the scripting API, so they're painted manually in MapView::drawForeground. this->mapGrid = new QGraphicsItemGroup(); - const int pixelMapWidth = this->layout->getWidth() * 16; - const int pixelMapHeight = this->layout->getHeight() * 16; + const int pixelMapWidth = this->layout->pixelWidth(); + const int pixelMapHeight = this->layout->pixelHeight(); // The grid can be moved with a user-specified x/y offset. The grid's dash patterns will only wrap in full pattern increments, // so we draw an additional row/column outside the map that can be revealed as the offset changes. @@ -2476,7 +2476,7 @@ void Editor::setCollisionGraphics() { // Use the image sheet to create an icon for each collision/elevation combination. // Any icons for combinations that aren't provided by the image sheet are also created now using default graphics. - const int w = 16, h = 16; + const int w = Metatile::pixelWidth(), h = Metatile::pixelHeight(); imgSheet = imgSheet.scaled(w * imgColumns, h * imgRows); for (int collision = 0; collision <= Block::getMaxCollision(); collision++) { // If (collision >= imgColumns) here, it's a valid collision value, but it is not represented with an icon on the image sheet. diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8d44895e..36aef9e5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1408,7 +1408,7 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled); - this->editor->setPlayerViewRect(QRectF(0, 0, 16, 16).marginsAdded(projectConfig.playerViewDistance)); + this->editor->setPlayerViewRect(QRectF(QPoint(0,0), Metatile::pixelSize()).marginsAdded(projectConfig.playerViewDistance)); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); @@ -1809,7 +1809,7 @@ void MainWindow::scrollMetatileSelectorToSelection() { QPoint pos = editor->metatile_selector_item->getMetatileIdCoordsOnWidget(selection.metatileItems.first().metatileId); QPoint size = editor->metatile_selector_item->getSelectionDimensions(); - pos += QPoint(size.x() - 1, size.y() - 1) * 16 / 2; // We want to focus on the center of the whole selection + pos += QPoint((size.x() - 1) * Metatile::pixelWidth(), (size.y() - 1) * Metatile::pixelHeight()) / 2; // We want to focus on the center of the whole selection pos *= getMetatilesZoomScale(); auto viewport = ui->scrollArea_MetatileSelector->viewport(); diff --git a/src/project.cpp b/src/project.cpp index 9a65ac38..cc3a567f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2400,12 +2400,11 @@ bool Project::readFieldmapProperties() { // We can determine whether triple-layer metatiles are in-use by reading this constant. // If the constant is missing (or is using a value other than 8 or 12) the user must tell // us whether they're using triple-layer metatiles under Project Settings. - static const int numTilesPerLayer = 4; int numTilesPerMetatile = it.value(); - if (numTilesPerMetatile == 2 * numTilesPerLayer) { + if (numTilesPerMetatile == 2 * Metatile::tilesPerLayer()) { projectConfig.tripleLayerMetatilesEnabled = false; this->disabledSettingsNames.insert(numTilesPerMetatileName); - } else if (numTilesPerMetatile == 3 * numTilesPerLayer) { + } else if (numTilesPerMetatile == 3 * Metatile::tilesPerLayer()) { projectConfig.tripleLayerMetatilesEnabled = true; this->disabledSettingsNames.insert(numTilesPerMetatileName); } @@ -3422,14 +3421,25 @@ QString Project::getEmptySpeciesName() { return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty); } -// Get the distance in metatiles (rounded up) that the player is able to see in each direction in-game. +// Get the distance in pixels that the player is able to see in each direction in-game, +// rounded up to a multiple of a metatile's pixel size. +QMargins Project::getPixelViewDistance() { + QMargins viewDistance = projectConfig.playerViewDistance; + viewDistance.setTop(Util::roundUpToMultiple(viewDistance.top(), Metatile::pixelHeight())); + viewDistance.setBottom(Util::roundUpToMultiple(viewDistance.bottom(), Metatile::pixelHeight())); + viewDistance.setLeft(Util::roundUpToMultiple(viewDistance.left(), Metatile::pixelWidth())); + viewDistance.setRight(Util::roundUpToMultiple(viewDistance.right(), Metatile::pixelWidth())); + return viewDistance; +} + +// Get the distance in metatiles that the player is able to see in each direction in-game. // For the default view distance (i.e. assuming the player is centered in a 240x160 pixel GBA screen) this is 7x5 metatiles. QMargins Project::getMetatileViewDistance() { - QMargins viewDistance = projectConfig.playerViewDistance; - viewDistance.setTop(qCeil(viewDistance.top() / 16.0)); - viewDistance.setBottom(qCeil(viewDistance.bottom() / 16.0)); - viewDistance.setLeft(qCeil(viewDistance.left() / 16.0)); - viewDistance.setRight(qCeil(viewDistance.right() / 16.0)); + QMargins viewDistance = getPixelViewDistance(); + viewDistance.setTop(viewDistance.top() / Metatile::pixelHeight()); + viewDistance.setBottom(viewDistance.bottom() / Metatile::pixelHeight()); + viewDistance.setLeft(viewDistance.left() / Metatile::pixelWidth()); + viewDistance.setRight(viewDistance.right() / Metatile::pixelWidth()); return viewDistance; } diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index 9d6ab07f..4e847b1b 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -39,13 +39,13 @@ void BorderMetatilesPixmapItem::draw() { int width = layout->getBorderWidth(); int height = layout->getBorderHeight(); - QImage image(16 * width, 16 * height, QImage::Format_RGBA8888); + QImage image(width * Metatile::pixelWidth(), height * Metatile::pixelHeight(), QImage::Format_RGBA8888); QPainter painter(&image); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { - int x = i * 16; - int y = j * 16; + int x = i * Metatile::pixelWidth(); + int y = j * Metatile::pixelHeight(); QImage metatile_image = getMetatileImage(layout->getBorderMetatileId(i, j), layout); QPoint metatile_origin = QPoint(x, y); painter.drawImage(metatile_origin, metatile_image); diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index b4fbfa12..db05ffab 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -100,9 +100,9 @@ void ConnectionPixmapItem::updatePos() { void ConnectionPixmapItem::updateOrigin() { if (this->connection->isVertical()) { this->originX = 0; - this->originY = this->connection->relativePos(true).y() * this->mHeight; + this->originY = this->connection->relativePixelPos(true).y(); } else if (this->connection->isHorizontal()) { - this->originX = this->connection->relativePos(true).x() * this->mWidth; + this->originX = this->connection->relativePixelPos(true).x(); this->originY = 0; } updatePos(); diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp index dcc3cb24..33425131 100644 --- a/src/ui/currentselectedmetatilespixmapitem.cpp +++ b/src/ui/currentselectedmetatilespixmapitem.cpp @@ -3,16 +3,16 @@ #include QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) { - int width = selection.dimensions.x() * 16; - int height = selection.dimensions.y() * 16; + int width = selection.dimensions.x() * Metatile::pixelWidth(); + int height = selection.dimensions.y() * 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++) { - int x = i * 16; - int y = j * 16; + int x = i * Metatile::pixelWidth(); + int y = j * Metatile::pixelHeight(); QPoint metatile_origin = QPoint(x, y); int index = j * selection.dimensions.x() + i; MetatileSelectionItem item = selection.metatileItems.at(index); diff --git a/src/ui/cursortilerect.cpp b/src/ui/cursortilerect.cpp index ef1de285..e62bcdc8 100644 --- a/src/ui/cursortilerect.cpp +++ b/src/ui/cursortilerect.cpp @@ -65,6 +65,6 @@ void CursorTileRect::updateLocation(int coordX, int coordY) { } } - this->setX(coordX * 16); - this->setY(coordY * 16); + this->setX(coordX * m_tileSize.width()); + this->setY(coordY * m_tileSize.height()); } diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index c8fc4495..667e0aab 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -33,7 +33,7 @@ void MapView::drawForeground(QPainter *painter, const QRectF&) { painter->save(); if (editor->layout) { // We're clipping here to hide parts of the grid that are outside the map. - const QRectF mapRect(-0.5, -0.5, editor->layout->getWidth() * 16 + 1.5, editor->layout->getHeight() * 16 + 1.5); + const QRectF mapRect(-0.5, -0.5, editor->layout->pixelWidth() + 1.5, editor->layout->pixelHeight() + 1.5); painter->setClipping(true); painter->setClipRect(mapRect); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 59efbe4b..450ad9d0 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -333,8 +333,8 @@ QGifImage* MapImageExporter::createTimelapseGifImage(QProgressDialog *progress) if (currentHistoryAppliesToFrame(step.historyStack) || step.historyStack->index() == step.initialStackIndex) { // Either this is relevant edit history, or it's the final frame (which is always rendered). Record the size of the map at this point. QMargins margins = getMargins(m_map); - canvasSize = canvasSize.expandedTo(QSize(m_layout->getWidth() * 16 + margins.left() + margins.right(), - m_layout->getHeight() * 16 + margins.top() + margins.bottom())); + canvasSize = canvasSize.expandedTo(QSize(m_layout->pixelWidth() + margins.left() + margins.right(), + m_layout->pixelHeight() + margins.top() + margins.bottom())); } if (step.historyStack->canUndo()){ step.historyStack->undo(); @@ -434,8 +434,8 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress) { if (!connection->isCardinal()) continue; Map *connectedMap = connection->targetMap(); if (!connectedMap) continue; - QPoint pos = connection->relativePos(); - unvisited.append(StitchedMap{cur.x + (pos.x() * 16), cur.y + (pos.y() * 16), connectedMap}); + QPoint pos = connection->relativePixelPos(); + unvisited.append(StitchedMap{cur.x + pos.x(), cur.y + pos.y(), connectedMap}); } } if (stitchedMaps.isEmpty()) @@ -447,7 +447,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress) { // Determine the overall dimensions of the stitched maps. QRect dimensions = QRect(0, 0, m_map->getWidth(), m_map->getHeight()) + getMargins(m_map); for (const StitchedMap &map : stitchedMaps) { - dimensions |= (QRect(map.x, map.y, map.map->getWidth() * 16, map.map->getHeight() * 16) + getMargins(map.map)); + dimensions |= (QRect(map.x, map.y, map.map->pixelWidth(), map.map->pixelHeight()) + getMargins(map.map)); } QPixmap stitchedPixmap(dimensions.width(), dimensions.height()); @@ -602,7 +602,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap() { QMargins MapImageExporter::getMargins(const Map *map) { QMargins margins; if (m_settings.showBorder) { - margins = m_project->getMetatileViewDistance() * 16; + margins = m_project->getPixelViewDistance(); } else if (map && connectionsEnabled()) { for (const auto &connection : map->getConnections()) { const QString dir = connection->direction(); @@ -612,10 +612,10 @@ QMargins MapImageExporter::getMargins(const Map *map) { if (!targetMap) continue; QRect rect = targetMap->getConnectionRect(dir); - if (dir == "up") margins.setTop(rect.height() * 16); - else if (dir == "down") margins.setBottom(rect.height() * 16); - else if (dir == "left") margins.setLeft(rect.width() * 16); - else if (dir == "right") margins.setRight(rect.width() * 16); + if (dir == "up") margins.setTop(rect.height()); + else if (dir == "down") margins.setBottom(rect.height()); + else if (dir == "left") margins.setLeft(rect.width()); + else if (dir == "right") margins.setRight(rect.width()); } } if (m_settings.showGrid) { @@ -652,7 +652,7 @@ void MapImageExporter::paintBorder(QPainter *painter, Layout *layout) { // Skip border painting if it would be fully covered by the rest of the map if (layout->isWithinBounds(QRect(x, y, layout->getBorderWidth(), layout->getBorderHeight()))) continue; - painter->drawPixmap(x * 16, y * 16, layout->border_pixmap); + painter->drawPixmap(x * Metatile::pixelWidth(), y * Metatile::pixelHeight(), layout->border_pixmap); } painter->restore(); @@ -665,7 +665,7 @@ void MapImageExporter::paintConnections(QPainter *painter, const Map *map) { for (const auto &connection : map->getConnections()) { if (!m_settings.showConnections.contains(connection->direction())) continue; - painter->drawImage(connection->relativePos(true) * 16, connection->render().toImage()); + painter->drawImage(connection->relativePixelPos(true), connection->render().toImage()); } } @@ -693,12 +693,12 @@ void MapImageExporter::paintGrid(QPainter *painter, const Layout *layout) { if (!m_settings.showGrid) return; - int w = layout->getWidth() * 16; - int h = layout->getHeight() * 16; - for (int x = 0; x <= w; x += 16) { + int w = layout->pixelWidth(); + int h = layout->pixelHeight(); + for (int x = 0; x <= w; x += Metatile::pixelWidth()) { painter->drawLine(x, 0, x, h); } - for (int y = 0; y <= h; y += 16) { + for (int y = 0; y <= h; y += Metatile::pixelHeight()) { painter->drawLine(0, y, w, y); } } diff --git a/src/ui/mapruler.cpp b/src/ui/mapruler.cpp index 908e0a46..da83821c 100644 --- a/src/ui/mapruler.cpp +++ b/src/ui/mapruler.cpp @@ -37,9 +37,11 @@ QPainterPath MapRuler::shape() const { ruler.addRect(xRuler); ruler.addRect(yRuler); ruler = ruler.simplified(); - for (int x = 16; x < pixWidth(); x += 16) + int w = Metatile::pixelWidth(); + int h = Metatile::pixelHeight(); + for (int x = w; x < pixWidth(); x += w) ruler.addRect(x, xRuler.y(), 0, thickness); - for (int y = 16; y < pixHeight(); y += 16) + for (int y = h; y < pixHeight(); y += h) ruler.addRect(yRuler.x(), y, thickness, 0); if (deltaX() && deltaY()) ruler.addPolygon(QVector({ cornerTick.p1(), cornerTick.p2() })); @@ -131,7 +133,9 @@ QPoint MapRuler::snapToWithinBounds(QPoint pos) const { void MapRuler::updateGeometry() { prepareGeometryChange(); - setPos(QPoint(left() * 16 + 8, top() * 16 + 8)); + int w = Metatile::pixelWidth(); + int h = Metatile::pixelHeight(); + setPos(QPoint(left() * w + w/2, top() * h + h/2)); /* Determine what quadrant the end point is in relative to the anchor point. The anchor * point is the top-left corner of the metatile the ruler starts in, so a zero-length * ruler is considered to be in the bottom-right quadrant from the anchor point. */ diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index b0279e4e..5ce6e68c 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -4,7 +4,7 @@ #include MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset) - : SelectablePixmapItem(16, 16, 2 * projectConfig.getNumLayersInMetatile(), 2), + : SelectablePixmapItem(16, 16, Metatile::tileWidth() * projectConfig.getNumLayersInMetatile(), Metatile::tileHeight()), metatile(metatile), primaryTileset(primaryTileset), secondaryTileset(secondaryTileset) @@ -31,8 +31,8 @@ static const QList tilePositions = { void MetatileLayersItem::draw() { const int numLayers = projectConfig.getNumLayersInMetatile(); - const int layerWidth = this->cellWidth * 2; - const int layerHeight = this->cellHeight * 2; + const int layerWidth = this->cellWidth * Metatile::tileWidth(); + const int layerHeight = this->cellHeight * Metatile::tileHeight(); QPixmap pixmap(numLayers * layerWidth, layerHeight); QPainter painter(&pixmap); diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 70e08e46..1e751aa9 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -5,16 +5,17 @@ #include "movablerect.h" #include "utility.h" -MovableRect::MovableRect(const QRectF &rect, const QRgb &color) +MovableRect::MovableRect(const QRectF &rect, const QSize &cellSize, const QRgb &color) : QGraphicsRectItem(rect), baseRect(rect), + cellSize(cellSize), color(color) { } /// Center rect on grid position (x, y) void MovableRect::updateLocation(int x, int y) { - setRect(this->baseRect.x() + (x * 16), - this->baseRect.y() + (y * 16), + setRect(this->baseRect.x() + (x * this->cellSize.width()), + this->baseRect.y() + (y * this->cellSize.height()), this->baseRect.width(), this->baseRect.height()); } @@ -24,9 +25,9 @@ void MovableRect::updateLocation(int x, int y) { ************************************************************************ ******************************************************************************/ -ResizableRect::ResizableRect(QObject *parent, int width, int height, QRgb color) +ResizableRect::ResizableRect(QObject *parent, const QSize &cellSize, const QSize &size, const QRgb &color) : QObject(parent), - MovableRect(QRect(0, 0, width * 16, height * 16), color) + MovableRect(QRect(0, 0, size.width(), size.height()), cellSize, color) { setAcceptHoverEvents(true); setFlags(this->flags() | QGraphicsItem::ItemIsMovable); @@ -114,8 +115,8 @@ void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) { } void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - int dx = Util::roundUpToMultiple(event->scenePos().x() - this->clickedPos.x(), 16); - int dy = Util::roundUpToMultiple(event->scenePos().y() - this->clickedPos.y(), 16); + int dx = Util::roundUpToMultiple(event->scenePos().x() - this->clickedPos.x(), this->cellSize.width()); + int dy = Util::roundUpToMultiple(event->scenePos().y() - this->clickedPos.y(), this->cellSize.height()); QRect resizedRect = this->clickedRect; @@ -149,20 +150,20 @@ void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { break; } - // Lower limits: smallest possible size is 16x16 square - if (resizedRect.width() < 16) { + // Lower limits: smallest possible size is 1 cell + if (resizedRect.width() < this->cellSize.width()) { if (dx < 0) { // right sided adjustment made - resizedRect.setWidth(16); + resizedRect.setWidth(this->cellSize.width()); } else { // left sided adjustment slightly more complicated - int dxMax = this->clickedRect.right() - this->clickedRect.left() - 16; + int dxMax = this->clickedRect.right() - this->clickedRect.left() - this->cellSize.width(); resizedRect.adjust(dxMax - dx, 0, 0, 0); } } - if (resizedRect.height() < 16) { + if (resizedRect.height() < this->cellSize.height()) { if (dy < 0) { // bottom - resizedRect.setHeight(16); + resizedRect.setHeight(this->cellSize.height()); } else { // top - int dyMax = this->clickedRect.bottom() - this->clickedRect.top() - 16; + int dyMax = this->clickedRect.bottom() - this->clickedRect.top() - this->cellSize.height(); resizedRect.adjust(0, dyMax - dy, 0, 0); } } diff --git a/src/ui/prefabcreationdialog.cpp b/src/ui/prefabcreationdialog.cpp index ef6e6a0f..76c71f39 100644 --- a/src/ui/prefabcreationdialog.cpp +++ b/src/ui/prefabcreationdialog.cpp @@ -23,12 +23,12 @@ 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() * 16; - int selectionHeight = this->selection.dimensions.y() * 16; + int selectionWidth = this->selection.dimensions.x() * Metatile::pixelWidth(); + int selectionHeight = this->selection.dimensions.y() * Metatile::pixelHeight(); if (pos.x() < 0 || pos.x() >= selectionWidth || pos.y() < 0 || pos.y() >= selectionHeight) return; - int metatileX = pos.x() / 16; - int metatileY = pos.y() / 16; + int metatileX = pos.x() / Metatile::pixelWidth(); + int metatileY = pos.y() / Metatile::pixelHeight(); int index = metatileY * this->selection.dimensions.x() + metatileX; bool toggledState = !this->selection.metatileItems[index].enabled; this->selection.metatileItems[index].enabled = toggledState; diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index e6b814b1..738d4861 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -41,8 +41,9 @@ void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { ************************************************************************ ******************************************************************************/ -BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) { +BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, const QSize &cellSize, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) { setFlags(this->flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable); + this->cellSize = cellSize; } void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { @@ -61,7 +62,8 @@ void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionChange && scene()) { QPointF newPos = value.toPointF(); - return QPointF(Util::roundUpToMultiple(newPos.x(), 16), Util::roundUpToMultiple(newPos.y(), 16)); + return QPointF(Util::roundUpToMultiple(newPos.x(), this->cellSize.width()), + Util::roundUpToMultiple(newPos.y(), this->cellSize.height())); } else return QGraphicsItem::itemChange(change, value); @@ -124,11 +126,13 @@ void ResizeLayoutPopup::setupLayoutView() { this->ui->spinBox_borderHeight->setValue(this->layout->getBorderHeight()); // Layout stuff - this->layoutPixmap = new BoundedPixmapItem(this->layout->pixmap); + this->layoutPixmap = new BoundedPixmapItem(this->layout->pixmap, Metatile::pixelSize()); this->scene->addItem(layoutPixmap); int maxWidth = this->project->getMaxMapWidth(); int maxHeight = this->project->getMaxMapHeight(); - QGraphicsRectItem *cover = new QGraphicsRectItem(-maxWidth * 8, -maxHeight * 8, maxWidth * 16, maxHeight * 16); + int maxPixelWidth = maxWidth * Metatile::pixelWidth() * 2; // *2 to allow reaching max dimension by expanding from 0,0 in either direction + int maxPixelHeight = maxHeight * Metatile::pixelHeight() * 2; + QGraphicsRectItem *cover = new QGraphicsRectItem(-(maxPixelWidth / 2), -(maxPixelHeight / 2), maxPixelWidth, maxPixelHeight); this->scene->addItem(cover); this->ui->spinBox_width->setMinimum(1); @@ -136,12 +140,14 @@ void ResizeLayoutPopup::setupLayoutView() { this->ui->spinBox_height->setMinimum(1); this->ui->spinBox_height->setMaximum(maxHeight); - this->outline = new ResizableRect(this, this->layout->getWidth(), this->layout->getHeight(), qRgb(255, 0, 255)); + this->outline = new ResizableRect(this, Metatile::pixelSize(), this->layout->pixelSize(), qRgb(255, 0, 255)); this->outline->setZValue(Editor::ZValue::ResizeLayoutPopup); // Ensure on top of view this->outline->setLimit(cover->rect().toAlignedRect()); connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ // Note: this extra limit check needs access to the project values, so it is done here and not ResizableRect::mouseMoveEvent - int size = this->project->getMapDataSize(rect.width() / 16, rect.height() / 16); + int metatilesWide = rect.width() / Metatile::pixelWidth(); + int metatilesTall = rect.height() / Metatile::pixelHeight(); + int size = this->project->getMapDataSize(metatilesWide, metatilesTall); int maxSize = this->project->getMaxMapDataSize(); if (size > maxSize) { QSize addition = this->project->getMapSizeAddition(); @@ -151,8 +157,8 @@ void ResizeLayoutPopup::setupLayoutView() { .arg(addition.width()) .arg(addition.height()) .arg(maxSize) - .arg(rect.width() / 16) - .arg(rect.height() / 16) + .arg(metatilesWide) + .arg(metatilesTall) .arg(size), this); // adjust rect to last accepted size @@ -160,8 +166,11 @@ void ResizeLayoutPopup::setupLayoutView() { } this->scene->setValidRect(rect); this->outline->setRect(rect); - this->ui->spinBox_width->setValue(rect.width() / 16); - this->ui->spinBox_height->setValue(rect.height() / 16); + + const QSignalBlocker b_Width(this->ui->spinBox_width); + const QSignalBlocker b_Height(this->ui->spinBox_height); + this->ui->spinBox_width->setValue(metatilesWide); + this->ui->spinBox_height->setValue(metatilesTall); }); scene->addItem(outline); @@ -171,7 +180,7 @@ void ResizeLayoutPopup::setupLayoutView() { this->scale = 1.0; QRectF rect = this->outline->rect(); - const int marginSize = 10 * 16; // Leave a margin of 10 metatiles around the map + const int marginSize = 10 * Metatile::pixelWidth(); // Leave a margin of 10 metatiles around the map rect += QMargins(marginSize, marginSize, marginSize, marginSize); this->ui->graphicsView->fitInView(rect, Qt::KeepAspectRatio); } @@ -179,25 +188,23 @@ void ResizeLayoutPopup::setupLayoutView() { void ResizeLayoutPopup::on_spinBox_width_valueChanged(int value) { if (!this->outline) return; QRectF rect = this->outline->rect(); - this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), value * 16, rect.height())); + this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), value * Metatile::pixelWidth(), rect.height())); } void ResizeLayoutPopup::on_spinBox_height_valueChanged(int value) { if (!this->outline) return; QRectF rect = this->outline->rect(); - this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), rect.width(), value * 16)); + this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), rect.width(), value * Metatile::pixelHeight())); } /// Result is the number of metatiles to add (or subtract) to each side of the map after dimension changes QMargins ResizeLayoutPopup::getResult() { QMargins result = QMargins(); - - result.setLeft(this->layoutPixmap->x() - this->outline->rect().left()); - result.setTop(this->layoutPixmap->y() - this->outline->rect().top()); - result.setRight(this->outline->rect().right() - (this->layoutPixmap->x() + this->layoutPixmap->pixmap().width())); - result.setBottom(this->outline->rect().bottom() - (this->layoutPixmap->y() + this->layoutPixmap->pixmap().height())); - - return result / 16; + result.setLeft((this->layoutPixmap->x() - this->outline->rect().left()) / Metatile::pixelWidth()); + result.setTop((this->layoutPixmap->y() - this->outline->rect().top()) / Metatile::pixelHeight()); + result.setRight((this->outline->rect().right() - (this->layoutPixmap->x() + this->layoutPixmap->pixmap().width())) / Metatile::pixelWidth()); + result.setBottom((this->outline->rect().bottom() - (this->layoutPixmap->y() + this->layoutPixmap->pixmap().height())) / Metatile::pixelHeight()); + return result; } QSize ResizeLayoutPopup::getBorderResult() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 55545616..b25130bd 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -388,19 +388,21 @@ void TilesetEditor::drawSelectedTiles() { return; } + const int imgTileWidth = 16; + const int imgTileHeight = 16; this->selectedTileScene->clear(); QList tiles = this->tileSelector->getSelectedTiles(); QPoint dimensions = this->tileSelector->getSelectionDimensions(); - QImage selectionImage(16 * dimensions.x(), 16 * dimensions.y(), QImage::Format_RGBA8888); + QImage selectionImage(imgTileWidth * dimensions.x(), imgTileHeight * dimensions.y(), QImage::Format_RGBA8888); QPainter painter(&selectionImage); int tileIndex = 0; for (int j = 0; j < dimensions.y(); j++) { for (int i = 0; i < dimensions.x(); i++) { auto tile = tiles.at(tileIndex); - QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(16, 16); + QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(imgTileWidth, imgTileHeight); tile.flip(&tileImage); tileIndex++; - painter.drawImage(i * 16, j * 16, tileImage); + painter.drawImage(i * imgTileWidth, j * imgTileHeight, tileImage); } } @@ -730,18 +732,20 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset) { } else { logError(QString("Failed to open image file: '%1'").arg(filepath)); } - if (image.width() == 0 || image.height() == 0 || image.width() % 8 != 0 || image.height() % 8 != 0) { + if (image.width() == 0 || image.height() == 0 || image.width() % Tile::pixelWidth() != 0 || image.height() % Tile::pixelHeight() != 0) { ErrorMessage::show(QStringLiteral("Failed to import tiles."), - QString("The image dimensions (%1 x %2) are invalid. Width and height must be multiples of 8 pixels.") + QString("The image dimensions (%1x%2) are invalid. The dimensions must be a multiple of %3x%4 pixels.") .arg(image.width()) - .arg(image.height()), + .arg(image.height()) + .arg(Tile::pixelWidth()) + .arg(Tile::pixelHeight()), this); return; } // Validate total number of tiles in image. - int numTilesWide = image.width() / 8; - int numTilesHigh = image.height() / 8; + 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(); if (totalTiles > maxAllowedTiles) { diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 034a86a7..56c65073 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -244,19 +244,16 @@ QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() { } QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) { - const int tileWidth = 8; - const int tileHeight = 8; int height = qCeil(numTiles / static_cast(this->numTilesWide)); - QImage image(this->numTilesWide * tileWidth, height * tileHeight, QImage::Format_RGBA8888); + QImage image(this->numTilesWide * Tile::pixelWidth(), height * Tile::pixelHeight(), QImage::Format_RGBA8888); image.fill(0); QPainter painter(&image); for (int i = 0; i < numTiles; i++) { QImage tileImage = getGreyscaleTileImage(tileIdStart + i, this->primaryTileset, this->secondaryTileset); - int y = i / this->numTilesWide; - int x = i % this->numTilesWide; - QPoint origin = QPoint(x * tileWidth, y * tileHeight); - painter.drawImage(origin, tileImage); + int x = (i % this->numTilesWide) * Tile::pixelWidth(); + int y = (i / this->numTilesWide) * Tile::pixelHeight(); + painter.drawImage(x, y, tileImage); } painter.end(); From 25e8bdee4932b4d1668f7cd49a58fc973ff0e1a3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Jul 2025 13:02:03 -0400 Subject: [PATCH 26/71] Fix resize layout grid changing with viewport --- include/ui/resizelayoutpopup.h | 20 +++++++++++++++----- src/ui/resizelayoutpopup.cpp | 29 ++++++++++++++--------------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h index 4f8482a7..917cda44 100644 --- a/include/ui/resizelayoutpopup.h +++ b/include/ui/resizelayoutpopup.h @@ -24,11 +24,21 @@ class CheckeredBgScene : public QGraphicsScene { Q_OBJECT public: - CheckeredBgScene(QObject *parent = nullptr); + CheckeredBgScene(const QSize &gridSize, QObject *parent = nullptr) + : QGraphicsScene(parent), + gridSize(gridSize) + {}; + CheckeredBgScene(int width, int height, QObject *parent = nullptr) + : CheckeredBgScene(QSize(width, height), parent) + {}; + void setValidRect(int x, int y, int width, int height) { - this->validRect = QRect(x * this->gridSize, y * this->gridSize, width * this->gridSize, height * this->gridSize); + this->validRect = QRect(x * this->gridSize.width(), + y * this->gridSize.height(), + width * this->gridSize.width(), + height * this->gridSize.height()); } - void setValidRect(QRect rect) { + void setValidRect(const QRect &rect) { this->validRect = rect; } QRect getValidRect() { return this->validRect; } @@ -37,8 +47,8 @@ protected: void drawBackground(QPainter *painter, const QRectF &rect) override; private: - int gridSize = 16; // virtual pixels - QRect validRect = QRect(); + QSize gridSize; + QRect validRect; }; diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 738d4861..48d9cebd 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -7,32 +7,31 @@ #include "ui_resizelayoutpopup.h" -CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { } - void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { QRect r = rect.toRect(); - int xMin = r.left() - r.left() % this->gridSize - this->gridSize; - int yMin = r.top() - r.top() % this->gridSize - this->gridSize; - int xMax = r.right() - r.right() % this->gridSize + this->gridSize; - int yMax = r.bottom() - r.bottom() % this->gridSize + this->gridSize; + int w = this->gridSize.width(); + int h = this->gridSize.height(); + int xMin = r.left() - (r.left() % w) - w; + int yMin = r.top() - (r.top() % h) - h; + int xMax = r.right() - (r.right() % w) + w; + int yMax = r.bottom() - (r.bottom() % h) + h; - // draw grid 16x16 from top to bottom of scene - QColor paintColor(0x00ff00); - for (int x = xMin, xTile = 0; x <= xMax; x += this->gridSize, xTile++) { - for (int y = yMin, yTile = 0; y <= yMax; y += this->gridSize, yTile++) { - if (!((xTile ^ yTile) & 1)) { // tile numbers have same parity (evenness) + // draw grid from top to bottom of scene + QColor paintColor; + for (int x = xMin; x <= xMax; x += w) { + for (int y = yMin; y <= yMax; y += h) { + if ((x/w + y/h) % 2) { if (this->validRect.contains(x, y)) paintColor = QColor(132, 217, 165); // green light color else paintColor = 0xbcbcbc; // normal light color - } - else { + } else { if (this->validRect.contains(x, y)) paintColor = QColor(76, 178, 121); // green dark color else paintColor = 0x969696; // normal dark color } - painter->fillRect(QRect(x, y, this->gridSize, this->gridSize), paintColor); + painter->fillRect(QRect(x, y, w, h), paintColor); } } } @@ -85,7 +84,7 @@ ResizeLayoutPopup::ResizeLayoutPopup(QWidget *parent, Layout *layout, Project *p this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint); this->setWindowModality(Qt::ApplicationModal); - this->scene = new CheckeredBgScene(this); + this->scene = new CheckeredBgScene(Metatile::pixelSize(), this); this->ui->graphicsView->setScene(this->scene); this->ui->graphicsView->setRenderHints(QPainter::Antialiasing); this->ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); From 70aeda9adba90dc920a15369c917b9e9fde353f8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Jul 2025 15:03:37 -0400 Subject: [PATCH 27/71] Fix ranges for metatile sheet images --- include/ui/imageproviders.h | 24 ++++- src/ui/imageproviders.cpp | 109 ++++++++++++++++------- src/ui/metatileimageexporter.cpp | 27 +++--- src/ui/tileseteditormetatileselector.cpp | 2 - 4 files changed, 114 insertions(+), 48 deletions(-) diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index 0cfe8511..4bdfe801 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -10,15 +10,35 @@ class Layout; QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); + QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false); QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false); QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false); QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false); -QImage getMetatileSheetImage(Layout *, int, bool useTruePalettes = false); -QImage getMetatileSheetImage(Tileset *, Tileset *, uint16_t, int, int, const QList &, const QList & = {}, const QSize &size = Metatile::pixelSize(), bool useTruePalettes = false); + +QImage getMetatileSheetImage(Layout *layout, int numMetatilesWIde, bool useTruePalettes = false); +QImage getMetatileSheetImage(Tileset *primaryTileset, + Tileset *secondaryTileset, + uint16_t metatileIdStart, + uint16_t metatileIdEnd, + int numMetatilesWIde, + const QList &layerOrder, + const QList &layerOpacity = {}, + const QSize &metatileSize = Metatile::pixelSize(), + bool useTruePalettes = false); +QImage getMetatileSheetImage(Tileset *primaryTileset, + Tileset *secondaryTileset, + int numMetatilesWide, + const QList &layerOrder, + const QList &layerOpacity = {}, + const QSize &metatileSize = Metatile::pixelSize(), + bool useTruePalettes = false); + + QImage getTileImage(uint16_t, Tileset*, Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset); + void flattenTo4bppImage(QImage * image); static QList greyscalePalette({ diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 0997c1e1..ef966852 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -48,6 +48,12 @@ QImage getMetatileImage( useTruePalettes); } +// The color to use when we want to show some portion of the image request was invalid. +// Normally this is Qt::magenta, but we'll use Qt::transparent if we think the image allows it. +QColor getInvalidImageColor() { + return (projectConfig.transparencyColor == QColor(Qt::transparent)) ? QColor(Qt::transparent) : QColor(Qt::magenta); +} + QImage getMetatileImage( Metatile *metatile, Tileset *primaryTileset, @@ -58,7 +64,7 @@ QImage getMetatileImage( { QImage metatile_image(Metatile::pixelWidth(), Metatile::pixelHeight(), QImage::Format_RGBA8888); if (!metatile) { - metatile_image.fill(projectConfig.transparencyColor == QColor(Qt::transparent) ? projectConfig.transparencyColor : QColor(Qt::magenta)); + metatile_image.fill(getInvalidImageColor()); return metatile_image; } @@ -192,56 +198,93 @@ void flattenTo4bppImage(QImage * image) { *pixel %= 16; } -QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) { - return getMetatileSheetImage(layout->tileset_primary, - layout->tileset_secondary, - 0, - -1, - numMetatilesWide, - layout->metatileLayerOrder(), - layout->metatileLayerOpacity(), - Metatile::pixelSize(), - useTruePalettes); -} - +// Constructs a grid image of the metatiles in the specified ID range. QImage getMetatileSheetImage(Tileset *primaryTileset, Tileset *secondaryTileset, uint16_t metatileIdStart, - int numMetatilesToDraw, + uint16_t metatileIdEnd, int numMetatilesWide, const QList &layerOrder, const QList &layerOpacity, const QSize &metatileSize, bool useTruePalettes) { - // We round up the number of primary metatiles to keep the tilesets on separate rows. - int numPrimary = Util::roundUpToMultiple(primaryTileset ? primaryTileset->numMetatiles() : 0, numMetatilesWide); - int maxPrimary = Project::getNumMetatilesPrimary(); - bool includesPrimary = metatileIdStart < maxPrimary; + if (metatileIdEnd < metatileIdStart || numMetatilesWide == 0) + return QImage(); - // Negative values are used to indicate 'draw all metatiles' - if (numMetatilesToDraw < 0) { - numMetatilesToDraw = numPrimary + (secondaryTileset ? secondaryTileset->numMetatiles() : 0) - metatileIdStart; - } + int numMetatilesToDraw = metatileIdEnd - metatileIdStart + 1; - // Round up height for incomplete last row - int numMetatilesTall = ceil((double)numMetatilesToDraw / numMetatilesWide); + // Round up image height for incomplete last row. + int numMetatilesTall = Util::roundUpToMultiple(numMetatilesToDraw, numMetatilesWide) / numMetatilesWide; QImage image(numMetatilesWide * metatileSize.width(), numMetatilesTall * metatileSize.height(), QImage::Format_RGBA8888); - image.fill(projectConfig.transparencyColor == QColor(Qt::transparent) ? projectConfig.transparencyColor : QColor(Qt::magenta)); + image.fill(getInvalidImageColor()); QPainter painter(&image); for (int i = 0; i < numMetatilesToDraw; i++) { uint16_t metatileId = i + metatileIdStart; - if (includesPrimary && metatileId >= numPrimary) - metatileId += maxPrimary - numPrimary; // Skip over unused region of primary tileset - QImage metatile_image = getMetatileImage(metatileId, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes) - .scaled(metatileSize.width(), metatileSize.height()); - int map_y = i / numMetatilesWide; - int map_x = i % numMetatilesWide; - QPoint metatile_origin = QPoint(map_x * metatileSize.width(), map_y * metatileSize.height()); - painter.drawImage(metatile_origin, metatile_image); + QImage metatileImage = getMetatileImage(metatileId, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes) + .scaled(metatileSize); + + int x = (i % numMetatilesWide) * metatileSize.width(); + int y = (i / numMetatilesWide) * metatileSize.height(); + painter.drawImage(x, y, metatileImage); } painter.end(); return image; } + +// Constructs a grid image of the metatiles in the primary and secondary tileset, +// rounding as necessary to keep the two tilesets on separate rows. +// The unused metatiles (if any) between the primary and secondary tilesets are skipped. +QImage getMetatileSheetImage(Tileset *primaryTileset, + Tileset *secondaryTileset, + int numMetatilesWide, + const QList &layerOrder, + const QList &layerOpacity, + const QSize &metatileSize, + bool useTruePalettes) +{ + QImage primaryImage = getMetatileSheetImage(primaryTileset, + secondaryTileset, + 0, + primaryTileset ? primaryTileset->numMetatiles()-1 : 0, + numMetatilesWide, + layerOrder, + layerOpacity, + metatileSize, + useTruePalettes); + + uint16_t secondaryMetatileIdStart = Project::getNumMetatilesPrimary(); + QImage secondaryImage = getMetatileSheetImage(primaryTileset, + secondaryTileset, + secondaryMetatileIdStart, + secondaryMetatileIdStart + (secondaryTileset ? secondaryTileset->numMetatiles()-1 : 0), + numMetatilesWide, + layerOrder, + layerOpacity, + metatileSize, + useTruePalettes); + + QImage image(qMax(primaryImage.width(), secondaryImage.width()), primaryImage.height() + secondaryImage.height(), QImage::Format_RGBA8888); + image.fill(getInvalidImageColor()); + + QPainter painter(&image); + painter.drawImage(0, 0, primaryImage); + painter.drawImage(0, primaryImage.height(), secondaryImage); + painter.end(); + + return image; +} + +QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) { + if (!layout) + return QImage(); + return getMetatileSheetImage(layout->tileset_primary, + layout->tileset_secondary, + numMetatilesWide, + layout->metatileLayerOrder(), + layout->metatileLayerOpacity(), + Metatile::pixelSize(), + useTruePalettes); +} diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp index d5e94f13..52a55694 100644 --- a/src/ui/metatileimageexporter.cpp +++ b/src/ui/metatileimageexporter.cpp @@ -211,10 +211,6 @@ void MetatileImageExporter::tryUpdatePreview() { void MetatileImageExporter::updatePreview() { copyRenderSettings(); - int numMetatilesWide = ui->spinBox_WidthMetatiles->value(); - int metatileStart = ui->spinBox_MetatileStart->value(); - int numMetatiles = Util::roundUpToMultiple(ui->spinBox_MetatileEnd->value() - metatileStart + 1, numMetatilesWide); - m_layerOrder.clear(); for (int i = 0; i < ui->listWidget_Layers->count(); i++) { auto item = ui->listWidget_Layers->item(i); @@ -224,12 +220,22 @@ void MetatileImageExporter::updatePreview() { } } - QImage previewImage = getMetatileSheetImage(m_primaryTileset, - m_secondaryTileset, - metatileStart, - numMetatiles, - numMetatilesWide, - m_layerOrder); + QImage previewImage; + if (ui->checkBox_PrimaryTileset->isChecked() && ui->checkBox_SecondaryTileset->isChecked()) { + // Special behavior to combine the two tilesets while skipping the unused region between tilesets. + previewImage = getMetatileSheetImage(m_primaryTileset, + m_secondaryTileset, + ui->spinBox_WidthMetatiles->value(), + m_layerOrder); + } else { + previewImage = getMetatileSheetImage(m_primaryTileset, + m_secondaryTileset, + ui->spinBox_MetatileStart->value(), + ui->spinBox_MetatileEnd->value(), + ui->spinBox_WidthMetatiles->value(), + m_layerOrder); + } + m_preview->setPixmap(QPixmap::fromImage(previewImage)); m_scene->setSceneRect(m_scene->itemsBoundingRect()); m_previewUpdateQueued = false; @@ -255,7 +261,6 @@ uint16_t MetatileImageExporter::getExpectedMetatileStart() { return ui->spinBox_MetatileStart->value(); } -// TODO: Combining tilesets is not rendering the correct range of metatiles uint16_t MetatileImageExporter::getExpectedMetatileEnd() { if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary() + (m_secondaryTileset ? (m_secondaryTileset->numMetatiles() - 1) : 0); if (ui->checkBox_PrimaryTileset->isChecked()) return m_primaryTileset ? (m_primaryTileset->numMetatiles() - 1) : 0; diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 60f4b0f5..2d589195 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -57,8 +57,6 @@ void TilesetEditorMetatileSelector::drawSelectedMetatile() { void TilesetEditorMetatileSelector::updateBasePixmap() { this->baseImage = getMetatileSheetImage(this->primaryTileset, this->secondaryTileset, - 0, - this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles(), this->numMetatilesWide, this->layout->metatileLayerOrder(), this->layout->metatileLayerOpacity(), From dc4b1ef93a517104c5cf2c23f9f147adb62ec841 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Jul 2025 16:28:51 -0400 Subject: [PATCH 28/71] Enforce tile/metatile limits on load, fix tile selector performance --- include/core/tile.h | 1 + include/core/tileset.h | 13 ++- include/project.h | 3 + include/ui/tileseteditortileselector.h | 3 +- src/core/tileset.cpp | 134 ++++++++++++++++++------- src/mainwindow.cpp | 4 +- src/project.cpp | 2 +- src/scriptapi/apimap.cpp | 13 ++- src/ui/imageproviders.cpp | 50 +++++---- src/ui/tileseteditor.cpp | 5 +- src/ui/tileseteditortileselector.cpp | 60 +++++------ 11 files changed, 176 insertions(+), 112 deletions(-) diff --git a/include/core/tile.h b/include/core/tile.h index 585ceebf..a13e9fc7 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -29,6 +29,7 @@ public: static constexpr int pixelWidth() { return 8; } static constexpr int pixelHeight() { return 8; } static constexpr QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); } + static constexpr int sizeInBytes() { return sizeof(uint16_t); } }; inline bool operator==(const Tile &a, const Tile &b) { diff --git a/include/core/tileset.h b/include/core/tileset.h index 80fef000..d592a429 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -30,10 +30,8 @@ public: QString metatile_attrs_label; QString metatile_attrs_path; QString tilesImagePath; - QImage tilesImage; QStringList palettePaths; - QList tiles; QHash metatileLabels; QList> palettes; QList> palettePreviews; @@ -77,15 +75,24 @@ public: void setMetatiles(const QList &metatiles); void addMetatile(Metatile* metatile); - QList metatiles() const { return m_metatiles; } + const QList &metatiles() const { return m_metatiles; } Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); } void clearMetatiles(); void resizeMetatiles(int newNumMetatiles); int numMetatiles() const { return m_metatiles.length(); } + int maxMetatiles() const; + + int numTiles() const { return m_tiles.length(); } + int maxTiles() const; + + QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); } private: QList m_metatiles; + + QList m_tiles; + QImage m_tilesImage; bool m_hasUnsavedTilesImage = false; }; diff --git a/include/project.h b/include/project.h index c5ef6338..3dbae1c5 100644 --- a/include/project.h +++ b/include/project.h @@ -255,10 +255,13 @@ public: static QMargins getMetatileViewDistance(); static int getNumTilesPrimary() { return num_tiles_primary; } static int getNumTilesTotal() { return num_tiles_total; } + static int getNumTilesSecondary() { return getNumTilesTotal() - getNumTilesPrimary(); } static int getNumMetatilesPrimary() { return num_metatiles_primary; } static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; } + static int getNumMetatilesSecondary() { return getNumMetatilesTotal() - getNumMetatilesPrimary(); } static int getNumPalettesPrimary(){ return num_pals_primary; } static int getNumPalettesTotal() { return num_pals_total; } + static int getNumPalettesSecondary() { return getNumPalettesTotal() - getNumPalettesPrimary(); } static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index bed655e1..2b1982ba 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -43,6 +43,7 @@ protected: void hoverLeaveEvent(QGraphicsSceneHoverEvent*); private: + QPixmap basePixmap; bool externalSelection; int externalSelectionWidth; int externalSelectionHeight; @@ -63,7 +64,7 @@ private: QList getCurPaletteTable(); QList buildSelectedTiles(int, int, QList); QImage buildImage(int tileIdStart, int numTiles); - + void updateBasePixmap(); void drawUnused(); signals: diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index ca0650fe..729c3334 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -20,15 +20,15 @@ Tileset::Tileset(const Tileset &other) metatile_attrs_label(other.metatile_attrs_label), metatile_attrs_path(other.metatile_attrs_path), tilesImagePath(other.tilesImagePath), - tilesImage(other.tilesImage.copy()), palettePaths(other.palettePaths), metatileLabels(other.metatileLabels), palettes(other.palettes), palettePreviews(other.palettePreviews), + m_tilesImage(other.m_tilesImage.copy()), m_hasUnsavedTilesImage(other.m_hasUnsavedTilesImage) { - for (auto tile : other.tiles) { - tiles.append(tile.copy()); + for (auto tile : other.m_tiles) { + m_tiles.append(tile.copy()); } for (auto *metatile : other.m_metatiles) { @@ -46,15 +46,15 @@ Tileset &Tileset::operator=(const Tileset &other) { metatile_attrs_label = other.metatile_attrs_label; metatile_attrs_path = other.metatile_attrs_path; tilesImagePath = other.tilesImagePath; - tilesImage = other.tilesImage.copy(); + m_tilesImage = other.m_tilesImage.copy(); palettePaths = other.palettePaths; metatileLabels = other.metatileLabels; palettes = other.palettes; palettePreviews = other.palettePreviews; - tiles.clear(); - for (auto tile : other.tiles) { - tiles.append(tile.copy()); + m_tiles.clear(); + for (auto tile : other.m_tiles) { + m_tiles.append(tile.copy()); } clearMetatiles(); @@ -94,6 +94,14 @@ void Tileset::resizeMetatiles(int newNumMetatiles) { } } +int Tileset::maxMetatiles() const { + return this->is_secondary ? Project::getNumMetatilesSecondary() : Project::getNumMetatilesPrimary(); +} + +int Tileset::maxTiles() const { + return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary(); +} + Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { if (tileId < Project::getNumTilesPrimary()) { return primaryTileset; @@ -407,17 +415,25 @@ QHash Tileset::getHeaderMemberMap(bool usingAsm) bool Tileset::loadMetatiles() { clearMetatiles(); - QFile metatiles_file(this->metatiles_path); - if (!metatiles_file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(metatiles_file.errorString())); + QFile file(this->metatiles_path); + if (!file.open(QIODevice::ReadOnly)) { + logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(file.errorString())); return false; } - QByteArray data = metatiles_file.readAll(); + QByteArray data = file.readAll(); int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); - int bytesPerMetatile = 2 * tilesPerMetatile; - int num_metatiles = data.length() / bytesPerMetatile; - for (int i = 0; i < num_metatiles; i++) { + int bytesPerMetatile = Tile::sizeInBytes() * tilesPerMetatile; + int numMetatiles = data.length() / bytesPerMetatile; + if (numMetatiles > maxMetatiles()) { + logWarn(QString("%1 metatile count %2 exceeds limit of %3. Additional metatiles will be ignored.") + .arg(this->name) + .arg(numMetatiles) + .arg(maxMetatiles())); + numMetatiles = maxMetatiles(); + } + + for (int i = 0; i < numMetatiles; i++) { auto metatile = new Metatile; int index = i * bytesPerMetatile; for (int j = 0; j < tilesPerMetatile; j++) { @@ -431,9 +447,9 @@ bool Tileset::loadMetatiles() { } bool Tileset::saveMetatiles() { - QFile metatiles_file(this->metatiles_path); - if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(metatiles_file.errorString())); + QFile file(this->metatiles_path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(file.errorString())); return false; } @@ -441,31 +457,40 @@ bool Tileset::saveMetatiles() { int numTiles = projectConfig.getNumTilesInMetatile(); for (const auto &metatile : m_metatiles) { for (int i = 0; i < numTiles; i++) { - uint16_t tile = metatile->tiles.at(i).rawValue(); + uint16_t tile = metatile->tiles.value(i).rawValue(); data.append(static_cast(tile)); data.append(static_cast(tile >> 8)); } } - metatiles_file.write(data); + file.write(data); return true; } bool Tileset::loadMetatileAttributes() { - QFile attrs_file(this->metatile_attrs_path); - if (!attrs_file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString())); + QFile file(this->metatile_attrs_path); + if (!file.open(QIODevice::ReadOnly)) { + logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(file.errorString())); return false; } - QByteArray data = attrs_file.readAll(); + QByteArray data = file.readAll(); int attrSize = projectConfig.metatileAttributesSize; int numMetatiles = m_metatiles.length(); int numMetatileAttrs = data.length() / attrSize; - if (numMetatiles != numMetatileAttrs) { - logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(numMetatiles).arg(numMetatileAttrs).arg(this->name)); + if (numMetatileAttrs > numMetatiles) { + logWarn(QString("%1 metatile attributes count %2 exceeds metatile count of %3. Additional attributes will be ignored.") + .arg(this->name) + .arg(numMetatileAttrs) + .arg(numMetatiles)); + numMetatileAttrs = numMetatiles; + } else if (numMetatileAttrs < numMetatiles) { + logWarn(QString("%1 metatile attributes count %2 is fewer than the metatile count of %3. Missing attributes will default to 0.") + .arg(this->name) + .arg(numMetatileAttrs) + .arg(numMetatiles)); } - for (int i = 0; i < qMin(numMetatiles, numMetatileAttrs); i++) { + for (int i = 0; i < numMetatileAttrs; i++) { uint32_t attributes = 0; for (int j = 0; j < attrSize; j++) attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); @@ -475,9 +500,9 @@ bool Tileset::loadMetatileAttributes() { } bool Tileset::saveMetatileAttributes() { - QFile attrs_file(this->metatile_attrs_path); - if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString())); + QFile file(this->metatile_attrs_path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(file.errorString())); return false; } @@ -487,21 +512,36 @@ bool Tileset::saveMetatileAttributes() { for (int i = 0; i < projectConfig.metatileAttributesSize; i++) data.append(static_cast(attributes >> (8 * i))); } - attrs_file.write(data); + file.write(data); return true; } bool Tileset::loadTilesImage(QImage *importedImage) { QImage image; + bool imported = false; if (importedImage) { image = *importedImage; - m_hasUnsavedTilesImage = true; + imported = true; } else if (QFile::exists(this->tilesImagePath)) { // No image provided, load from file path. image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); - } else { - // Use default image + } + + if (image.isNull()) { + logWarn(QString("Failed to load tiles image for %1. Using default tiles image.").arg(this->name)); image = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_Indexed8); + image.fill(0); + } + + // Validate image dimensions + if (image.width() % Tile::pixelWidth() || image.height() % Tile::pixelHeight()) { + logError(QString("%1 tiles image has invalid dimensions %2x%3. Dimensions must be a multiple of %4x%5.") + .arg(this->name) + .arg(image.width()) + .arg(image.height()) + .arg(Tile::pixelWidth()) + .arg(Tile::pixelHeight())); + return false; } // Validate image contains 16 colors. @@ -515,15 +555,31 @@ bool Tileset::loadTilesImage(QImage *importedImage) { } image.setColorTable(colorTable); } + m_tilesImage = image; - QList tiles; + // Cut up the full tiles image into individual tile images. + m_tiles.clear(); for (int y = 0; y < image.height(); y += Tile::pixelHeight()) for (int x = 0; x < image.width(); x += Tile::pixelWidth()) { - QImage tile = image.copy(x, y, Tile::pixelWidth(), Tile::pixelHeight()); - tiles.append(tile); + m_tiles.append(image.copy(x, y, Tile::pixelWidth(), Tile::pixelHeight())); } - this->tilesImage = image; - this->tiles = tiles; + + if (m_tiles.length() > maxTiles()) { + logWarn(QString("%1 tile count of %2 exceeds limit of %3. Additional tiles will not be displayed.") + .arg(this->name) + .arg(m_tiles.length()) + .arg(maxTiles())); + + // Just resize m_tiles so that numTiles() reports the correct tile count. + // We'll leave m_tilesImage alone (it doesn't get displayed, and we don't want to delete the user's image data). + m_tiles = m_tiles.mid(0, maxTiles()); + } + + if (imported) { + // Only set this flag once we've successfully loaded the tiles image. + m_hasUnsavedTilesImage = true; + } + return true; } @@ -533,7 +589,7 @@ bool Tileset::saveTilesImage() { if (!m_hasUnsavedTilesImage) return true; - if (!this->tilesImage.save(this->tilesImagePath, "PNG")) { + if (!m_tilesImage.save(this->tilesImagePath, "PNG")) { logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath)); return false; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 36aef9e5..697ce835 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2844,7 +2844,9 @@ void MainWindow::on_actionTileset_Editor_triggered() openSubWindow(this->tilesetEditor); MetatileSelection selection = this->editor->metatile_selector_item->getMetatileSelection(); - this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId); + if (!selection.metatileItems.isEmpty()) { + this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId); + } } void MainWindow::initTilesetEditor() { diff --git a/src/project.cpp b/src/project.cpp index cc3a567f..2957ca2b 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2461,6 +2461,7 @@ bool Project::readFieldmapMasks() { projectConfig.blockCollisionMask = blockMask; if (readBlockMask(elevationMaskName, &blockMask)) projectConfig.blockElevationMask = blockMask; + Block::setLayout(); // Read RSE metatile attribute masks auto it = defines.find(behaviorMaskName); @@ -3469,7 +3470,6 @@ void Project::applyParsedLimits() { projectConfig.metatileEncounterTypeMask &= maxMask; projectConfig.metatileLayerTypeMask &= maxMask; - Block::setLayout(); Metatile::setLayout(this); Project::num_metatiles_primary = qBound(1, Project::num_metatiles_primary, Block::getMaxMetatileId() + 1); diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index c7b6d386..a1730791 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -546,13 +546,13 @@ int MainWindow::getNumSecondaryTilesetMetatiles() { int MainWindow::getNumPrimaryTilesetTiles() { if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; - return this->editor->layout->tileset_primary->tiles.length(); + return this->editor->layout->tileset_primary->numTiles(); } int MainWindow::getNumSecondaryTilesetTiles() { if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; - return this->editor->layout->tileset_secondary->tiles.length(); + return this->editor->layout->tileset_secondary->numTiles(); } QString MainWindow::getPrimaryTileset() { @@ -797,12 +797,15 @@ void MainWindow::setMetatileTile(int metatileId, int tileIndex, QJSValue tileObj QJSValue MainWindow::getTilePixels(int tileId) { if (tileId < 0 || !this->editor || !this->editor->layout) return QJSValue(); + + const int numPixels = Tile::pixelWidth() * Tile::pixelHeight(); QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); - if (tileImage.isNull() || tileImage.sizeInBytes() < 64) + if (tileImage.isNull() || tileImage.sizeInBytes() < numPixels) return QJSValue(); + const uchar * pixels = tileImage.constBits(); - QJSValue pixelArray = Scripting::getEngine()->newArray(64); - for (int i = 0; i < 64; i++) { + QJSValue pixelArray = Scripting::getEngine()->newArray(numPixels); + for (int i = 0; i < numPixels; i++) { pixelArray.setProperty(i, pixels[i]); } return pixelArray; diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index ef966852..188398bb 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -9,7 +9,7 @@ QImage getCollisionMetatileImage(Block block) { } QImage getCollisionMetatileImage(int collision, int elevation) { - const QImage * image = Editor::collisionIcons.at(collision).at(elevation); + const QImage * image = Editor::collisionIcons.value(collision).value(elevation); return image ? *image : QImage(); } @@ -158,11 +158,7 @@ QImage getMetatileImage( QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset); - int index = Tile::getIndexInTileset(tileId); - if (!tileset) { - return QImage(); - } - return tileset->tiles.value(index, QImage()); + return tileset ? tileset->tileImage(tileId) : QImage(); } QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) { @@ -170,10 +166,10 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se if (tileImage.isNull()) { tileImage = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_RGBA8888); QPainter painter(&tileImage); - painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.at(0)); + painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.value(0)); } else { for (int i = 0; i < 16; i++) { - tileImage.setColor(i, palette.at(i)); + tileImage.setColor(i, palette.value(i)); } } @@ -245,26 +241,26 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, const QSize &metatileSize, bool useTruePalettes) { - QImage primaryImage = getMetatileSheetImage(primaryTileset, - secondaryTileset, - 0, - primaryTileset ? primaryTileset->numMetatiles()-1 : 0, - numMetatilesWide, - layerOrder, - layerOpacity, - metatileSize, - useTruePalettes); + auto createSheetImage = [=](uint16_t start, Tileset *tileset) { + uint16_t end = start; + if (tileset) { + if (tileset->numMetatiles() == 0) + return QImage(); + end += tileset->numMetatiles() - 1; + } + return getMetatileSheetImage(primaryTileset, + secondaryTileset, + start, + end, + numMetatilesWide, + layerOrder, + layerOpacity, + metatileSize, + useTruePalettes); + }; - uint16_t secondaryMetatileIdStart = Project::getNumMetatilesPrimary(); - QImage secondaryImage = getMetatileSheetImage(primaryTileset, - secondaryTileset, - secondaryMetatileIdStart, - secondaryMetatileIdStart + (secondaryTileset ? secondaryTileset->numMetatiles()-1 : 0), - numMetatilesWide, - layerOrder, - layerOpacity, - metatileSize, - useTruePalettes); + QImage primaryImage = createSheetImage(0, primaryTileset); + QImage secondaryImage = createSheetImage(Project::getNumMetatilesPrimary(), secondaryTileset); QImage image(qMax(primaryImage.width(), secondaryImage.width()), primaryImage.height() + secondaryImage.height(), QImage::Format_RGBA8888); image.fill(getInvalidImageColor()); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index b25130bd..e64a6add 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -783,7 +783,10 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset) { image = image.convertToFormat(QImage::Format::Format_Indexed8, colorTable); } - tileset->loadTilesImage(&image); + if (!tileset->loadTilesImage(&image)) { + RecentErrorMessage::show(QStringLiteral("Failed to import tiles."), this); + return; + } this->refresh(); this->hasUnsavedChanges = true; } diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 56c65073..31ab088e 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -12,53 +12,40 @@ QPoint TilesetEditorTileSelector::getSelectionDimensions() { } } -void TilesetEditorTileSelector::draw() { - if (!this->primaryTileset || !this->secondaryTileset) { - this->setPixmap(QPixmap()); +void TilesetEditorTileSelector::updateBasePixmap() { + if (!this->primaryTileset || !this->secondaryTileset || this->numTilesWide == 0) { + this->basePixmap = QPixmap(); + return; } int totalTiles = Project::getNumTilesTotal(); - int primaryLength = this->primaryTileset->tiles.length(); - int secondaryLength = this->secondaryTileset->tiles.length(); int height = totalTiles / this->numTilesWide; - QList palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset, true); QImage image(this->numTilesWide * this->cellWidth, height * this->cellHeight, QImage::Format_RGBA8888); QPainter painter(&image); - for (uint16_t tile = 0; tile < totalTiles; tile++) { - QImage tileImage; - if (tile < primaryLength) { - tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight); - } else if (tile < Project::getNumTilesPrimary()) { - tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888); - tileImage.fill(palette.at(0)); - } else if (tile < Project::getNumTilesPrimary() + secondaryLength) { - tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight); - } else { - tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888); - QPainter painter(&tileImage); - painter.fillRect(0, 0, this->cellWidth, this->cellHeight, palette.at(0)); - } - - int y = tile / this->numTilesWide; - int x = tile % this->numTilesWide; - QPoint origin = QPoint(x * this->cellWidth, y * this->cellHeight); - painter.drawImage(origin, tileImage); + for (uint16_t tileId = 0; tileId < totalTiles; tileId++) { + QImage tileImage = getPalettedTileImage(tileId, this->primaryTileset, this->secondaryTileset, this->paletteId, true) + .scaled(this->cellWidth, this->cellHeight); + int x = (tileId % this->numTilesWide) * this->cellWidth; + int y = (tileId / this->numTilesWide) * this->cellHeight; + painter.drawImage(x, y, tileImage); } if (this->showDivider) { - int row = this->primaryTileset->tiles.length() / this->numTilesWide; - if (this->primaryTileset->tiles.length() % this->numTilesWide != 0) { - // Round up height for incomplete last row - row++; - } + 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->setPixmap(QPixmap::fromImage(image)); + this->basePixmap = QPixmap::fromImage(image); +} + +void TilesetEditorTileSelector::draw() { + if (this->basePixmap.isNull()) + updateBasePixmap(); + setPixmap(this->basePixmap); if (!this->externalSelection || (this->externalSelectionWidth == 1 && this->externalSelectionHeight == 1)) { this->drawSelection(); @@ -82,12 +69,14 @@ void TilesetEditorTileSelector::highlight(uint16_t tile) { void TilesetEditorTileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; + this->updateBasePixmap(); this->draw(); } void TilesetEditorTileSelector::setPaletteId(int paletteId) { this->paletteId = paletteId; this->paletteChanged = true; + this->updateBasePixmap(); this->draw(); } @@ -213,7 +202,7 @@ void TilesetEditorTileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { } QPoint TilesetEditorTileSelector::getTileCoords(uint16_t tile) { - if (tile >= Project::getNumTilesTotal()) + if (tile >= Project::getNumTilesTotal() || this->numTilesWide == 0) { // Invalid tile. return QPoint(0, 0); @@ -233,17 +222,20 @@ QImage TilesetEditorTileSelector::buildPrimaryTilesIndexedImage() { if (!this->primaryTileset) return QImage(); - return buildImage(0, this->primaryTileset->tiles.length()); + return buildImage(0, this->primaryTileset->numTiles()); } QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() { if (!this->secondaryTileset) return QImage(); - return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->tiles.length()); + return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->numTiles()); } QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) { + if (this->numTilesWide == 0) + return QImage(); + int height = qCeil(numTiles / static_cast(this->numTilesWide)); QImage image(this->numTilesWide * Tile::pixelWidth(), height * Tile::pixelHeight(), QImage::Format_RGBA8888); image.fill(0); From c5117e458b73081af57da20799812f1f5a62056a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 19 Jul 2025 16:25:31 -0400 Subject: [PATCH 29/71] Use checkered bg for image export previews --- include/ui/checkeredbgscene.h | 39 ++++++++++++++++++++++++++++++ include/ui/mapimageexporter.h | 3 ++- include/ui/metatileimageexporter.h | 4 +-- include/ui/resizelayoutpopup.h | 39 +----------------------------- porymap.pro | 2 ++ src/ui/checkeredbgscene.cpp | 32 ++++++++++++++++++++++++ src/ui/mapimageexporter.cpp | 2 +- src/ui/metatileimageexporter.cpp | 2 +- src/ui/resizelayoutpopup.cpp | 33 ------------------------- 9 files changed, 80 insertions(+), 76 deletions(-) create mode 100644 include/ui/checkeredbgscene.h create mode 100644 src/ui/checkeredbgscene.cpp diff --git a/include/ui/checkeredbgscene.h b/include/ui/checkeredbgscene.h new file mode 100644 index 00000000..bf0452a7 --- /dev/null +++ b/include/ui/checkeredbgscene.h @@ -0,0 +1,39 @@ +#ifndef CHECKEREDBGSCENE_H +#define CHECKEREDBGSCENE_H + +#include + +// Custom scene that paints its background a gray checkered pattern. +// Additionally there is a definable "valid" area which will paint the checkerboard green inside. +class CheckeredBgScene : public QGraphicsScene { + Q_OBJECT + +public: + CheckeredBgScene(const QSize &gridSize, QObject *parent = nullptr) + : QGraphicsScene(parent), + gridSize(gridSize) + {}; + CheckeredBgScene(int width, int height, QObject *parent = nullptr) + : CheckeredBgScene(QSize(width, height), parent) + {}; + + void setValidRect(int x, int y, int width, int height) { + this->validRect = QRect(x * this->gridSize.width(), + y * this->gridSize.height(), + width * this->gridSize.width(), + height * this->gridSize.height()); + } + void setValidRect(const QRect &rect) { + this->validRect = rect; + } + QRect getValidRect() { return this->validRect; } + +protected: + void drawBackground(QPainter *painter, const QRectF &rect) override; + +private: + QSize gridSize; + QRect validRect; +}; + +#endif // CHECKEREDBGSCENE_H diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 51c1afe3..789cf9b9 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -2,6 +2,7 @@ #define MAPIMAGEEXPORTER_H #include "project.h" +#include "checkeredbgscene.h" class QGifImage; @@ -52,7 +53,7 @@ private: Project *m_project = nullptr; Map *m_map = nullptr; Layout *m_layout = nullptr; - QGraphicsScene *m_scene = nullptr; + CheckeredBgScene *m_scene = nullptr; QGifImage *m_timelapseGifImage = nullptr; QBuffer *m_timelapseBuffer = nullptr; QMovie *m_timelapseMovie = nullptr; diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h index a6b8f6cd..9e9b0bf2 100644 --- a/include/ui/metatileimageexporter.h +++ b/include/ui/metatileimageexporter.h @@ -2,7 +2,6 @@ #define METATILEIMAGEEXPORTER_H #include -#include #include #include #include @@ -10,6 +9,7 @@ #include #include "config.h" +#include "checkeredbgscene.h" class Tileset; @@ -73,7 +73,7 @@ private: Tileset *m_secondaryTileset; Settings *m_savedSettings; - QGraphicsScene *m_scene = nullptr; + CheckeredBgScene *m_scene = nullptr; QGraphicsPixmapItem *m_preview = nullptr; bool m_previewUpdateQueued = false; QList m_layerOrder; diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h index 917cda44..b187044f 100644 --- a/include/ui/resizelayoutpopup.h +++ b/include/ui/resizelayoutpopup.h @@ -3,10 +3,10 @@ #include "maplayout.h" #include "project.h" +#include "checkeredbgscene.h" #include #include -#include #include #include #include @@ -16,43 +16,6 @@ namespace Ui { class ResizeLayoutPopup; } - - -/// Custom scene that paints its background a gray checkered pattern. -/// Additionally there is a definable "valid" area which will paint the checkerboard green inside. -class CheckeredBgScene : public QGraphicsScene { - Q_OBJECT - -public: - CheckeredBgScene(const QSize &gridSize, QObject *parent = nullptr) - : QGraphicsScene(parent), - gridSize(gridSize) - {}; - CheckeredBgScene(int width, int height, QObject *parent = nullptr) - : CheckeredBgScene(QSize(width, height), parent) - {}; - - void setValidRect(int x, int y, int width, int height) { - this->validRect = QRect(x * this->gridSize.width(), - y * this->gridSize.height(), - width * this->gridSize.width(), - height * this->gridSize.height()); - } - void setValidRect(const QRect &rect) { - this->validRect = rect; - } - QRect getValidRect() { return this->validRect; } - -protected: - void drawBackground(QPainter *painter, const QRectF &rect) override; - -private: - QSize gridSize; - QRect validRect; -}; - - - /// PixmapItem subclass which allows for creating a boundary which determine whether /// the pixmap paints normally or with a black tint. /// This item is movable and snaps on a 'cellSize' grid. diff --git a/porymap.pro b/porymap.pro index 982fbd37..9c6327b7 100644 --- a/porymap.pro +++ b/porymap.pro @@ -66,6 +66,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/scriptapi/apiutility.cpp \ src/scriptapi/scripting.cpp \ src/ui/aboutporymap.cpp \ + src/ui/checkeredbgscene.cpp \ src/ui/colorinputwidget.cpp \ src/ui/connectionslistitem.cpp \ src/ui/customattributesdialog.cpp \ @@ -181,6 +182,7 @@ HEADERS += include/core/advancemapparser.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ + include/ui/checkeredbgscene.h \ include/ui/connectionslistitem.h \ include/ui/customattributesdialog.h \ include/ui/customattributestable.h \ diff --git a/src/ui/checkeredbgscene.cpp b/src/ui/checkeredbgscene.cpp new file mode 100644 index 00000000..9a32dff7 --- /dev/null +++ b/src/ui/checkeredbgscene.cpp @@ -0,0 +1,32 @@ +#include "checkeredbgscene.h" + +#include + +void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { + QRect r = rect.toRect(); + int w = this->gridSize.width(); + int h = this->gridSize.height(); + int xMin = r.left() - (r.left() % w) - w; + int yMin = r.top() - (r.top() % h) - h; + int xMax = r.right() - (r.right() % w) + w; + int yMax = r.bottom() - (r.bottom() % h) + h; + + // draw grid from top to bottom of scene + QColor paintColor; + for (int x = xMin; x <= xMax; x += w) { + for (int y = yMin; y <= yMax; y += h) { + if ((x/w + y/h) % 2) { + if (this->validRect.contains(x, y)) + paintColor = QColor(132, 217, 165); // green light color + else + paintColor = 0xbcbcbc; // normal light color + } else { + if (this->validRect.contains(x, y)) + paintColor = QColor(76, 178, 121); // green dark color + else + paintColor = 0x969696; // normal dark color + } + painter->fillRect(QRect(x, y, w, h), paintColor); + } + } +} diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 450ad9d0..30c6ff72 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -46,7 +46,7 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); - m_scene = new QGraphicsScene(this); + m_scene = new CheckeredBgScene(QSize(8,8), this); m_preview = m_scene->addPixmap(QPixmap()); ui->graphicsView_Preview->setScene(m_scene); diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp index 52a55694..5ffbf8d7 100644 --- a/src/ui/metatileimageexporter.cpp +++ b/src/ui/metatileimageexporter.cpp @@ -23,7 +23,7 @@ MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTi ui->radioButton_TransparencyFirst, }; - m_scene = new QGraphicsScene(this); + m_scene = new CheckeredBgScene(QSize(8,8), this); m_preview = m_scene->addPixmap(QPixmap()); ui->graphicsView_Preview->setScene(m_scene); diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 48d9cebd..679e9ee2 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -7,39 +7,6 @@ #include "ui_resizelayoutpopup.h" -void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { - QRect r = rect.toRect(); - int w = this->gridSize.width(); - int h = this->gridSize.height(); - int xMin = r.left() - (r.left() % w) - w; - int yMin = r.top() - (r.top() % h) - h; - int xMax = r.right() - (r.right() % w) + w; - int yMax = r.bottom() - (r.bottom() % h) + h; - - // draw grid from top to bottom of scene - QColor paintColor; - for (int x = xMin; x <= xMax; x += w) { - for (int y = yMin; y <= yMax; y += h) { - if ((x/w + y/h) % 2) { - if (this->validRect.contains(x, y)) - paintColor = QColor(132, 217, 165); // green light color - else - paintColor = 0xbcbcbc; // normal light color - } else { - if (this->validRect.contains(x, y)) - paintColor = QColor(76, 178, 121); // green dark color - else - paintColor = 0x969696; // normal dark color - } - painter->fillRect(QRect(x, y, w, h), paintColor); - } - } -} - -/****************************************************************************** - ************************************************************************ - ******************************************************************************/ - BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, const QSize &cellSize, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) { setFlags(this->flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable); this->cellSize = cellSize; From 15b300f864b971a5cdc7bb18ab2c814f7264582e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 19 Jul 2025 23:10:49 -0400 Subject: [PATCH 30/71] Clean up tile/metatile image changes --- include/core/maplayout.h | 1 - include/core/tileset.h | 6 +- include/scripting.h | 4 +- include/ui/imageproviders.h | 3 +- include/ui/metatileimageexporter.h | 2 - include/ui/metatilelayersitem.h | 2 +- include/ui/metatileselector.h | 15 +-- include/ui/selectablepixmapitem.h | 5 +- include/ui/tileseteditor.h | 1 + include/ui/tileseteditormetatileselector.h | 13 ++- include/ui/tileseteditortileselector.h | 14 +-- resources/text/script_template.txt | 4 +- src/config.cpp | 5 +- src/core/advancemapparser.cpp | 2 +- src/core/mapconnection.cpp | 8 +- src/core/maplayout.cpp | 16 +-- src/core/tileset.cpp | 36 +++--- src/editor.cpp | 6 +- src/mainwindow.cpp | 10 +- src/project.cpp | 3 +- src/scriptapi/scripting.cpp | 24 ++-- src/ui/bordermetatilespixmapitem.cpp | 5 +- src/ui/imageproviders.cpp | 66 +++++------ src/ui/metatileimageexporter.cpp | 25 ++-- src/ui/metatilelayersitem.cpp | 2 +- src/ui/metatileselector.cpp | 113 +++++++++++------- src/ui/projectsettingseditor.cpp | 9 +- src/ui/selectablepixmapitem.cpp | 2 +- src/ui/tileseteditor.cpp | 27 +++-- src/ui/tileseteditormetatileselector.cpp | 126 +++++++++++++-------- src/ui/tileseteditortileselector.cpp | 2 +- 31 files changed, 315 insertions(+), 242 deletions(-) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 2dc3cc24..9f073d20 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -158,7 +158,6 @@ public: QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, const QRect &bounds = QRect(0, 0, -1, -1)); QPixmap renderCollision(bool ignoreCache); - // QPixmap renderConnection(MapConnection, Layout *); QPixmap renderBorder(bool ignoreCache = false); QPixmap getLayoutItemPixmap(); diff --git a/include/core/tileset.h b/include/core/tileset.h index d592a429..be5bdace 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -76,13 +76,17 @@ public: void addMetatile(Metatile* metatile); const QList &metatiles() const { return m_metatiles; } - Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); } + const Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); } void clearMetatiles(); void resizeMetatiles(int newNumMetatiles); int numMetatiles() const { return m_metatiles.length(); } int maxMetatiles() const; + uint16_t firstMetatileId() const; + uint16_t lastMetatileId() const; + bool contains(uint16_t metatileId) const { return metatileId >= firstMetatileId() && metatileId <= lastMetatileId(); } + int numTiles() const { return m_tiles.length(); } int maxTiles() const; diff --git a/include/scripting.h b/include/scripting.h index aaa2bd02..b2c43ed2 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -23,7 +23,7 @@ enum CallbackType { OnMapResized, OnBorderResized, OnMapShifted, - OnTilesetsChanged, + OnTilesetUpdated, OnMainTabChanged, OnMapViewTabChanged, OnBorderVisibilityToggled, @@ -51,7 +51,7 @@ public: static void cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta); static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_MapShifted(int xDelta, int yDelta); - static void cb_TilesetsChanged(const QString &primaryTilesetName, const QString &secondaryTilesetName); + static void cb_TilesetUpdated(const QString &tilesetName); static void cb_MainTabChanged(int oldTab, int newTab); static void cb_MapViewTabChanged(int oldTab, int newTab); static void cb_BorderVisibilityToggled(bool visible); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index 4bdfe801..17b823ce 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -37,7 +37,8 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, QImage getTileImage(uint16_t, Tileset*, Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); -QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset); +QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette); +QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset); void flattenTo4bppImage(QImage * image); diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h index 9e9b0bf2..d155ad3f 100644 --- a/include/ui/metatileimageexporter.h +++ b/include/ui/metatileimageexporter.h @@ -89,8 +89,6 @@ private: void syncMetatileWidth(); void validateMetatileStart(); void validateMetatileEnd(); - uint16_t getExpectedMetatileStart(); - uint16_t getExpectedMetatileEnd(); void updateMetatileRange(); void copyRenderSettings(); void restoreRenderSettings(); diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h index 1ca27c1c..ceea3ff8 100644 --- a/include/ui/metatilelayersitem.h +++ b/include/ui/metatilelayersitem.h @@ -26,7 +26,7 @@ private: signals: void tileChanged(int, int); void selectedTilesChanged(QPoint, int, int); - void hoveredTileChanged(uint16_t); + void hoveredTileChanged(const Tile &tile); void hoveredTileCleared(); protected: void mousePressEvent(QGraphicsSceneMouseEvent*); diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index d7c6a40b..8bc2ef35 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -31,7 +31,7 @@ struct MetatileSelection class MetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(Metatile::pixelWidth(), Metatile::pixelHeight()) { + MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(Metatile::pixelSize()) { this->externalSelection = false; this->prefabSelection = false; this->numMetatilesWide = numMetatilesWide; @@ -41,16 +41,16 @@ public: setAcceptHoverEvents(true); } - QPoint getSelectionDimensions() override; + QPoint getSelectionDimensions() const override; void draw() override; void refresh(); bool select(uint16_t metatile); void selectFromMap(uint16_t metatileId, uint16_t collision, uint16_t elevation); - MetatileSelection getMetatileSelection(); + MetatileSelection getMetatileSelection() const { return this->selection; } void setPrefabSelection(MetatileSelection selection); - void setExternalSelection(int, int, QList, QList>); - QPoint getMetatileIdCoordsOnWidget(uint16_t); + void setExternalSelection(int, int, const QList&, const QList>&); + QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId) const; void setLayout(Layout *layout); bool isInternalSelection() const { return (!this->externalSelection && !this->prefabSelection); } @@ -79,8 +79,9 @@ private: void updateBasePixmap(); void updateSelectedMetatiles(); void updateExternalSelectedMetatiles(); - uint16_t getMetatileId(int x, int y) const; - QPoint getMetatileIdCoords(uint16_t); + 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 positionIsValid(const QPoint &pos) const; bool selectionIsValid(); void hoverChanged(); diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h index d497e00d..8323f7e1 100644 --- a/include/ui/selectablepixmapitem.h +++ b/include/ui/selectablepixmapitem.h @@ -19,7 +19,7 @@ public: selectionOffsetX(0), selectionOffsetY(0) {} - virtual QPoint getSelectionDimensions(); + virtual QPoint getSelectionDimensions() const; virtual void draw() = 0; protected: @@ -33,7 +33,8 @@ protected: int selectionOffsetY; QPoint getSelectionStart(); - void select(int, int, int, int); + void select(int x, int y, int width = 0, int height = 0); + void select(const QPoint &pos, const QSize &size = QSize(0,0)) { select(pos.x(), pos.y(), size.width(), size.height()); } void updateSelection(int, int); QPoint getCellPos(QPointF); virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index b89709df..f9ccfee4 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -62,6 +62,7 @@ private slots: void onWindowActivated(); void onHoveredMetatileChanged(uint16_t); void onHoveredMetatileCleared(); + void onHoveredTileChanged(const Tile&); void onHoveredTileChanged(uint16_t); void onHoveredTileCleared(); void onSelectedTilesChanged(); diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 77a5c4e4..47a70e4b 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -18,9 +18,9 @@ public: bool select(uint16_t metatileId); void setTilesets(Tileset*, Tileset*); - uint16_t getSelectedMetatileId(); + uint16_t getSelectedMetatileId() const { return this->selectedMetatileId; } void updateSelectedMetatile(); - QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId); + QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId) const; QVector usedMetatiles; bool selectorShowUnused = false; @@ -44,11 +44,12 @@ private: int numMetatilesWide; int numMetatilesHigh; void updateBasePixmap(); - uint16_t getMetatileId(int x, int y); - QPoint getMetatileIdCoords(uint16_t); + 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*); - int numRows(int numMetatiles); - int numRows(); + int numRows(int numMetatiles) const; + int numRows() const; void drawGrid(); void drawDivider(); void drawFilters(); diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index 2b1982ba..0ad22b97 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -18,8 +18,8 @@ public: this->paletteChanged = false; setAcceptHoverEvents(true); } - QPoint getSelectionDimensions(); - void draw(); + QPoint getSelectionDimensions() const override; + void draw() override; void select(uint16_t metatileId); void highlight(uint16_t metatileId); void setTilesets(Tileset*, Tileset*); @@ -36,11 +36,11 @@ public: bool showDivider = false; protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; private: QPixmap basePixmap; diff --git a/resources/text/script_template.txt b/resources/text/script_template.txt index 8135a853..bee6e56e 100644 --- a/resources/text/script_template.txt +++ b/resources/text/script_template.txt @@ -53,8 +53,8 @@ export function onMapShifted(xDelta, yDelta) { } -// Called when a currently loaded tileset is changed by switching to a new one or by saving changes in the Tileset Editor. -export function onTilesetsChanged(primaryTilesetName, secondaryTilesetName) { +// Called when the currently loaded tileset is changed by switching to a new one or by saving changes to it in the Tileset Editor. +export function onTilesetUpdated(tilesetName) { } diff --git a/src/config.cpp b/src/config.cpp index f3457e9e..6347e7e4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -298,6 +298,9 @@ uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString & } QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &value, const QColor &defaultValue) { + if (value.isEmpty()) + return QColor(); + QColor color = QColor("#" + value); if (!color.isValid()) { logWarn(QString("Invalid config value for %1: '%2'. Must be a color in the format 'RRGGBB'. Using default value '%3'.").arg(key).arg(value).arg(defaultValue.name())); @@ -307,7 +310,7 @@ QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &val } QString KeyValueConfigBase::toConfigColor(const QColor &color) { - return color.name().remove("#"); // Our text config treats '#' as the start of a comment. + return color.isValid() ? color.name().remove("#") : QString(); // Our text config treats '#' as the start of a comment. } PorymapConfig porymapConfig; diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp index 6a8428ec..2e4ef7cd 100644 --- a/src/core/advancemapparser.cpp +++ b/src/core/advancemapparser.cpp @@ -131,7 +131,7 @@ QList AdvanceMapParser::parseMetatiles(const QString &filepath, bool } int attrSize = Metatile::getDefaultAttributesSize(version); - int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); + int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesSecondary(); int numMetatiles = static_cast(in.at(0)) | (static_cast(in.at(1)) << 8) | (static_cast(in.at(2)) << 16) | diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index 3e5d82dd..e1478fad 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -76,15 +76,15 @@ QPoint MapConnection::relativePixelPos(bool clipped) const { int x = 0, y = 0; if (m_direction == "right") { if (m_parentMap) x = m_parentMap->pixelWidth(); - y = m_offset; + y = m_offset * Metatile::pixelHeight(); } else if (m_direction == "down") { - x = m_offset; + x = m_offset * Metatile::pixelWidth(); if (m_parentMap) y = m_parentMap->pixelHeight(); } else if (m_direction == "left") { if (targetMap()) x = !clipped ? -targetMap()->pixelWidth() : -targetMap()->getConnectionRect(m_direction).width(); - y = m_offset; + y = m_offset * Metatile::pixelHeight(); } else if (m_direction == "up") { - x = m_offset; + x = m_offset * Metatile::pixelWidth(); if (targetMap()) y = !clipped ? -targetMap()->pixelHeight() : -targetMap()->getConnectionRect(m_direction).height(); } return QPoint(x, y); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index a8d3934d..a965ddb8 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -530,6 +530,7 @@ bool Layout::loadBorder(const QString &root) { logError(QString("Failed to load border for %1 from '%2': %3").arg(this->name).arg(path).arg(error)); return false; } + this->border = blockdata; // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG if (this->border_width <= 0) { @@ -539,10 +540,6 @@ bool Layout::loadBorder(const QString &root) { this->border_height = DEFAULT_BORDER_HEIGHT; } - this->border = blockdata; - this->lastCommitBlocks.border = blockdata; - this->lastCommitBlocks.borderDimensions = QSize(this->border_width, this->border_height); - int expectedSize = this->border_width * this->border_height; if (this->border.count() != expectedSize) { logWarn(QString("%1 border blockdata length %2 does not match dimensions %3x%4 (should be %5). Resizing border blockdata.") @@ -553,6 +550,10 @@ bool Layout::loadBorder(const QString &root) { .arg(expectedSize)); this->border.resize(expectedSize); } + + this->lastCommitBlocks.border = this->border; + this->lastCommitBlocks.borderDimensions = QSize(this->border_width, this->border_height); + return true; } @@ -569,10 +570,7 @@ bool Layout::loadBlockdata(const QString &root) { logError(QString("Failed to load blockdata for %1 from '%2': %3").arg(this->name).arg(path).arg(error)); return false; } - this->blockdata = blockdata; - this->lastCommitBlocks.blocks = blockdata; - this->lastCommitBlocks.layoutDimensions = QSize(this->width, this->height); int expectedSize = this->width * this->height; if (expectedSize <= 0) { @@ -588,6 +586,10 @@ bool Layout::loadBlockdata(const QString &root) { .arg(expectedSize)); this->blockdata.resize(expectedSize); } + + this->lastCommitBlocks.blocks = this->blockdata; + this->lastCommitBlocks.layoutDimensions = QSize(this->width, this->height); + return true; } diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 729c3334..84a34548 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -94,6 +94,14 @@ void Tileset::resizeMetatiles(int newNumMetatiles) { } } +uint16_t Tileset::firstMetatileId() const { + return this->is_secondary ? Project::getNumMetatilesPrimary() : 0; +} + +uint16_t Tileset::lastMetatileId() const { + return firstMetatileId() + qMax(m_metatiles.length(), 1) - 1; +} + int Tileset::maxMetatiles() const { return this->is_secondary ? Project::getNumMetatilesSecondary() : Project::getNumMetatilesPrimary(); } @@ -213,16 +221,8 @@ QString Tileset::getMetatileLabelPrefix(const QString &name) } bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { - if (metatileId >= Project::getNumMetatilesTotal()) - return false; - - if (metatileId < Project::getNumMetatilesPrimary() && metatileId >= primaryTileset->numMetatiles()) - return false; - - if (metatileId >= Project::getNumMetatilesPrimary() + secondaryTileset->numMetatiles()) - return false; - - return true; + return (primaryTileset && primaryTileset->contains(metatileId)) + || (secondaryTileset && secondaryTileset->contains(metatileId)); } QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) { @@ -252,10 +252,12 @@ QList Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset Tileset *tileset = paletteId < Project::getNumPalettesPrimary() ? primaryTileset : secondaryTileset; - auto palettes = useTruePalettes ? tileset->palettes : tileset->palettePreviews; + if (!tileset) { + return paletteTable; + } - if (paletteId < 0 || paletteId >= palettes.length()){ - logError(QString("Invalid tileset palette id '%1' requested.").arg(paletteId)); + auto palettes = useTruePalettes ? tileset->palettes : tileset->palettePreviews; + if (paletteId < 0 || paletteId >= palettes.length()) { return paletteTable; } @@ -636,20 +638,20 @@ bool Tileset::savePalettes() { bool Tileset::load() { bool success = true; + if (!loadPalettes()) success = false; + if (!loadTilesImage()) success = false; if (!loadMetatiles()) success = false; if (!loadMetatileAttributes()) success = false; - if (!loadTilesImage()) success = false; - if (!loadPalettes()) success = false; return success; } // Because metatile labels are global (and handled by the project) we don't save them here. bool Tileset::save() { bool success = true; + if (!savePalettes()) success = false; + if (!saveTilesImage()) success = false; if (!saveMetatiles()) success = false; if (!saveMetatileAttributes()) success = false; - if (!saveTilesImage()) success = false; - if (!savePalettes()) success = false; return success; } diff --git a/src/editor.cpp b/src/editor.cpp index b043c942..daa490e4 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1372,8 +1372,10 @@ bool Editor::setLayout(QString layoutId) { if (this->layout->name != prevLayoutName) Scripting::cb_LayoutOpened(this->layout->name); - if (this->layout->tileset_primary_label != prevPrimaryTileset || this->layout->tileset_secondary_label != prevSecondaryTileset) - Scripting::cb_TilesetsChanged(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); + if (this->layout->tileset_primary_label != prevPrimaryTileset) + Scripting::cb_TilesetUpdated(this->layout->tileset_primary_label); + if (this->layout->tileset_secondary_label != prevSecondaryTileset) + Scripting::cb_TilesetUpdated(this->layout->tileset_secondary_label); return true; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 697ce835..2e2f9440 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1820,7 +1820,9 @@ void MainWindow::currentMetatilesSelectionChanged() { redrawMetatileSelection(); if (this->tilesetEditor) { MetatileSelection selection = editor->metatile_selector_item->getMetatileSelection(); - this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId); + if (!selection.metatileItems.isEmpty()) { + this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId); + } } // Don't scroll to internal selections here, it will disrupt the user while they make their selection. @@ -2603,20 +2605,20 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT bool updated = false; if (primaryTilesetLabel == this->editor->layout->tileset_primary_label) { this->editor->updatePrimaryTileset(primaryTilesetLabel, true); + Scripting::cb_TilesetUpdated(primaryTilesetLabel); updated = true; } else { this->editor->project->getTileset(primaryTilesetLabel, true); } if (secondaryTilesetLabel == this->editor->layout->tileset_secondary_label) { this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); + Scripting::cb_TilesetUpdated(secondaryTilesetLabel); updated = true; } else { this->editor->project->getTileset(secondaryTilesetLabel, true); } - if (updated) { + if (updated) redrawMapScene(); - Scripting::cb_TilesetsChanged(primaryTilesetLabel, secondaryTilesetLabel); - } } void MainWindow::onMapRulerStatusChanged(const QString &status) { diff --git a/src/project.cpp b/src/project.cpp index 2957ca2b..e1f074b7 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1558,9 +1558,8 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa tileset->loadTilesImage(&tilesImage); // Create default metatiles - const int numMetatiles = tileset->is_secondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); const int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); - for (int i = 0; i < numMetatiles; ++i) { + for (int i = 0; i < tileset->maxMetatiles(); ++i) { auto metatile = new Metatile(); for(int j = 0; j < tilesPerMetatile; ++j){ Tile tile = Tile(); diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 5de63221..148a75fe 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -16,7 +16,7 @@ const QMap callbackFunctions = { {OnMapResized, "onMapResized"}, {OnBorderResized, "onBorderResized"}, {OnMapShifted, "onMapShifted"}, - {OnTilesetsChanged, "onTilesetsChanged"}, + {OnTilesetUpdated, "onTilesetUpdated"}, {OnMainTabChanged, "onMainTabChanged"}, {OnMapViewTabChanged, "onMapViewTabChanged"}, {OnBorderVisibilityToggled, "onBorderVisibilityToggled"}, @@ -99,15 +99,12 @@ void Scripting::populateGlobalObject(MainWindow *mainWindow) { constants.setProperty("version", version); // Get basic tileset information - int numTilesPrimary = Project::getNumTilesPrimary(); - int numMetatilesPrimary = Project::getNumMetatilesPrimary(); - int numPalettesPrimary = Project::getNumPalettesPrimary(); - constants.setProperty("max_primary_tiles", numTilesPrimary); - constants.setProperty("max_secondary_tiles", Project::getNumTilesTotal() - numTilesPrimary); - constants.setProperty("max_primary_metatiles", numMetatilesPrimary); - constants.setProperty("max_secondary_metatiles", Project::getNumMetatilesTotal() - numMetatilesPrimary); - constants.setProperty("num_primary_palettes", numPalettesPrimary); - constants.setProperty("num_secondary_palettes", Project::getNumPalettesTotal() - numPalettesPrimary); + constants.setProperty("max_primary_tiles", Project::getNumTilesPrimary()); + constants.setProperty("max_secondary_tiles", Project::getNumTilesSecondary()); + constants.setProperty("max_primary_metatiles", Project::getNumMetatilesPrimary()); + constants.setProperty("max_secondary_metatiles", Project::getNumMetatilesSecondary()); + constants.setProperty("num_primary_palettes", Project::getNumPalettesPrimary()); + constants.setProperty("num_secondary_palettes", Project::getNumPalettesSecondary()); constants.setProperty("layers_per_metatile", projectConfig.getNumLayersInMetatile()); constants.setProperty("tiles_per_metatile", projectConfig.getNumTilesInMetatile()); @@ -301,14 +298,13 @@ void Scripting::cb_MapShifted(int xDelta, int yDelta) { instance->invokeCallback(OnMapShifted, args); } -void Scripting::cb_TilesetsChanged(const QString &primaryTilesetName, const QString &secondaryTilesetName) { +void Scripting::cb_TilesetUpdated(const QString &tilesetName) { if (!instance) return; QJSValueList args { - primaryTilesetName, - secondaryTilesetName + tilesetName, }; - instance->invokeCallback(OnTilesetsChanged, args); + instance->invokeCallback(OnTilesetUpdated, args); } void Scripting::cb_MainTabChanged(int oldTab, int newTab) { diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index 4e847b1b..ef3c33db 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -44,11 +44,10 @@ void BorderMetatilesPixmapItem::draw() { for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { + QImage metatile_image = getMetatileImage(layout->getBorderMetatileId(i, j), layout); int x = i * Metatile::pixelWidth(); int y = j * Metatile::pixelHeight(); - QImage metatile_image = getMetatileImage(layout->getBorderMetatileId(i, j), layout); - QPoint metatile_origin = QPoint(x, y); - painter.drawImage(metatile_origin, metatile_image); + painter.drawImage(x, y, metatile_image); } } diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 188398bb..f2e9b26c 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -1,6 +1,5 @@ #include "config.h" #include "imageproviders.h" -#include "log.h" #include "editor.h" #include @@ -62,10 +61,10 @@ QImage getMetatileImage( const QList &layerOpacity, bool useTruePalettes) { - QImage metatile_image(Metatile::pixelWidth(), Metatile::pixelHeight(), QImage::Format_RGBA8888); + QImage metatileImage(Metatile::pixelSize(), QImage::Format_RGBA8888); if (!metatile) { - metatile_image.fill(getInvalidImageColor()); - return metatile_image; + metatileImage.fill(getInvalidImageColor()); + return metatileImage; } QList> palettes = Tileset::getBlockPalettes(primaryTileset, secondaryTileset, useTruePalettes); @@ -75,9 +74,9 @@ QImage getMetatileImage( // The GBA renders transparent pixels using palette 0 color 0. We have this color, // but all 3 games actually overwrite it with black when loading the tileset palettes, // so we have a setting to specify an override transparency color. - metatile_image.fill(projectConfig.transparencyColor.isValid() ? projectConfig.transparencyColor : QColor(palettes.value(0).value(0))); + metatileImage.fill(projectConfig.transparencyColor.isValid() ? projectConfig.transparencyColor : QColor(palettes.value(0).value(0))); - QPainter metatile_painter(&metatile_image); + QPainter painter(&metatileImage); uint32_t layerType = metatile->layerType(); for (const auto &layer : layerOrder) @@ -115,45 +114,31 @@ QImage getMetatileImage( } } - QImage tile_image = getTileImage(tile.tileId, primaryTileset, secondaryTileset); - if (tile_image.isNull()) { - // Some metatiles specify tiles that are outside the valid range. - // The way the GBA will render these depends on what's in memory (which Porymap can't know) - // so we treat them as if they were transparent. - continue; - } + QImage tileImage = getColoredTileImage(tile.tileId, primaryTileset, secondaryTileset, palettes.value(tile.palette)); - // Colorize the metatile tiles with its palette. - if (tile.palette < palettes.length()) { - const QList palette = palettes.value(tile.palette); - for (int j = 0; j < palette.length(); j++) { - tile_image.setColor(j, palette.value(j)); - } - } else { - logWarn(QString("Tile '%1' is referring to invalid palette number: '%2'").arg(tile.tileId).arg(tile.palette)); - } - - QPoint origin = QPoint(x * Tile::pixelWidth(), y * Tile::pixelHeight()); float opacity = layerOpacity.value(layer, 1.0); if (opacity < 1.0) { int alpha = 255 * opacity; - for (int c = 0; c < tile_image.colorCount(); c++) { - QColor color(tile_image.color(c)); + for (int c = 0; c < tileImage.colorCount(); c++) { + QColor color(tileImage.color(c)); color.setAlpha(alpha); - tile_image.setColor(c, color.rgba()); + tileImage.setColor(c, color.rgba()); } } // Color 0 is displayed as transparent. - QColor color(tile_image.color(0)); - color.setAlpha(0); - tile_image.setColor(0, color.rgba()); - tile.flip(&tile_image); - metatile_painter.drawImage(origin, tile_image); - } - metatile_painter.end(); + if (tileImage.colorCount()) { + QColor color(tileImage.color(0)); + color.setAlpha(0); + tileImage.setColor(0, color.rgba()); + } - return metatile_image; + tile.flip(&tileImage); + painter.drawImage(x * Tile::pixelWidth(), y * Tile::pixelHeight(), tileImage); + } + painter.end(); + + return metatileImage; } QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { @@ -164,15 +149,16 @@ QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondary QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) { QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset); if (tileImage.isNull()) { - tileImage = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_RGBA8888); - QPainter painter(&tileImage); - painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.value(0)); + // Some tiles specify tile IDs or palette IDs that are outside the valid range. + // The way the GBA will render these depends on what's in memory (which Porymap can't know) + // so we render them using the invalid color + tileImage = QImage(Tile::pixelSize(), QImage::Format_RGBA8888); + tileImage.fill(getInvalidImageColor()); } else { for (int i = 0; i < 16; i++) { - tileImage.setColor(i, palette.value(i)); + tileImage.setColor(i, palette.value(i, getInvalidImageColor().rgb())); } } - return tileImage; } diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp index 5ffbf8d7..4c3b6b03 100644 --- a/src/ui/metatileimageexporter.cpp +++ b/src/ui/metatileimageexporter.cpp @@ -255,23 +255,20 @@ void MetatileImageExporter::validateMetatileEnd() { ui->spinBox_MetatileEnd->value())); } -uint16_t MetatileImageExporter::getExpectedMetatileStart() { - if (ui->checkBox_PrimaryTileset->isChecked()) return 0; - if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary(); - return ui->spinBox_MetatileStart->value(); -} - -uint16_t MetatileImageExporter::getExpectedMetatileEnd() { - if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary() + (m_secondaryTileset ? (m_secondaryTileset->numMetatiles() - 1) : 0); - if (ui->checkBox_PrimaryTileset->isChecked()) return m_primaryTileset ? (m_primaryTileset->numMetatiles() - 1) : 0; - return ui->spinBox_MetatileEnd->value(); -} - void MetatileImageExporter::updateMetatileRange() { + Tileset *tileset = nullptr; + if (ui->checkBox_PrimaryTileset->isChecked()) { + tileset = m_primaryTileset; + } else if (ui->checkBox_PrimaryTileset->isChecked()) { + tileset = m_secondaryTileset; + } + if (!tileset) + return; + const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart); const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileEnd); - ui->spinBox_MetatileStart->setValue(getExpectedMetatileStart()); - ui->spinBox_MetatileEnd->setValue(getExpectedMetatileEnd()); + ui->spinBox_MetatileStart->setValue(tileset->firstMetatileId()); + ui->spinBox_MetatileEnd->setValue(tileset->lastMetatileId()); } void MetatileImageExporter::updateTilesetUI() { diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index 5ce6e68c..bae09ee6 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -129,7 +129,7 @@ void MetatileLayersItem::hoverMoveEvent(QGraphicsSceneHoverEvent * event) { if (tileIndex < 0 || tileIndex >= this->metatile->tiles.length()) return; - emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex).tileId); + emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex)); } void MetatileLayersItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index d8f50410..4fe8060a 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -3,15 +3,16 @@ #include "project.h" #include -QPoint MetatileSelector::getSelectionDimensions() { +QPoint MetatileSelector::getSelectionDimensions() const { if (this->prefabSelection || this->externalSelection) return selection.dimensions; return SelectablePixmapItem::getSelectionDimensions(); } int MetatileSelector::numPrimaryMetatilesRounded() const { - // We round up the number of primary metatiles to keep the tilesets on separate rows. - return ceil((double)this->primaryTileset()->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; + if (!primaryTileset()) + return 0; + return Util::roundUpToMultiple(primaryTileset()->numMetatiles(), this->numMetatilesWide); } void MetatileSelector::updateBasePixmap() { @@ -32,7 +33,11 @@ void MetatileSelector::drawSelection() { } bool MetatileSelector::select(uint16_t metatileId) { - if (!this->layout->metatileIsValid(metatileId)) return false; + bool ok; + QPoint pos = metatileIdToPos(metatileId, &ok); + if (!ok) { + return false; + } this->externalSelection = false; this->prefabSelection = false; this->selection = MetatileSelection{ @@ -41,8 +46,7 @@ bool MetatileSelector::select(uint16_t metatileId) { QList({MetatileSelectionItem{true, metatileId}}), QList(), }; - QPoint coords = this->getMetatileIdCoords(metatileId); - SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); + SelectablePixmapItem::select(pos); this->updateSelectedMetatiles(); return true; } @@ -67,11 +71,7 @@ void MetatileSelector::refresh() { setLayout(this->layout); } -MetatileSelection MetatileSelector::getMetatileSelection() { - return selection; -} - -void MetatileSelector::setExternalSelection(int width, int height, QList metatiles, QList> collisions) { +void MetatileSelector::setExternalSelection(int width, int height, const QList &metatiles, const QList> &collisions) { this->prefabSelection = false; this->externalSelection = true; this->externalSelectionWidth = width; @@ -81,18 +81,18 @@ void MetatileSelector::setExternalSelection(int width, int height, QListselection.collisionItems.clear(); this->selection.hasCollision = true; this->selection.dimensions = QPoint(width, height); - for (int i = 0; i < metatiles.length(); i++) { - auto collision = collisions.at(i); - this->selection.collisionItems.append(CollisionSelectionItem{true, collision.first, collision.second}); + for (int i = 0; i < qMin(metatiles.length(), collisions.length()); i++) { uint16_t metatileId = metatiles.at(i); + uint16_t collision = collisions.at(i).first; + uint16_t elevation = collisions.at(i).second; + this->selection.collisionItems.append(CollisionSelectionItem{true, collision, elevation}); this->externalSelectedMetatiles.append(metatileId); if (!this->layout->metatileIsValid(metatileId)) metatileId = 0; this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId}); } if (this->selection.metatileItems.length() == 1) { - QPoint coords = this->getMetatileIdCoords(this->selection.metatileItems.first().metatileId); - SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); + SelectablePixmapItem::select(metatileIdToPos(this->selection.metatileItems.first().metatileId)); } this->draw(); @@ -109,7 +109,9 @@ void MetatileSelector::setPrefabSelection(MetatileSelection selection) { } bool MetatileSelector::positionIsValid(const QPoint &pos) const { - return this->layout->metatileIsValid(getMetatileId(pos.x(), pos.y())); + bool ok; + posToMetatileId(pos, &ok); + return ok; } void MetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { @@ -150,8 +152,14 @@ void MetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { } void MetatileSelector::hoverChanged() { - uint16_t metatileId = this->getMetatileId(this->cellPos.x(), this->cellPos.y()); - emit this->hoveredMetatileSelectionChanged(metatileId); + bool ok; + uint16_t metatileId = posToMetatileId(this->cellPos, &ok); + if (ok) { + emit this->hoveredMetatileSelectionChanged(metatileId); + } else { + emit this->hoveredMetatileSelectionCleared(); + this->cellPos = QPoint(-1, -1); + } } void MetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { @@ -169,9 +177,7 @@ void MetatileSelector::updateSelectedMetatiles() { QPoint origin = this->getSelectionStart(); for (int j = 0; j < this->selection.dimensions.y(); j++) { for (int i = 0; i < this->selection.dimensions.x(); i++) { - uint16_t metatileId = this->getMetatileId(origin.x() + i, origin.y() + j); - if (!this->layout->metatileIsValid(metatileId)) - metatileId = 0; + uint16_t metatileId = posToMetatileId(origin.x() + i, origin.y() + j); this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId}); } } @@ -190,29 +196,56 @@ void MetatileSelector::updateExternalSelectedMetatiles() { emit selectedMetatilesChanged(); } -uint16_t MetatileSelector::getMetatileId(int x, int y) const { +uint16_t MetatileSelector::posToMetatileId(const QPoint &pos, bool *ok) const { + return posToMetatileId(pos.x(), pos.y(), ok); +} + +uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const { + if (ok) *ok = true; int index = y * this->numMetatilesWide + x; - int numPrimary = this->numPrimaryMetatilesRounded(); - if (index < numPrimary) { - return static_cast(index); - } else { - return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary); - } -} - -QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) { - if (!this->layout->metatileIsValid(metatileId)) { - return QPoint(0, 0); + uint16_t metatileId = static_cast(index); + if (primaryTileset() && primaryTileset()->contains(metatileId)) { + return metatileId; } - int index = metatileId < Project::getNumMetatilesPrimary() - ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded(); - return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); + // There's some extra handling here because we round the tilesets to keep them on separate rows. + // This means if the maximum number of primary metatiles is not divisible by the metatile width + // then the metatiles we used to round the primary tileset would have the index of valid secondary metatiles. + // These need to be ignored, or they'll appear to be duplicates of the subseqeunt secondary metatiles. + int numPrimaryRounded = numPrimaryMetatilesRounded(); + int firstSecondaryRow = numPrimaryRounded / qMax(this->numMetatilesWide, 1); + metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded); + if (secondaryTileset() && secondaryTileset()->contains(metatileId) && y >= firstSecondaryRow) { + return metatileId; + } + + if (ok) *ok = false; + return 0; } -QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) { - QPoint pos = getMetatileIdCoords(metatileId); +QPoint MetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const { + if (this->numMetatilesWide == 0) { + if (ok) *ok = false; + return QPoint(0,0); + } + + if (primaryTileset() && primaryTileset()->contains(metatileId)) { + if (ok) *ok = true; + int index = metatileId; + return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); + } + if (secondaryTileset() && secondaryTileset()->contains(metatileId)) { + if (ok) *ok = true; + int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded(); + return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); + } + + if (ok) *ok = false; + return QPoint(0,0); +} + +QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) const { + QPoint pos = metatileIdToPos(metatileId); pos.rx() = (pos.x() * this->cellWidth) + (this->cellWidth / 2); pos.ry() = (pos.y() * this->cellHeight) + (this->cellHeight / 2); return pos; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index f9f820ab..07a503ec 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -232,7 +232,7 @@ QList ProjectSettingsEditor::getBorderMetatileIds(bool customSize) { // Custom border size, read metatiles from line edit for (auto s : ui->lineEdit_BorderMetatiles->text().split(",")) { uint16_t metatileId = s.toUInt(nullptr, 0); - metatileIds.append(qMin(metatileId, static_cast(Project::getNumMetatilesTotal() - 1))); + metatileIds.append(qMin(metatileId, Block::getMaxMetatileId())); } } else { // Default border size, read metatiles from spin boxes @@ -475,11 +475,10 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData); // Radio buttons - // TODO: Replace - /*if (projectConfig.setTransparentPixelsBlack) + if (projectConfig.transparencyColor == QColor(Qt::black)) ui->radioButton_RenderBlack->setChecked(true); else - ui->radioButton_RenderFirstPalColor->setChecked(true);*/ + ui->radioButton_RenderFirstPalColor->setChecked(true); // Set spin box values ui->spinBox_Elevation->setValue(projectConfig.defaultElevation); @@ -575,7 +574,7 @@ void ProjectSettingsEditor::save() { projectConfig.tilesetsHaveCallback = ui->checkBox_OutputCallback->isChecked(); projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked(); porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked(); - //projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); // TODO + projectConfig.transparencyColor = ui->radioButton_RenderBlack->isChecked() ? QColor(Qt::black) : QColor(); projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked(); // Save spin box settings diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index 3b212579..bd001e59 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -1,7 +1,7 @@ #include "selectablepixmapitem.h" #include -QPoint SelectablePixmapItem::getSelectionDimensions() +QPoint SelectablePixmapItem::getSelectionDimensions() const { return QPoint(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e64a6add..3fc80774 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -241,8 +241,9 @@ void TilesetEditor::initMetatileLayersItem() { this, &TilesetEditor::onMetatileLayerTileChanged); connect(this->metatileLayersItem, &MetatileLayersItem::selectedTilesChanged, this, &TilesetEditor::onMetatileLayerSelectionChanged); - connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, - this, &TilesetEditor::onHoveredTileChanged); + connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, [this](const Tile &tile) { + onHoveredTileChanged(tile); + }); connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared); @@ -258,8 +259,9 @@ void TilesetEditor::initMetatileLayersItem() { void TilesetEditor::initTileSelector() { this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset, projectConfig.getNumLayersInMetatile()); - connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, - this, &TilesetEditor::onHoveredTileChanged); + connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) { + onHoveredTileChanged(tileId); + }); connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared); connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, @@ -453,8 +455,17 @@ void TilesetEditor::queueMetatileReload(uint16_t metatileId) { this->metatileReloadQueue.insert(metatileId); } -void TilesetEditor::onHoveredTileChanged(uint16_t tile) { - this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tile, 3))); +void TilesetEditor::onHoveredTileChanged(const Tile &tile) { + this->ui->statusbar->showMessage(QString("Tile: %1, Palette: %2%3%4") + .arg(Util::toHexString(tile.tileId, 3)) + .arg(QString::number(tile.palette)) + .arg(tile.xflip ? ", X-flipped" : "") + .arg(tile.yflip ? ", Y-flipped" : "") + ); +} + +void TilesetEditor::onHoveredTileChanged(uint16_t tileId) { + this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tileId, 3))); } void TilesetEditor::onHoveredTileCleared() { @@ -834,7 +845,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() primarySpinBox->setMinimum(1); secondarySpinBox->setMinimum(1); primarySpinBox->setMaximum(Project::getNumMetatilesPrimary()); - secondarySpinBox->setMaximum(Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()); + secondarySpinBox->setMaximum(Project::getNumMetatilesSecondary()); primarySpinBox->setValue(this->primaryTileset->numMetatiles()); secondarySpinBox->setValue(this->secondaryTileset->numMetatiles()); form.addRow(new QLabel("Primary Tileset"), primarySpinBox); @@ -1018,7 +1029,7 @@ void TilesetEditor::importAdvanceMapMetatiles(Tileset *tileset) { // TODO: This is crude because it makes a history entry for every newly-imported metatile. // Revisit this when tiles and num metatiles are added to tileset editory history. - int metatileIdBase = primary ? 0 : Project::getNumMetatilesPrimary(); + uint16_t metatileIdBase = tileset->firstMetatileId(); for (int i = 0; i < metatiles.length(); i++) { if (i >= tileset->numMetatiles()) { break; diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 2d589195..6c9cc74c 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -3,6 +3,9 @@ #include "project.h" #include +// TODO: This class has a decent bit of overlap with the MetatileSelector class. +// They should be refactored to inherit from a single parent class. + TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) : SelectablePixmapItem(32, 32, 1, 1) { this->primaryTileset = primaryTileset; @@ -13,7 +16,7 @@ TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTil this->usedMetatiles.resize(Project::getNumMetatilesTotal()); } -int TilesetEditorMetatileSelector::numRows(int numMetatiles) { +int TilesetEditorMetatileSelector::numRows(int numMetatiles) const { int numMetatilesHigh = numMetatiles / this->numMetatilesWide; if (numMetatiles % this->numMetatilesWide != 0) { // Round up height for incomplete last row @@ -22,17 +25,21 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) { return numMetatilesHigh; } -int TilesetEditorMetatileSelector::numRows() { +int TilesetEditorMetatileSelector::numRows() const { return this->numRows(this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles()); } int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const { - // We round up the number of primary metatiles to keep the tilesets on separate rows. - return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; + if (!this->primaryTileset) + return 0; + return Util::roundUpToMultiple(this->primaryTileset->numMetatiles(), this->numMetatilesWide); } void TilesetEditorMetatileSelector::drawMetatile(uint16_t metatileId) { - QPoint pos = getMetatileIdCoords(metatileId); + bool ok; + QPoint pos = metatileIdToPos(metatileId, &ok); + if (!ok) + return; QPainter painter(&this->baseImage); QImage metatile_image = getMetatileImage( @@ -78,9 +85,11 @@ void TilesetEditorMetatileSelector::draw() { } bool TilesetEditorMetatileSelector::select(uint16_t metatileId) { - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) return false; - QPoint coords = this->getMetatileIdCoords(metatileId); - SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); + bool ok; + QPoint pos = metatileIdToPos(metatileId, &ok); + if (!ok) + return false; + SelectablePixmapItem::select(pos); this->selectedMetatileId = metatileId; emit selectedMetatileChanged(metatileId); return true; @@ -95,32 +104,19 @@ void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset } void TilesetEditorMetatileSelector::updateSelectedMetatile() { - QPoint origin = this->getSelectionStart(); - uint16_t metatileId = this->getMetatileId(origin.x(), origin.y()); - if (Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) - this->selectedMetatileId = metatileId; - else - this->selectedMetatileId = Project::getNumMetatilesPrimary() + this->secondaryTileset->numMetatiles() - 1; + bool ok; + uint16_t metatileId = posToMetatileId(getSelectionStart(), &ok); + if (!ok) + return; + + this->selectedMetatileId = metatileId; emit selectedMetatileChanged(this->selectedMetatileId); } -uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() { - return this->selectedMetatileId; -} - -uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) { - int index = y * this->numMetatilesWide + x; - int numPrimary = numPrimaryMetatilesRounded(); - if (index < numPrimary) { - return static_cast(index); - } else { - return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary); - } -} - bool TilesetEditorMetatileSelector::shouldAcceptEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = this->getCellPos(event->pos()); - return Tileset::metatileIsValid(getMetatileId(pos.x(), pos.y()), this->primaryTileset, this->secondaryTileset); + bool ok; + posToMetatileId(getCellPos(event->pos()), &ok); + return ok; } void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { @@ -143,29 +139,69 @@ void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent * } void TilesetEditorMetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { - QPoint pos = this->getCellPos(event->pos()); - uint16_t metatileId = this->getMetatileId(pos.x(), pos.y()); - emit this->hoveredMetatileChanged(metatileId); + bool ok; + uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok); + if (ok) { + emit this->hoveredMetatileChanged(metatileId); + } else { + emit this->hoveredMetatileCleared(); + } } void TilesetEditorMetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { emit this->hoveredMetatileCleared(); } -QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) { - if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) - { - // Invalid metatile id. - return QPoint(0, 0); - } - int index = metatileId < Project::getNumMetatilesPrimary() - ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded(); - return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); +uint16_t TilesetEditorMetatileSelector::posToMetatileId(const QPoint &pos, bool *ok) const { + return posToMetatileId(pos.x(), pos.y(), ok); } -QPoint TilesetEditorMetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) { - QPoint pos = getMetatileIdCoords(metatileId); +uint16_t TilesetEditorMetatileSelector::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 (this->primaryTileset && this->primaryTileset->contains(metatileId)) { + return metatileId; + } + + // There's some extra handling here because we round the tilesets to keep them on separate rows. + // This means if the maximum number of primary metatiles is not divisible by the metatile width + // then the metatiles we used to round the primary tileset would have the index of valid secondary metatiles. + // These need to be ignored, or they'll appear to be duplicates of the subseqeunt secondary metatiles. + int numPrimaryRounded = numPrimaryMetatilesRounded(); + int firstSecondaryRow = numPrimaryRounded / qMax(this->numMetatilesWide, 1); + metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded); + if (this->secondaryTileset && this->secondaryTileset->contains(metatileId) && y >= firstSecondaryRow) { + return metatileId; + } + + if (ok) *ok = false; + return 0; +} + +QPoint TilesetEditorMetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const { + if (this->numMetatilesWide == 0) { + if (ok) *ok = false; + return QPoint(0,0); + } + + if (this->primaryTileset && this->primaryTileset->contains(metatileId)) { + if (ok) *ok = true; + int index = metatileId; + return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); + } + if (this->secondaryTileset && this->secondaryTileset->contains(metatileId)) { + if (ok) *ok = true; + int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded(); + return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); + } + + if (ok) *ok = false; + return QPoint(0,0); +} + +QPoint TilesetEditorMetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) const { + QPoint pos = metatileIdToPos(metatileId); pos.rx() = (pos.x() * this->cellWidth) + (this->cellWidth / 2); pos.ry() = (pos.y() * this->cellHeight) + (this->cellHeight / 2); return pos; diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 31ab088e..95d5be6c 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -4,7 +4,7 @@ #include #include -QPoint TilesetEditorTileSelector::getSelectionDimensions() { +QPoint TilesetEditorTileSelector::getSelectionDimensions() const { if (this->externalSelection) { return QPoint(this->externalSelectionWidth, this->externalSelectionHeight); } else { From 26ad93f2ae6b742d5520e97d3b56630a53a1bdbf Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 22 Jul 2025 01:34:03 -0400 Subject: [PATCH 31/71] Add Porytiles export shortcut --- forms/tileseteditor.ui | 90 ++++++++++++-------- include/ui/metatileimageexporter.h | 12 ++- include/ui/tileseteditor.h | 1 + src/core/tileset.cpp | 2 +- src/ui/metatileimageexporter.cpp | 127 +++++++++++++++++++---------- src/ui/tileseteditor.cpp | 48 ++++++++++- 6 files changed, 195 insertions(+), 85 deletions(-) diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 36aa92f7..895fae0f 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -11,7 +11,7 @@ - Qt::ClickFocus + Qt::FocusPolicy::ClickFocus Tileset Editor @@ -21,7 +21,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal false @@ -34,10 +34,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -58,7 +58,7 @@ true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -88,17 +88,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Vertical + Qt::Orientation::Vertical @@ -121,7 +121,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -129,10 +129,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -162,14 +162,14 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize 0 @@ -255,7 +255,7 @@ - QComboBox::NoInsert + QComboBox::InsertPolicy::NoInsert @@ -274,10 +274,10 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff @@ -290,17 +290,17 @@ - QComboBox::NoInsert + QComboBox::InsertPolicy::NoInsert - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -319,7 +319,7 @@ - QComboBox::NoInsert + QComboBox::InsertPolicy::NoInsert @@ -361,7 +361,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -412,7 +412,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight @@ -436,10 +436,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -464,10 +464,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -510,13 +510,13 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff @@ -526,7 +526,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -548,7 +548,7 @@ true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -575,17 +575,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Vertical + Qt::Orientation::Vertical @@ -602,7 +602,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -646,6 +646,13 @@ + + + Export Porytiles Layer Images... + + + + @@ -653,6 +660,7 @@ + @@ -845,6 +853,16 @@ Export Metatiles Image... + + + Primary... + + + + + Secondary... + + diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h index d155ad3f..99c3528b 100644 --- a/include/ui/metatileimageexporter.h +++ b/include/ui/metatileimageexporter.h @@ -62,6 +62,12 @@ public: explicit MetatileImageExporter(QWidget *parent, Tileset *primaryTileset, Tileset *secondaryTileset, Settings *savedSettings = nullptr); ~MetatileImageExporter(); + bool saveImage(QString filepath = QString()); + QImage getImage(); + QString getDefaultFileName() const; + void applySettings(const Settings &settings); + void reset(); + protected: virtual void showEvent(QShowEvent *) override; virtual void closeEvent(QCloseEvent *) override; @@ -80,11 +86,11 @@ private: ProjectConfig m_savedConfig; QList m_transparencyButtons; - void applySettings(const Settings &settings); + void populate(const Settings &settings); void updatePreview(); void tryUpdatePreview(); void queuePreviewUpdate(); - void updateTilesetUI(); + void tryEnforceMetatileRange(); void syncPixelWidth(); void syncMetatileWidth(); void validateMetatileStart(); @@ -92,8 +98,6 @@ private: void updateMetatileRange(); void copyRenderSettings(); void restoreRenderSettings(); - void saveImage(); - void reset(); }; #endif // METATILEIMAGEEXPORTER_H diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index f9ccfee4..dc5b935a 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -115,6 +115,7 @@ private: void importTilesetTiles(Tileset*); void importAdvanceMapMetatiles(Tileset*); void exportTilesImage(Tileset*); + void exportPorytilesLayerImages(Tileset*); void exportMetatilesImage(); void refresh(); void commitMetatileLabel(); diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 84a34548..5f7ac20b 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -99,7 +99,7 @@ uint16_t Tileset::firstMetatileId() const { } uint16_t Tileset::lastMetatileId() const { - return firstMetatileId() + qMax(m_metatiles.length(), 1) - 1; + return qMax(1, firstMetatileId() + m_metatiles.length()) - 1; } int Tileset::maxMetatiles() const { diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp index 4c3b6b03..92dc5079 100644 --- a/src/ui/metatileimageexporter.cpp +++ b/src/ui/metatileimageexporter.cpp @@ -15,7 +15,6 @@ MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTi m_secondaryTileset(secondaryTileset), m_savedSettings(savedSettings) { - setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); m_transparencyButtons = { ui->radioButton_TransparencyNormal, @@ -47,15 +46,15 @@ MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTi } if (m_savedSettings) { - applySettings(*m_savedSettings); + populate(*m_savedSettings); } else { - applySettings({}); + populate({}); } connect(ui->listWidget_Layers, &ReorderableListWidget::itemChanged, this, &MetatileImageExporter::updatePreview); connect(ui->listWidget_Layers, &ReorderableListWidget::reordered, this, &MetatileImageExporter::updatePreview); - connect(ui->pushButton_Save, &QPushButton::pressed, this, &MetatileImageExporter::saveImage); + connect(ui->pushButton_Save, &QPushButton::pressed, [this] { saveImage(); }); connect(ui->pushButton_Close, &QPushButton::pressed, this, &MetatileImageExporter::close); connect(ui->pushButton_Reset, &QPushButton::pressed, this, &MetatileImageExporter::reset); @@ -82,9 +81,9 @@ MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTi connect(ui->radioButton_TransparencyFirst, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview); connect(ui->checkBox_Placeholders, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview); - connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updateTilesetUI); + connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::tryEnforceMetatileRange); connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview); - connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updateTilesetUI); + connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::tryEnforceMetatileRange); connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview); ui->graphicsView_Preview->setFocus(); @@ -128,19 +127,36 @@ void MetatileImageExporter::closeEvent(QCloseEvent *event) { QDialog::closeEvent(event); } -void MetatileImageExporter::applySettings(const Settings &settings) { +void MetatileImageExporter::populate(const Settings &settings) { + const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart); ui->spinBox_MetatileStart->setValue(settings.metatileStart); + + const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileStart); ui->spinBox_MetatileEnd->setValue(settings.metatileEnd); + + const QSignalBlocker b_WidthMetatiles(ui->spinBox_MetatileStart); ui->spinBox_WidthMetatiles->setValue(settings.numMetatilesWide); + + const QSignalBlocker b_WidthPixels(ui->spinBox_MetatileStart); ui->spinBox_WidthPixels->setValue(settings.numMetatilesWide * Metatile::pixelWidth()); + + const QSignalBlocker b_PrimaryTileset(ui->spinBox_MetatileStart); ui->checkBox_PrimaryTileset->setChecked(settings.usePrimaryTileset); + + const QSignalBlocker b_SecondaryTileset(ui->spinBox_MetatileStart); ui->checkBox_SecondaryTileset->setChecked(settings.useSecondaryTileset); + + const QSignalBlocker b_Placeholders(ui->spinBox_MetatileStart); ui->checkBox_Placeholders->setChecked(settings.renderPlaceholders); + if (m_transparencyButtons.value(settings.transparencyMode)) { - m_transparencyButtons[settings.transparencyMode]->setChecked(true); + auto button = m_transparencyButtons[settings.transparencyMode]; + const QSignalBlocker b_Transparency(button); + button->setChecked(true); } // Build layer list from settings + const QSignalBlocker b_Layers(ui->listWidget_Layers); ui->listWidget_Layers->clear(); for (auto it = settings.layerOrder.cbegin(); it != settings.layerOrder.cend(); it++) { int layerNum = it.key(); @@ -155,45 +171,58 @@ void MetatileImageExporter::applySettings(const Settings &settings) { // Don't give extra unnecessary space to the list ui->listWidget_Layers->setFixedHeight(ui->listWidget_Layers->sizeHintForRow(0) * ui->listWidget_Layers->count() + 4); - updateTilesetUI(); + tryEnforceMetatileRange(); +} + +void MetatileImageExporter::applySettings(const Settings &settings) { + populate(settings); + updatePreview(); } void MetatileImageExporter::reset() { applySettings({}); - updatePreview(); } -void MetatileImageExporter::saveImage() { - // Ensure the image in the preview is up-to-date before exporting. - updatePreview(); +QImage MetatileImageExporter::getImage() { + tryUpdatePreview(); + return m_preview->pixmap().toImage(); +} - QString defaultFilename; +bool MetatileImageExporter::saveImage(QString filepath) { + tryUpdatePreview(); + if (filepath.isEmpty()) { + QString defaultFilepath = QString("%1/%2").arg(FileDialog::getDirectory()).arg(getDefaultFileName()); + filepath = FileDialog::getSaveFileName(this, windowTitle(), defaultFilepath, QStringLiteral("Image Files (*.png *.jpg *.bmp)")); + if (filepath.isEmpty()) { + return false; + } + } + return m_preview->pixmap().save(filepath); +} + +QString MetatileImageExporter::getDefaultFileName() const { if (m_layerOrder.length() == 1) { // Exporting a metatile layer image is an expected use case for Porytiles, which expects certain file names. // We can make the process a little easier by setting the default file name to those expected file names. static const QStringList layerFilenames = { "bottom", "middle", "top" }; - defaultFilename = (layerFilenames.at(m_layerOrder.constFirst())); - } else { - if (ui->checkBox_PrimaryTileset->isChecked() && m_primaryTileset) { - defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_primaryTileset->name))); - } - if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) { - defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_secondaryTileset->name))); - } - if (!m_layerOrder.isEmpty() && m_layerOrder != QList({0,1,2})) { - for (int i = m_layerOrder.length() - 1; i >= 0; i--) { - defaultFilename.append(Metatile::getLayerName(m_layerOrder.at(i))); - } - defaultFilename.append("_"); - } - defaultFilename.append("Metatiles"); + return layerFilenames.at(m_layerOrder.constFirst()) + ".png"; } - QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultFilename); - QString filepath = FileDialog::getSaveFileName(this, windowTitle(), defaultFilepath, QStringLiteral("Image Files (*.png *.jpg *.bmp)")); - if (!filepath.isEmpty()) { - m_preview->pixmap().save(filepath); + QString defaultFilename; + if (ui->checkBox_PrimaryTileset->isChecked() && m_primaryTileset) { + defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_primaryTileset->name))); } + if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) { + defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_secondaryTileset->name))); + } + if (!m_layerOrder.isEmpty() && m_layerOrder != QList({0,1,2})) { + for (int i = m_layerOrder.length() - 1; i >= 0; i--) { + defaultFilename.append(Metatile::getLayerName(m_layerOrder.at(i))); + } + defaultFilename.append("_"); + } + defaultFilename.append("Metatiles.png"); + return defaultFilename; } void MetatileImageExporter::queuePreviewUpdate() { @@ -256,22 +285,34 @@ void MetatileImageExporter::validateMetatileEnd() { } void MetatileImageExporter::updateMetatileRange() { - Tileset *tileset = nullptr; - if (ui->checkBox_PrimaryTileset->isChecked()) { - tileset = m_primaryTileset; - } else if (ui->checkBox_PrimaryTileset->isChecked()) { - tileset = m_secondaryTileset; - } - if (!tileset) + uint16_t min; + uint16_t max; + if (ui->checkBox_PrimaryTileset->isChecked() && m_primaryTileset) { + if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) { + // Both tilesets enforced + min = qMin(m_primaryTileset->firstMetatileId(), m_secondaryTileset->firstMetatileId()); + max = qMax(m_primaryTileset->lastMetatileId(), m_secondaryTileset->lastMetatileId()); + } else { + // Primary enforced + min = m_primaryTileset->firstMetatileId(); + max = m_primaryTileset->lastMetatileId(); + } + } else if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) { + // Secondary enforced + min = m_secondaryTileset->firstMetatileId(); + max = m_secondaryTileset->lastMetatileId(); + } else { + // No tilesets enforced return; + } const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart); const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileEnd); - ui->spinBox_MetatileStart->setValue(tileset->firstMetatileId()); - ui->spinBox_MetatileEnd->setValue(tileset->lastMetatileId()); + ui->spinBox_MetatileStart->setValue(min); + ui->spinBox_MetatileEnd->setValue(max); } -void MetatileImageExporter::updateTilesetUI() { +void MetatileImageExporter::tryEnforceMetatileRange() { // Users can either specify which tileset(s) to render, or specify a range of metatiles, but not both. if (ui->checkBox_PrimaryTileset->isChecked() || ui->checkBox_SecondaryTileset->isChecked()) { updateMetatileRange(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 3fc80774..279007b9 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -43,9 +43,12 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) connect(ui->actionImport_Primary_AdvanceMap_Metatiles, &QAction::triggered, [this] { importAdvanceMapMetatiles(this->primaryTileset); }); connect(ui->actionImport_Secondary_AdvanceMap_Metatiles, &QAction::triggered, [this] { importAdvanceMapMetatiles(this->secondaryTileset); }); - connect(ui->actionExport_Primary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->primaryTileset); }); + connect(ui->actionExport_Primary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->primaryTileset); }); connect(ui->actionExport_Secondary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->secondaryTileset); }); + connect(ui->actionExport_Primary_Porytiles_Layer_Images, &QAction::triggered, [this] { exportPorytilesLayerImages(this->primaryTileset); }); + connect(ui->actionExport_Secondary_Porytiles_Layer_Images, &QAction::triggered, [this] { exportPorytilesLayerImages(this->secondaryTileset); }); + connect(ui->actionExport_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); }); ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); @@ -1007,9 +1010,52 @@ void TilesetEditor::exportMetatilesImage() { this->metatileImageExportSettings = new MetatileImageExporter::Settings; } auto dialog = new MetatileImageExporter(this, this->primaryTileset, this->secondaryTileset, this->metatileImageExportSettings); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->open(); } +void TilesetEditor::exportPorytilesLayerImages(Tileset *tileset) { + QString dir = FileDialog::getExistingDirectory(this, QStringLiteral("Choose Folder to Export Images")); + if (dir.isEmpty()) { + return; + } + + MetatileImageExporter layerExporter(this, this->primaryTileset, this->secondaryTileset); + MetatileImageExporter::Settings settings = {}; + settings.usePrimaryTileset = !tileset->is_secondary; + settings.useSecondaryTileset = tileset->is_secondary; + + QMap images; + QStringList pathCollisions; + for (int i = 0; i < 3; i++) { + settings.layerOrder.clear(); + settings.layerOrder[i] = true; + layerExporter.applySettings(settings); + + QString filename = layerExporter.getDefaultFileName(); + QString path = QString("%1/%2").arg(dir).arg(filename); + if (QFileInfo::exists(path)) { + pathCollisions.append(filename); + } + images[path] = layerExporter.getImage(); + } + + if (!pathCollisions.isEmpty()) { + QString message = QString("The following files will be overwritten, are you sure you want to export?\n\n%1").arg(pathCollisions.join("\n")); + auto reply = QuestionMessage::show(message, this); + if (reply != QMessageBox::Yes) { + return; + } + } + + for (auto it = images.constBegin(); it != images.constEnd(); it++) { + QString path = it.key(); + if (!it.value().save(path)) { + logError(QString("Failed to save Porytiles layer image '%1'.").arg(path)); + } + } +} + void TilesetEditor::importAdvanceMapMetatiles(Tileset *tileset) { bool primary = !tileset->is_secondary; QString descriptorCaps = primary ? "Primary" : "Secondary"; From be9728523ee2ca31ec0ad837fb1ad5151076ce6f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Jul 2025 12:22:03 -0400 Subject: [PATCH 32/71] Add setting to change metatile selector width --- forms/projectsettingseditor.ui | 43 ++++++++++++++++------ include/config.h | 2 + include/ui/metatileimageexporter.h | 2 +- include/ui/metatileselector.h | 8 ++-- include/ui/tileseteditormetatileselector.h | 5 +-- src/config.cpp | 3 ++ src/editor.cpp | 2 +- src/ui/imageproviders.cpp | 2 +- src/ui/metatileselector.cpp | 7 +--- src/ui/projectsettingseditor.cpp | 3 ++ src/ui/tileseteditor.cpp | 2 +- src/ui/tileseteditormetatileselector.cpp | 13 ++----- 12 files changed, 56 insertions(+), 36 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index f732f335..f2284c07 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -852,7 +852,7 @@ 0 0 570 - 798 + 837 @@ -973,6 +973,27 @@ + + + + + + Metatile Selector Width + + + + + + + <html><head/><body><p>The width (in metatiles) of the metatile selectors on the Map tab and in the Tileset Editor.</p></body></html> + + + 1 + + + + + @@ -1873,16 +1894,6 @@ QComboBox
noscrollcombobox.h
- - NoScrollSpinBox - QSpinBox -
noscrollspinbox.h
-
- - NoScrollTextEdit - QTextEdit -
noscrolltextedit.h
-
UIntSpinBox QAbstractSpinBox @@ -1893,6 +1904,16 @@ UIntSpinBox
uintspinbox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+ + NoScrollTextEdit + QTextEdit +
noscrolltextedit.h
+
diff --git a/include/config.h b/include/config.h index 18c66c9e..e929d64c 100644 --- a/include/config.h +++ b/include/config.h @@ -325,6 +325,7 @@ public: this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; this->forcedMajorVersion = 0; + this->metatileSelectorWidth = 8; this->globalConstantsFilepaths.clear(); this->globalConstants.clear(); this->identifiers.clear(); @@ -404,6 +405,7 @@ public: QList warpBehaviors; int maxEventsPerGroup; int forcedMajorVersion; + int metatileSelectorWidth; QStringList globalConstantsFilepaths; QMap globalConstants; diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h index 99c3528b..1d486bd4 100644 --- a/include/ui/metatileimageexporter.h +++ b/include/ui/metatileimageexporter.h @@ -52,7 +52,7 @@ public: }; uint16_t metatileStart = 0; uint16_t metatileEnd = 0xFFFF; - uint16_t numMetatilesWide = 8; + uint16_t numMetatilesWide = projectConfig.metatileSelectorWidth; bool usePrimaryTileset = true; bool useSecondaryTileset = false; bool renderPlaceholders = false; diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index 8bc2ef35..edc3bf77 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -31,10 +31,12 @@ struct MetatileSelection class MetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(Metatile::pixelSize()) { + MetatileSelector(int numMetatilesWide, Layout *layout) + : SelectablePixmapItem(Metatile::pixelSize()), + numMetatilesWide(qMax(numMetatilesWide, 1)) + { this->externalSelection = false; this->prefabSelection = false; - this->numMetatilesWide = numMetatilesWide; this->layout = layout; this->selection = MetatileSelection{}; this->cellPos = QPoint(-1, -1); @@ -65,10 +67,10 @@ protected: void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; void drawSelection() override; private: + const int numMetatilesWide; QPixmap basePixmap; bool externalSelection; bool prefabSelection; - int numMetatilesWide; Layout *layout; int externalSelectionWidth; int externalSelectionHeight; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 47a70e4b..6ca9088f 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -9,7 +9,7 @@ class Layout; class TilesetEditorMetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout); + TilesetEditorMetatileSelector(int numMetatilesWide, Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout); Layout *layout = nullptr; void draw() override; @@ -36,13 +36,12 @@ protected: void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; private: + const int numMetatilesWide; QImage baseImage; QPixmap basePixmap; Tileset *primaryTileset = nullptr; Tileset *secondaryTileset = nullptr; uint16_t selectedMetatileId; - int numMetatilesWide; - int numMetatilesHigh; void updateBasePixmap(); uint16_t posToMetatileId(int x, int y, bool *ok = nullptr) const; uint16_t posToMetatileId(const QPoint &pos, bool *ok = nullptr) const; diff --git a/src/config.cpp b/src/config.cpp index 5810828d..d5c5b5c9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1007,6 +1007,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255); } else if (key == "forced_major_version") { this->forcedMajorVersion = getConfigInteger(key, value); + } else if (key == "metatile_selector_width") { + this->metatileSelectorWidth = getConfigInteger(key, value, 1, INT_MAX, 8); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->filepath()).arg(key)); } @@ -1116,6 +1118,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("warp_behaviors", warpBehaviorStrs.join(",")); map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup)); map.insert("forced_major_version", QString::number(this->forcedMajorVersion)); + map.insert("metatile_selector_width", QString::number(this->metatileSelectorWidth)); return map; } diff --git a/src/editor.cpp b/src/editor.cpp index 5f4b3f62..5f1dfe0f 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1646,7 +1646,7 @@ void Editor::displayMetatileSelector() { scene_metatiles = new QGraphicsScene; if (!metatile_selector_item) { - metatile_selector_item = new MetatileSelector(8, this->layout); + metatile_selector_item = new MetatileSelector(projectConfig.metatileSelectorWidth, this->layout); connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionChanged, this, &Editor::onHoveredMetatileSelectionChanged); connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionCleared, diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index f2e9b26c..ce233066 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -191,7 +191,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, const QSize &metatileSize, bool useTruePalettes) { - if (metatileIdEnd < metatileIdStart || numMetatilesWide == 0) + if (metatileIdEnd < metatileIdStart || numMetatilesWide <= 0) return QImage(); int numMetatilesToDraw = metatileIdEnd - metatileIdStart + 1; diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 4fe8060a..38cc50be 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -213,7 +213,7 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const { // then the metatiles we used to round the primary tileset would have the index of valid secondary metatiles. // These need to be ignored, or they'll appear to be duplicates of the subseqeunt secondary metatiles. int numPrimaryRounded = numPrimaryMetatilesRounded(); - int firstSecondaryRow = numPrimaryRounded / qMax(this->numMetatilesWide, 1); + int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide; metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded); if (secondaryTileset() && secondaryTileset()->contains(metatileId) && y >= firstSecondaryRow) { return metatileId; @@ -224,11 +224,6 @@ uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const { } QPoint MetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const { - if (this->numMetatilesWide == 0) { - if (ok) *ok = false; - return QPoint(0,0); - } - if (primaryTileset() && primaryTileset()->contains(metatileId)) { if (ok) *ok = true; int index = metatileId; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 07a503ec..dadaefd7 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -161,6 +161,7 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_PlayerViewDistance_North->setMaximum(INT_MAX); ui->spinBox_PlayerViewDistance_East->setMaximum(INT_MAX); ui->spinBox_PlayerViewDistance_South->setMaximum(INT_MAX); + ui->spinBox_MetatileSelectorWidth->setMaximum(maxMetatileId + 1); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -503,6 +504,7 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_PlayerViewDistance_North->setValue(projectConfig.playerViewDistance.top()); ui->spinBox_PlayerViewDistance_East->setValue(projectConfig.playerViewDistance.right()); ui->spinBox_PlayerViewDistance_South->setValue(projectConfig.playerViewDistance.bottom()); + ui->spinBox_MetatileSelectorWidth->setValue(projectConfig.metatileSelectorWidth); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -598,6 +600,7 @@ void ProjectSettingsEditor::save() { ui->spinBox_PlayerViewDistance_North->value(), ui->spinBox_PlayerViewDistance_East->value(), ui->spinBox_PlayerViewDistance_South->value()); + projectConfig.metatileSelectorWidth = ui->spinBox_MetatileSelectorWidth->value(); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 279007b9..4df24065 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -215,7 +215,7 @@ void TilesetEditor::setRawAttributesVisible(bool visible) { void TilesetEditor::initMetatileSelector() { - this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout); + 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, diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 6c9cc74c..bba51e77 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -6,11 +6,11 @@ // TODO: This class has a decent bit of overlap with the MetatileSelector class. // They should be refactored to inherit from a single parent class. -TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) - : SelectablePixmapItem(32, 32, 1, 1) { +TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(int numMetatilesWide, Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) + : SelectablePixmapItem(32, 32, 1, 1), + numMetatilesWide(qMax(numMetatilesWide, 1)) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; - this->numMetatilesWide = 8; this->layout = layout; setAcceptHoverEvents(true); this->usedMetatiles.resize(Project::getNumMetatilesTotal()); @@ -169,7 +169,7 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok) // then the metatiles we used to round the primary tileset would have the index of valid secondary metatiles. // These need to be ignored, or they'll appear to be duplicates of the subseqeunt secondary metatiles. int numPrimaryRounded = numPrimaryMetatilesRounded(); - int firstSecondaryRow = numPrimaryRounded / qMax(this->numMetatilesWide, 1); + int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide; metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded); if (this->secondaryTileset && this->secondaryTileset->contains(metatileId) && y >= firstSecondaryRow) { return metatileId; @@ -180,11 +180,6 @@ uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok) } QPoint TilesetEditorMetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const { - if (this->numMetatilesWide == 0) { - if (ok) *ok = false; - return QPoint(0,0); - } - if (this->primaryTileset && this->primaryTileset->contains(metatileId)) { if (ok) *ok = true; int index = metatileId; From d3d30ae0f22608cfced60efd024316540d4c20df Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 25 Jul 2025 13:33:11 -0400 Subject: [PATCH 33/71] Update changelog --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cfad804..2e6dc886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,22 +6,35 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ## [Unreleased] ### Added +- Add an `Export Metatiles Image` option to the Tileset Editor that provides many more options for customizing metatile images. +- Add an `Export Porytiles Layer Images` option to the Tileset Editor, which is a shortcut for individually exporting layer images that Porytiles can use. - Add an option under `Preferences` to include common scripts in the autocomplete for Script labels. +- Add a setting under `Project Settings` to change the width of the metatile selectors. +- Add versions of the API functions `[get|set]MetatileLayerOrder` and `[get|set]MetatileLayerOpacity` that work globally, rather than on individual layouts. - A link to Porymap's manual is now available under `Help`. ### Changed +- The Player View Rectangle is now visible on the Events tab, as is the Cursor Tile Outline for certain tools. +- When hovering over tiles in the Tileset Editor their palette and x/yflip are now listed alongside the tile ID. - The scroll position of the map view now remains the same between the Connections tab and the Map/Events tabs. - The Move tool now behaves more like a traditional pan tool (with no momentum). -- The Player View Rectangle is now visible on the Events tab, as is the Cursor Tile Outline for certain tools. +- The map image exporter now uses a checkered background to indicate transparency. ### Fixed +- Fix crash when rendering tiles with invalid palette numbers. +- Fix crash when opening the Tileset Editor for tilesets with no metatiles. - Fix metatile images exporting at 2x scale. +- Fix display errors when a project's metatile limits are not divisible by 8. +- Fix incorrect dividing line position for primary tiles images that are smaller than the maximum size. +- Fix the checkered background of the `Change Dimensions` popup shifting while scrolling around. - Fix pasting Wild Pokémon data then changing maps resetting the pasted data. - Fix click-drag map selections behaving unexpectedly when the cursor is outside the map grid. - Fix events being dragged in negative coordinates lagging behind the cursor. - Fix the shortcut for duplicating events working while on the Connections tab. +- Fix Undo/Redo ignoring the automatic resizing that occurs if a layout/border was an unexpected size. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. - Fix warning not appearing when the log file exceeds maximum size. +- Fix possible lag while using the Tileset Editor's tile selector. - Fix unnecessary resources being used to watch files. ## [6.1.0] - 2025-06-09 From 2f2f71948a5077b0c996a5bdcadd1f10ac089814 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 24 Jul 2025 21:26:12 -0400 Subject: [PATCH 34/71] Add setting to show unused palette colors --- forms/paletteeditor.ui | 22 +++++++ include/config.h | 1 + include/core/events.h | 4 +- include/core/tile.h | 1 + include/core/tileset.h | 6 ++ include/project.h | 1 + include/ui/imageproviders.h | 2 +- include/ui/paletteeditor.h | 14 ++++- src/config.cpp | 4 ++ src/core/events.cpp | 9 +++ src/core/tileset.cpp | 65 ++++++++++++++++--- src/project.cpp | 23 +++++-- src/scriptapi/apimap.cpp | 17 ++--- src/ui/imageproviders.cpp | 6 +- src/ui/paletteeditor.cpp | 122 +++++++++++++++++++++++++++++------- 15 files changed, 243 insertions(+), 54 deletions(-) diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui index c98d49b6..cbbd7cd2 100644 --- a/forms/paletteeditor.ui +++ b/forms/paletteeditor.ui @@ -38,6 +38,13 @@ + + + + (All colors used) + + + @@ -133,8 +140,15 @@ + + + View + + + + @@ -164,6 +178,14 @@ Import Palette + + + true + + + Show Unused Colors + + diff --git a/include/config.h b/include/config.h index e929d64c..25482457 100644 --- a/include/config.h +++ b/include/config.h @@ -116,6 +116,7 @@ public: bool showTilesetEditorLayerGrid; bool showTilesetEditorDivider; bool showTilesetEditorRawAttributes; + bool showPaletteEditorUnusedColors; bool monitorFiles; bool tilesetCheckerboardFill; bool newMapHeaderSectionExpanded; diff --git a/include/core/events.h b/include/core/events.h index 76a58dcf..0e8af1f7 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -122,8 +122,8 @@ public: int getZ() const { return this->elevation; } int getElevation() const { return this->elevation; } - int getPixelX() const { return (this->x * 16) - qMax(0, (pixmap.width() - 16) / 2); } - int getPixelY() const { return (this->y * 16) - qMax(0, pixmap.height() - 16); } + int getPixelX() const; + int getPixelY() const; virtual EventFrame *getEventFrame(); virtual EventFrame *createEventFrame() = 0; diff --git a/include/core/tile.h b/include/core/tile.h index a13e9fc7..11fd6a99 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -29,6 +29,7 @@ public: static constexpr int pixelWidth() { return 8; } static constexpr int pixelHeight() { return 8; } static constexpr QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); } + static constexpr int numPixels() { return Tile::pixelWidth() * Tile::pixelHeight(); } static constexpr int sizeInBytes() { return sizeof(uint16_t); } }; diff --git a/include/core/tileset.h b/include/core/tileset.h index be5bdace..aea1146e 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -39,6 +39,7 @@ public: static QString stripPrefix(const QString &fullName); static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); + static const Tileset* getTileTileset(int, const Tileset*, const Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); static Tileset* getMetatileLabelTileset(int, Tileset*, Tileset*); static QString getMetatileLabel(int, Tileset *, Tileset *); @@ -92,6 +93,11 @@ public: QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); } + QSet getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet &searchColors = {}) const; + + static constexpr int maxPalettes() { return 16; } + static constexpr int numColorsPerPalette() { return 16; } + private: QList m_metatiles; diff --git a/include/project.h b/include/project.h index dd66c2e5..b34ce26a 100644 --- a/include/project.h +++ b/include/project.h @@ -109,6 +109,7 @@ public: QStringList primaryTilesetLabels; QStringList secondaryTilesetLabels; QStringList tilesetLabelsOrdered; + QSet getPairedTilesetLabels(Tileset *tileset) const; bool readMapGroups(); void addNewMapGroup(const QString &groupName); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index 17b823ce..032a58b6 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -35,7 +35,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, bool useTruePalettes = false); -QImage getTileImage(uint16_t, Tileset*, Tileset*); +QImage getTileImage(uint16_t, const Tileset*, const Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette); QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset); diff --git a/include/ui/paletteeditor.h b/include/ui/paletteeditor.h index 63581eb1..0ab3eda3 100644 --- a/include/ui/paletteeditor.h +++ b/include/ui/paletteeditor.h @@ -24,9 +24,14 @@ class PaletteEditor : public QMainWindow { public: explicit PaletteEditor(Project*, Tileset*, Tileset*, int paletteId, QWidget *parent = nullptr); ~PaletteEditor(); + void setPaletteId(int); + int currentPaletteId() const; + void setTilesets(Tileset*, Tileset*); + bool showingUnusedColors() const; + private: Ui::PaletteEditor *ui; Project *project = nullptr; @@ -36,13 +41,18 @@ private: Tileset *secondaryTileset; QList> palettesHistory; + QMap> unusedColorCache; - Tileset* getTileset(int paletteId); + Tileset* getTileset(int paletteId) const; void refreshColorInputs(); void commitEditHistory(); void commitEditHistory(int paletteId); void restoreWindowState(); + void onWindowActivated(); + void invalidateCache(); void closeEvent(QCloseEvent*); + void setColorInputTitles(bool show); + QSet getUnusedColorIds() const; void setRgb(int index, QRgb rgb); void setPalette(int paletteId, const QList &palette); @@ -50,7 +60,7 @@ private: void setBitDepth(int bits); int bitDepth = 24; - static const int numColors = 16; + static const int numColors = Tileset::numColorsPerPalette(); signals: void closed(); diff --git a/src/config.cpp b/src/config.cpp index d5c5b5c9..b53f171b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -346,6 +346,7 @@ void PorymapConfig::reset() { this->showTilesetEditorLayerGrid = true; this->showTilesetEditorDivider = false; this->showTilesetEditorRawAttributes = false; + this->showPaletteEditorUnusedColors = false; this->monitorFiles = true; this->tilesetCheckerboardFill = true; this->newMapHeaderSectionExpanded = false; @@ -469,6 +470,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->showTilesetEditorDivider = getConfigBool(key, value); } else if (key == "show_tileset_editor_raw_attributes") { this->showTilesetEditorRawAttributes = getConfigBool(key, value); + } else if (key == "show_palette_editor_unused_colors") { + this->showPaletteEditorUnusedColors = getConfigBool(key, value); } else if (key == "monitor_files") { this->monitorFiles = getConfigBool(key, value); } else if (key == "tileset_checkerboard_fill") { @@ -609,6 +612,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0"); map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0"); map.insert("show_tileset_editor_raw_attributes", this->showTilesetEditorRawAttributes ? "1" : "0"); + map.insert("show_palette_editor_unused_colors", this->showPaletteEditorUnusedColors ? "1" : "0"); map.insert("monitor_files", this->monitorFiles ? "1" : "0"); map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0"); map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0"); diff --git a/src/core/events.cpp b/src/core/events.cpp index a4ab33a9..58a2b997 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -3,6 +3,7 @@ #include "eventframes.h" #include "project.h" #include "config.h" +#include "metatile.h" Event* Event::create(Event::Type type) { switch (type) { @@ -23,6 +24,14 @@ Event::~Event() { delete this->eventFrame; } +int Event::getPixelX() const { + return (this->x * Metatile::pixelWidth()) - qMax(0, (this->pixmap.width() - Metatile::pixelWidth()) / 2); +} + +int Event::getPixelY() const { + return (this->y * Metatile::pixelHeight()) - qMax(0, this->pixmap.height() - Metatile::pixelHeight()); +} + EventFrame *Event::getEventFrame() { if (!this->eventFrame) createEventFrame(); return this->eventFrame; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 5f7ac20b..f5b7f440 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -111,6 +111,11 @@ int Tileset::maxTiles() const { } Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { + return const_cast(getTileTileset(tileId, static_cast(primaryTileset), static_cast(secondaryTileset))); +} + +// Get the tileset *expected* to contain the given 'tileId'. Note that this does not mean the tile actually exists in that tileset. +const Tileset* Tileset::getTileTileset(int tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { if (tileId < Project::getNumTilesPrimary()) { return primaryTileset; } else if (tileId < Project::getNumTilesTotal()) { @@ -120,6 +125,7 @@ Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *s } } +// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset. Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { if (metatileId < Project::getNumMetatilesPrimary()) { return primaryTileset; @@ -334,7 +340,7 @@ bool Tileset::appendToGraphics(const QString &filepath, const QString &friendlyN dataString.append(QString("\t.incbin \"%1\"\n").arg(tilesPath)); } else { // Append to C file - dataString.append(QString("const u16 gTilesetPalettes_%1[][16] =\n{\n").arg(friendlyName)); + dataString.append(QString("const u16 gTilesetPalettes_%1[][%2] =\n{\n").arg(friendlyName).arg(Tileset::numColorsPerPalette())); for (int i = 0; i < Project::getNumPalettesTotal(); i++) dataString.append(QString(" INCBIN_U16(\"%1%2%3\"),\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0')).arg(palettesExt)); dataString.append("};\n"); @@ -546,13 +552,13 @@ bool Tileset::loadTilesImage(QImage *importedImage) { return false; } - // Validate image contains 16 colors. + // Validate the number of colors in the image. int colorCount = image.colorCount(); - if (colorCount > 16) { + if (colorCount > Tileset::numColorsPerPalette()) { flattenTo4bppImage(&image); - } else if (colorCount < 16) { + } else if (colorCount < Tileset::numColorsPerPalette()) { QVector colorTable = image.colorTable(); - for (int i = colorTable.length(); i < 16; i++) { + for (int i = colorTable.length(); i < Tileset::numColorsPerPalette(); i++) { colorTable.append(0); } image.setColorTable(colorTable); @@ -616,8 +622,9 @@ bool Tileset::loadPalettes() { // Either the palette failed to load, or no palette exists. // We expect tilesets to have a certain number of palettes, // so fill this palette with dummy colors. - for (int j = 0; j < 16; j++) { - palette.append(qRgb(j * 16, j * 16, j * 16)); + for (int j = 0; j < Tileset::numColorsPerPalette(); j++) { + int colorComponent = j * (256 / Tileset::numColorsPerPalette()); + palette.append(qRgb(colorComponent, colorComponent, colorComponent)); } } this->palettes.append(palette); @@ -630,7 +637,7 @@ bool Tileset::savePalettes() { bool success = true; int numPalettes = qMin(this->palettePaths.length(), this->palettes.length()); for (int i = 0; i < numPalettes; i++) { - if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16)) + if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, Tileset::numColorsPerPalette())) success = false; } return success; @@ -658,3 +665,45 @@ bool Tileset::save() { QString Tileset::stripPrefix(const QString &fullName) { return QString(fullName).replace(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix), ""); } + +// Find which of the specified color IDs in 'searchColors' are not used by any of this tileset's metatiles. +// The 'pairedTileset' may be used to get the tile images for any tiles that don't belong to this tileset. +// If 'searchColors' is empty, it will for search for all unused colors. +QSet Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet &searchColors) const { + QSet unusedColors = searchColors; + if (unusedColors.isEmpty()) { + // Search for all colors + for (int i = 0; i < Tileset::numColorsPerPalette(); i++) { + unusedColors.insert(i); + } + } + const Tileset *primaryTileset = this->is_secondary ? pairedTileset : this; + const Tileset *secondaryTileset = this->is_secondary ? this : pairedTileset; + QSet seenTileIds; + for (const auto &metatile : m_metatiles) + for (const auto &tile : metatile->tiles) { + if (tile.palette != paletteId) + continue; + + // Save time by ignoring tiles we've already inspected. + if (seenTileIds.contains(tile.tileId)) + continue; + seenTileIds.insert(tile.tileId); + + QImage image = getTileImage(tile.tileId, primaryTileset, secondaryTileset); + if (image.isNull() || image.sizeInBytes() < Tile::numPixels()) + continue; + + const uchar * pixels = image.constBits(); + for (int i = 0; i < Tile::numPixels(); i++) { + auto it = unusedColors.constFind(pixels[i]); + if (it != unusedColors.constEnd()) { + unusedColors.erase(it); + if (unusedColors.isEmpty()) { + return {}; + } + } + } + } + return unusedColors; +} diff --git a/src/project.cpp b/src/project.cpp index b76821f3..19d99eb8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1517,7 +1517,7 @@ void Project::readTilesetPaths(Tileset* tileset) { tileset->metatile_attrs_path = defaultPath + "/metatile_attributes.bin"; if (tileset->palettePaths.isEmpty()) { QString palettes_dir_path = defaultPath + "/palettes/"; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < Tileset::maxPalettes(); i++) { tileset->palettePaths.append(palettes_dir_path + QString("%1").arg(i, 2, 10, QLatin1Char('0')) + ".pal"); } } @@ -1579,9 +1579,9 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa } // Create default palettes - for(int i = 0; i < 16; ++i) { + for(int i = 0; i < Tileset::maxPalettes(); ++i) { QList currentPal; - for(int i = 0; i < 16;++i) { + for(int i = 0; i < Tileset::numColorsPerPalette();++i) { currentPal.append(qRgb(0,0,0)); } tileset->palettes.append(currentPal); @@ -2347,7 +2347,7 @@ bool Project::readFieldmapProperties() { logWarn(QString("Value for '%1' not found. Using default (%2) instead.").arg(name).arg(*dest)); } }; - loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter. + loadDefine(numPalsTotalName, &Project::num_pals_total, 2, Tileset::maxPalettes()); loadDefine(numTilesTotalName, &Project::num_tiles_total, 2, 1024); // 1024 is fixed because we store tile IDs in a 10-bit field. loadDefine(numPalsPrimaryName, &Project::num_pals_primary, 1, Project::num_pals_total - 1); loadDefine(numTilesPrimaryName, &Project::num_tiles_primary, 1, Project::num_tiles_total - 1); @@ -3518,3 +3518,18 @@ bool Project::hasUnsavedChanges() { } return false; } + +// Searches the project's map layouts to find the names of the tilesets that the provided tileset gets paired with. +QSet Project::getPairedTilesetLabels(Tileset *tileset) const { + QSet pairedLabels; + for (const auto &layout : this->mapLayouts) { + if (tileset->is_secondary) { + if (layout->tileset_secondary_label == tileset->name) { + pairedLabels.insert(layout->tileset_primary_label); + } + } else if (layout->tileset_primary_label == tileset->name) { + pairedLabels.insert(layout->tileset_secondary_label); + } + } + return pairedLabels; +} diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index a1730791..472dd23a 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -346,10 +346,7 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList= tileset->palettes.size()) return; - if (colors.size() != 16) - return; - - for (int i = 0; i < 16; i++) { + for (int i = 0; i < qMin(colors.length(), Tileset::numColorsPerPalette()); i++) { if (colors[i].size() != 3) continue; tileset->palettes[paletteIndex][i] = qRgb(colors[i][0], colors[i][1], colors[i][2]); @@ -457,10 +454,7 @@ void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QL return; if (paletteIndex >= tileset->palettePreviews.size()) return; - if (colors.size() != 16) - return; - - for (int i = 0; i < 16; i++) { + for (int i = 0; i < qMin(colors.length(), Tileset::numColorsPerPalette()); i++) { if (colors[i].size() != 3) continue; tileset->palettePreviews[paletteIndex][i] = qRgb(colors[i][0], colors[i][1], colors[i][2]); @@ -798,14 +792,13 @@ QJSValue MainWindow::getTilePixels(int tileId) { if (tileId < 0 || !this->editor || !this->editor->layout) return QJSValue(); - const int numPixels = Tile::pixelWidth() * Tile::pixelHeight(); QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); - if (tileImage.isNull() || tileImage.sizeInBytes() < numPixels) + if (tileImage.isNull() || tileImage.sizeInBytes() < Tile::numPixels()) return QJSValue(); const uchar * pixels = tileImage.constBits(); - QJSValue pixelArray = Scripting::getEngine()->newArray(numPixels); - for (int i = 0; i < numPixels; i++) { + QJSValue pixelArray = Scripting::getEngine()->newArray(Tile::numPixels()); + for (int i = 0; i < Tile::numPixels(); i++) { pixelArray.setProperty(i, pixels[i]); } return pixelArray; diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index ce233066..2d4fee51 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -141,8 +141,8 @@ QImage getMetatileImage( return metatileImage; } -QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { - Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset); +QImage getTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { + const Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset); return tileset ? tileset->tileImage(tileId) : QImage(); } @@ -155,7 +155,7 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se tileImage = QImage(Tile::pixelSize(), QImage::Format_RGBA8888); tileImage.fill(getInvalidImageColor()); } else { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < Tileset::numColorsPerPalette(); i++) { tileImage.setColor(i, palette.value(i, getInvalidImageColor().rgb())); } } diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp index 1078ecc6..035e4397 100644 --- a/src/ui/paletteeditor.cpp +++ b/src/ui/paletteeditor.cpp @@ -5,6 +5,7 @@ #include "log.h" #include "filedialog.h" #include "message.h" +#include "eventfilters.h" PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) : @@ -21,7 +22,7 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset this->colorInputs.clear(); const int numColorsPerRow = 4; for (int i = 0; i < this->numColors; i++) { - auto colorInput = new ColorInputWidget(QString("Color %1").arg(i)); + auto colorInput = new ColorInputWidget; connect(colorInput, &ColorInputWidget::colorChanged, [this, i](QRgb color) { setRgb(i, color); }); connect(colorInput, &ColorInputWidget::editingFinished, [this] { commitEditHistory(); }); this->colorInputs.append(colorInput); @@ -45,17 +46,37 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(15); }); connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(24); }); + this->ui->actionShow_Unused_Colors->setChecked(porymapConfig.showPaletteEditorUnusedColors); + connect(this->ui->actionShow_Unused_Colors, &QAction::toggled, this, &PaletteEditor::setColorInputTitles); + + ActiveWindowFilter *filter = new ActiveWindowFilter(this); + connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::onWindowActivated); + this->installEventFilter(filter); + this->setPaletteId(paletteId); this->commitEditHistory(); this->restoreWindowState(); } -PaletteEditor::~PaletteEditor() -{ +PaletteEditor::~PaletteEditor() { delete ui; } -Tileset* PaletteEditor::getTileset(int paletteId) { +void PaletteEditor::onWindowActivated() { + // Rather than try to keep track of metatile/tile changes that affect which colors are used, + // we'll just refresh when the window is activated. + invalidateCache(); +} + +int PaletteEditor::currentPaletteId() const { + return ui->spinBox_PaletteId->value(); +} + +bool PaletteEditor::showingUnusedColors() const { + return ui->actionShow_Unused_Colors->isChecked(); +} + +Tileset* PaletteEditor::getTileset(int paletteId) const { return (paletteId < Project::getNumPalettesPrimary()) ? this->primaryTileset : this->secondaryTileset; @@ -70,8 +91,7 @@ void PaletteEditor::setBitDepth(int bits) { } void PaletteEditor::setRgb(int colorIndex, QRgb rgb) { - const int paletteId = this->ui->spinBox_PaletteId->value(); - + const int paletteId = currentPaletteId(); Tileset *tileset = getTileset(paletteId); tileset->palettes[paletteId][colorIndex] = rgb; tileset->palettePreviews[paletteId][colorIndex] = rgb; @@ -82,21 +102,22 @@ void PaletteEditor::setRgb(int colorIndex, QRgb rgb) { void PaletteEditor::setPalette(int paletteId, const QList &palette) { Tileset *tileset = getTileset(paletteId); for (int i = 0; i < this->numColors; i++) { - tileset->palettes[paletteId][i] = palette.at(i); - tileset->palettePreviews[paletteId][i] = palette.at(i); + tileset->palettes[paletteId][i] = palette.value(i); + tileset->palettePreviews[paletteId][i] = palette.value(i); } refreshColorInputs(); emit changedPaletteColor(); } void PaletteEditor::refreshColorInputs() { - const int paletteId = ui->spinBox_PaletteId->value(); + const int paletteId = currentPaletteId(); Tileset *tileset = getTileset(paletteId); - for (int i = 0; i < this->numColors; i++) { + for (int i = 0; i < this->colorInputs.length(); i++) { auto colorInput = this->colorInputs.at(i); const QSignalBlocker b(colorInput); - colorInput->setColor(tileset->palettes.at(paletteId).at(i)); + colorInput->setColor(tileset->palettes.value(paletteId).value(i)); } + setColorInputTitles(showingUnusedColors()); } void PaletteEditor::setPaletteId(int paletteId) { @@ -108,6 +129,7 @@ void PaletteEditor::setPaletteId(int paletteId) { void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; + this->invalidateCache(); this->refreshColorInputs(); } @@ -120,12 +142,12 @@ void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) { } void PaletteEditor::commitEditHistory() { - commitEditHistory(ui->spinBox_PaletteId->value()); + commitEditHistory(currentPaletteId()); } void PaletteEditor::commitEditHistory(int paletteId) { QList colors; - for (int i = 0; i < this->numColors; i++) { + for (int i = 0; i < this->colorInputs.length(); i++) { colors.append(this->colorInputs.at(i)->color()); } PaletteHistoryItem *commit = new PaletteHistoryItem(colors); @@ -139,24 +161,21 @@ void PaletteEditor::restoreWindowState() { this->restoreState(geometry.value("palette_editor_state")); } -void PaletteEditor::on_actionUndo_triggered() -{ - int paletteId = this->ui->spinBox_PaletteId->value(); +void PaletteEditor::on_actionUndo_triggered() { + int paletteId = currentPaletteId(); PaletteHistoryItem *prev = this->palettesHistory[paletteId].back(); if (prev) setPalette(paletteId, prev->colors); } -void PaletteEditor::on_actionRedo_triggered() -{ - int paletteId = this->ui->spinBox_PaletteId->value(); +void PaletteEditor::on_actionRedo_triggered() { + int paletteId = currentPaletteId(); PaletteHistoryItem *next = this->palettesHistory[paletteId].next(); if (next) setPalette(paletteId, next->colors); } -void PaletteEditor::on_actionImport_Palette_triggered() -{ +void PaletteEditor::on_actionImport_Palette_triggered() { QString filepath = FileDialog::getOpenFileName(this, "Import Tileset Palette", "", "Palette Files (*.pal *.act *tpl *gpl)"); if (filepath.isEmpty()) { return; @@ -171,11 +190,70 @@ void PaletteEditor::on_actionImport_Palette_triggered() palette.append(0); } - const int paletteId = ui->spinBox_PaletteId->value(); + const int paletteId = currentPaletteId(); setPalette(paletteId, palette); commitEditHistory(paletteId); } +void PaletteEditor::invalidateCache() { + this->unusedColorCache.clear(); + if (showingUnusedColors()) { + setColorInputTitles(true); + } +} + +QSet PaletteEditor::getUnusedColorIds() const { + const int paletteId = currentPaletteId(); + + if (this->unusedColorCache.contains(paletteId)) { + return this->unusedColorCache.value(paletteId); + } + this->unusedColorCache[paletteId] = {}; + + // Check our current tilesets for color usage. + QSet unusedColorIds = this->primaryTileset->getUnusedColorIds(paletteId, this->secondaryTileset); + if (unusedColorIds.isEmpty()) + return {}; + unusedColorIds = this->secondaryTileset->getUnusedColorIds(paletteId, this->primaryTileset, unusedColorIds); + if (unusedColorIds.isEmpty()) + return {}; + + // The current palette comes from either the primary or secondary tileset. + // We need to check all the other tilesets that are paired with the tileset that owns this palette. + Tileset *paletteTileset = getTileset(paletteId); + QSet tilesetsToSearch = this->project->getPairedTilesetLabels(paletteTileset); + + // We exclude the currently-loaded pair (we already checked them, and because they're being + // edited in the Tileset Editor they may differ from their copies saved in the layout). + tilesetsToSearch.remove(this->primaryTileset->name); + tilesetsToSearch.remove(this->secondaryTileset->name); + + for (const auto &label : tilesetsToSearch) { + Tileset *searchTileset = this->project->getTileset(label); + if (!searchTileset) continue; + unusedColorIds = searchTileset->getUnusedColorIds(paletteId, paletteTileset, unusedColorIds); + if (unusedColorIds.isEmpty()) + return {}; + } + + this->unusedColorCache[paletteId] = unusedColorIds; + return unusedColorIds; +} + +void PaletteEditor::setColorInputTitles(bool showUnused) { + porymapConfig.showPaletteEditorUnusedColors = showUnused; + + QSet unusedColorIds = showUnused ? getUnusedColorIds() : QSet(); + ui->label_AllColorsUsed->setVisible(showUnused && unusedColorIds.isEmpty()); + for (int i = 0; i < this->colorInputs.length(); i++) { + QString title = QString("Color %1").arg(i); + if (unusedColorIds.contains(i)) { + title.append(QStringLiteral(" (Unused)")); + } + this->colorInputs.at(i)->setTitle(title); + } +} + void PaletteEditor::closeEvent(QCloseEvent*) { porymapConfig.setPaletteEditorGeometry( this->saveGeometry(), From 6eaeee5f57962872b932dc82d1d7a6eb54263d6b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 25 Jul 2025 13:21:46 -0400 Subject: [PATCH 35/71] Add color search feature --- forms/palettecolorsearch.ui | 152 ++++++++++++++++++++++ forms/paletteeditor.ui | 32 ++++- include/core/metatile.h | 2 +- include/core/tileset.h | 13 +- include/core/utility.h | 1 + include/editor.h | 2 - include/mainwindow.h | 1 - include/project.h | 6 +- include/ui/imageproviders.h | 24 ++-- include/ui/numericsorttableitem.h | 20 +++ include/ui/palettecolorsearch.h | 67 ++++++++++ include/ui/paletteeditor.h | 17 ++- include/ui/tileseteditor.h | 17 +-- include/ui/wildmonsearch.h | 17 +-- porymap.pro | 3 + src/core/metatile.cpp | 2 +- src/core/tileset.cpp | 77 ++++++++++- src/core/utility.cpp | 13 ++ src/editor.cpp | 6 - src/mainwindow.cpp | 41 ++---- src/project.cpp | 59 +++++---- src/ui/imageproviders.cpp | 32 ++--- src/ui/palettecolorsearch.cpp | 207 ++++++++++++++++++++++++++++++ src/ui/paletteeditor.cpp | 96 ++++++++------ src/ui/tileseteditor.cpp | 111 ++++++---------- src/ui/wildmonsearch.cpp | 7 +- 26 files changed, 776 insertions(+), 249 deletions(-) create mode 100644 forms/palettecolorsearch.ui create mode 100644 include/ui/numericsorttableitem.h create mode 100644 include/ui/palettecolorsearch.h create mode 100644 src/ui/palettecolorsearch.cpp diff --git a/forms/palettecolorsearch.ui b/forms/palettecolorsearch.ui new file mode 100644 index 00000000..b6ae87c8 --- /dev/null +++ b/forms/palettecolorsearch.ui @@ -0,0 +1,152 @@ + + + PaletteColorSearch + + + + 0 + 0 + 547 + 329 + + + + Palette Color Search + + + + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::Shadow::Raised + + + + + + + + 0 + 0 + + + + Color + + + + + + + 1 + + + + + + + + 0 + 0 + + + + Palette + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + 2 + + + + + + + + + QDialogButtonBox::StandardButton::Close + + + + + + + + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui index cbbd7cd2..ae394040 100644 --- a/forms/paletteeditor.ui +++ b/forms/paletteeditor.ui @@ -38,6 +38,20 @@
+ + + + <html><head/><body><p>Opens a search dialog to find which tilesets/metatiles are using certain colors.</p></body></html> + + + ... + + + + :/icons/magnifier.ico:/icons/magnifier.ico + + + @@ -96,7 +110,7 @@ 0 0 883 - 784 + 779 @@ -146,9 +160,16 @@ + + + Tools + + + + @@ -186,7 +207,14 @@ Show Unused Colors + + + Find Color Usage... + + - + + + diff --git a/include/core/metatile.h b/include/core/metatile.h index e35e1fa3..eba4afed 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -61,7 +61,7 @@ public: static int getDefaultAttributesSize(BaseGameVersion version); static void setLayout(Project*); static QString getMetatileIdString(uint16_t metatileId); - static QString getMetatileIdStrings(const QList metatileIds); + static QString getMetatileIdStrings(const QList &metatileIds); static QString getLayerName(int layerNum); static constexpr int tileWidth() { return 2; } diff --git a/include/core/tileset.h b/include/core/tileset.h index aea1146e..3d137056 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -37,10 +37,14 @@ public: QList> palettePreviews; static QString stripPrefix(const QString &fullName); + static Tileset* getPaletteTileset(int, Tileset*, Tileset*); + static const Tileset* getPaletteTileset(int, const Tileset*, const Tileset*); static Tileset* getMetatileTileset(int, Tileset*, Tileset*); + static const Tileset* getMetatileTileset(int, const Tileset*, const Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static const Tileset* getTileTileset(int, const Tileset*, const Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); + static const Metatile* getMetatile(int, const Tileset*, const Tileset*); static Tileset* getMetatileLabelTileset(int, Tileset*, Tileset*); static QString getMetatileLabel(int, Tileset *, Tileset *); static QString getOwnedMetatileLabel(int, Tileset *, Tileset *); @@ -48,9 +52,9 @@ public: static bool setMetatileLabel(int, QString, Tileset *, Tileset *); QString getMetatileLabelPrefix(); static QString getMetatileLabelPrefix(const QString &name); - static QList> getBlockPalettes(Tileset*, Tileset*, bool useTruePalettes = false); - static QList getPalette(int, Tileset*, Tileset*, bool useTruePalettes = false); - static bool metatileIsValid(uint16_t metatileId, Tileset *, Tileset *); + static QList> getBlockPalettes(const Tileset*, const Tileset*, bool useTruePalettes = false); + static QList getPalette(int, const Tileset*, const Tileset*, bool useTruePalettes = false); + static bool metatileIsValid(uint16_t metatileId, const Tileset*, const Tileset*); static QHash getHeaderMemberMap(bool usingAsm); static QString getExpectedDir(QString tilesetName, bool isSecondary); QString getExpectedDir(); @@ -93,7 +97,8 @@ public: QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); } - QSet getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet &searchColors = {}) const; + QSet getUnusedColorIds(int paletteId, const Tileset *pairedTileset, const QSet &searchColors = {}) const; + QList findMetatilesUsingColor(int paletteId, int colorId, const Tileset *pairedTileset) const; static constexpr int maxPalettes() { return 16; } static constexpr int numColorsPerPalette() { return 16; } diff --git a/include/core/utility.h b/include/core/utility.h index 41c1dcde..7270b552 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -16,6 +16,7 @@ namespace Util { QString replaceExtension(const QString &path, const QString &newExtension); void setErrorStylesheet(QLineEdit *lineEdit, bool isError); QString toStylesheetString(const QFont &font); + void show(QWidget *w); } #endif // UTILITY_H diff --git a/include/editor.h b/include/editor.h index fd9a809e..ffe628ac 100644 --- a/include/editor.h +++ b/include/editor.h @@ -67,8 +67,6 @@ public: bool setLayout(QString layoutName); void unsetMap(); - Tileset *getCurrentMapPrimaryTileset(); - bool displayMap(); bool displayLayout(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 813370b4..841cdcf6 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -385,7 +385,6 @@ private: void openDuplicateMapOrLayoutDialog(); void openNewMapGroupDialog(); void openNewLocationDialog(); - void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); void scrollMapListToCurrentLayout(MapTree *list); diff --git a/include/project.h b/include/project.h index b34ce26a..1c14f996 100644 --- a/include/project.h +++ b/include/project.h @@ -104,12 +104,11 @@ public: bool load(); QMap tilesetCache; - Tileset* loadTileset(QString, Tileset *tileset = nullptr); - Tileset* getTileset(QString, bool forceLoad = false); + Tileset* getTileset(const QString&, bool forceLoad = false); QStringList primaryTilesetLabels; QStringList secondaryTilesetLabels; QStringList tilesetLabelsOrdered; - QSet getPairedTilesetLabels(Tileset *tileset) const; + QSet getPairedTilesetLabels(const Tileset *tileset) const; bool readMapGroups(); void addNewMapGroup(const QString &groupName); @@ -343,6 +342,7 @@ private: void resetFileCache(); void resetFileWatcher(); void logFileWatchStatus(); + void cacheTileset(const QString &label, Tileset *tileset); bool saveMapLayouts(); bool saveMapGroups(); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index 032a58b6..f1edc04a 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -11,14 +11,14 @@ class Layout; QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); -QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false); -QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false); -QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false); -QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false); +QImage getMetatileImage(uint16_t, const Layout*, bool useTruePalettes = false); +QImage getMetatileImage(const Metatile*, const Layout*, bool useTruePalettes = false); +QImage getMetatileImage(uint16_t, const Tileset*, const Tileset*, const QList& = {0,1,2}, const QList& = {}, bool useTruePalettes = false); +QImage getMetatileImage(const Metatile*, const Tileset*, const Tileset*, const QList& = {0,1,2}, const QList& = {}, bool useTruePalettes = false); -QImage getMetatileSheetImage(Layout *layout, int numMetatilesWIde, bool useTruePalettes = false); -QImage getMetatileSheetImage(Tileset *primaryTileset, - Tileset *secondaryTileset, +QImage getMetatileSheetImage(const Layout *layout, int numMetatilesWIde, bool useTruePalettes = false); +QImage getMetatileSheetImage(const Tileset *primaryTileset, + const Tileset *secondaryTileset, uint16_t metatileIdStart, uint16_t metatileIdEnd, int numMetatilesWIde, @@ -26,8 +26,8 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, const QList &layerOpacity = {}, const QSize &metatileSize = Metatile::pixelSize(), bool useTruePalettes = false); -QImage getMetatileSheetImage(Tileset *primaryTileset, - Tileset *secondaryTileset, +QImage getMetatileSheetImage(const Tileset *primaryTileset, + const Tileset *secondaryTileset, int numMetatilesWide, const QList &layerOrder, const QList &layerOpacity = {}, @@ -36,9 +36,9 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, QImage getTileImage(uint16_t, const Tileset*, const Tileset*); -QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); -QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette); -QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset); +QImage getPalettedTileImage(uint16_t, const Tileset*, const Tileset*, int, bool useTruePalettes = false); +QImage getColoredTileImage(uint16_t tileId, const Tileset *, const Tileset *, const QList &palette); +QImage getGreyscaleTileImage(uint16_t tileId, const Tileset *, const Tileset *); void flattenTo4bppImage(QImage * image); diff --git a/include/ui/numericsorttableitem.h b/include/ui/numericsorttableitem.h new file mode 100644 index 00000000..e33f48cb --- /dev/null +++ b/include/ui/numericsorttableitem.h @@ -0,0 +1,20 @@ +#ifndef NUMERICSORTTABLEITEM_H +#define NUMERICSORTTABLEITEM_H + +#include +#include + +class NumericSortTableItem : public QTableWidgetItem +{ +public: + explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {}; + +protected: + virtual bool operator<(const QTableWidgetItem &other) const override { + QCollator collator; + collator.setNumericMode(true); + return collator.compare(text(), other.text()) < 0; + } +}; + +#endif // NUMERICSORTTABLEITEM_H diff --git a/include/ui/palettecolorsearch.h b/include/ui/palettecolorsearch.h new file mode 100644 index 00000000..967f6efa --- /dev/null +++ b/include/ui/palettecolorsearch.h @@ -0,0 +1,67 @@ +#ifndef PALETTECOLORSEARCH_H +#define PALETTECOLORSEARCH_H + +#include +#include +#include + +class Tileset; +class Project; + +namespace Ui { +class PaletteColorSearch; +} + +class PaletteColorSearch : public QDialog +{ + Q_OBJECT + +public: + explicit PaletteColorSearch(Project *project, + const Tileset *primaryTileset, + const Tileset *secondaryTileset, + QWidget *parent = nullptr); + ~PaletteColorSearch(); + + void setPaletteId(int paletteId); + int currentPaletteId() const; + + void setColorId(int colorId); + int currentColorId() const; + + void setTilesets(const Tileset *primaryTileset, const Tileset *secondaryTileset); + const Tileset* currentTileset() const; + +signals: + void metatileSelected(uint16_t metatileId); + void paletteIdChanged(int paletteId); + +private: + struct RowData { + QString tilesetName; + QString pairedTilesetName; + QString metatileId; + QIcon metatileIcon; + }; + + enum ResultsColumn { + TilesetName, + Metatile, + }; + + Ui::PaletteColorSearch *ui; + Project *m_project; + const Tileset *m_primaryTileset; + const Tileset *m_secondaryTileset; + + QMap> m_resultsCache; + + void addTableEntry(const RowData &rowData); + QList search(int colorId) const; + QList search(int colorId, const Tileset *tileset, const Tileset *pairedTileset) const; + void refresh(); + void updateResults(); + void cellDoubleClicked(int row, int col); +}; + +#endif // PALETTECOLORSEARCH_H diff --git a/include/ui/paletteeditor.h b/include/ui/paletteeditor.h index 0ab3eda3..95fc80b8 100644 --- a/include/ui/paletteeditor.h +++ b/include/ui/paletteeditor.h @@ -2,10 +2,12 @@ #define PALETTEEDITOR_H #include +#include #include "colorinputwidget.h" #include "project.h" #include "history.h" +#include "palettecolorsearch.h" namespace Ui { class PaletteEditor; @@ -32,27 +34,31 @@ public: bool showingUnusedColors() const; +signals: + void metatileSelected(uint16_t metatileId); + private: Ui::PaletteEditor *ui; - Project *project = nullptr; - QList colorInputs; - + Project *project; Tileset *primaryTileset; Tileset *secondaryTileset; + QList colorInputs; QList> palettesHistory; QMap> unusedColorCache; + QPointer colorSearchWindow; Tileset* getTileset(int paletteId) const; void refreshColorInputs(); + void refreshPaletteId(); void commitEditHistory(); void commitEditHistory(int paletteId); void restoreWindowState(); - void onWindowActivated(); void invalidateCache(); void closeEvent(QCloseEvent*); void setColorInputTitles(bool show); - QSet getUnusedColorIds() const; + QSet getUnusedColorIds(); + void openColorSearch(); void setRgb(int index, QRgb rgb); void setPalette(int paletteId, const QList &palette); @@ -67,7 +73,6 @@ signals: void changedPaletteColor(); void changedPalette(int); private slots: - void on_spinBox_PaletteId_valueChanged(int arg1); void on_actionUndo_triggered(); void on_actionRedo_triggered(); void on_actionImport_Palette_triggered(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index dc5b935a..1266da14 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -2,6 +2,7 @@ #define TILESETEDITOR_H #include +#include #include "project.h" #include "history.h" #include "paletteeditor.h" @@ -54,6 +55,9 @@ public: QObjectList shortcutableObjects() const; + void setPaletteId(int paletteId); + int paletteId() const; + public slots: void applyUserShortcuts(); void onSelectedMetatileChanged(uint16_t); @@ -65,13 +69,9 @@ private slots: void onHoveredTileChanged(const Tile&); void onHoveredTileChanged(uint16_t); void onHoveredTileCleared(); - void onSelectedTilesChanged(); void onMetatileLayerTileChanged(int, int); void onMetatileLayerSelectionChanged(QPoint, int, int); void onPaletteEditorChangedPaletteColor(); - void onPaletteEditorChangedPalette(int); - - void on_spinBox_paletteSelector_valueChanged(int arg1); void on_actionChange_Metatiles_Count_triggered(); @@ -136,23 +136,20 @@ private: void commitTerrainType(); void commitLayerType(); void setRawAttributesVisible(bool visible); - void setXFlip(bool enabled); - void setYFlip(bool enabled); + void refreshTileFlips(); + void refreshPaletteId(); Ui::TilesetEditor *ui; History metatileHistory; TilesetEditorMetatileSelector *metatileSelector = nullptr; TilesetEditorTileSelector *tileSelector = nullptr; MetatileLayersItem *metatileLayersItem = nullptr; - PaletteEditor *paletteEditor = nullptr; + QPointer paletteEditor = nullptr; Project *project = nullptr; Layout *layout = nullptr; Metatile *metatile = nullptr; Metatile *copiedMetatile = nullptr; QString copiedMetatileLabel; - int paletteId; - bool tileXFlip; - bool tileYFlip; bool hasUnsavedChanges; Tileset *primaryTileset = nullptr; Tileset *secondaryTileset = nullptr; diff --git a/include/ui/wildmonsearch.h b/include/ui/wildmonsearch.h index ccf0fe94..4fe608b8 100644 --- a/include/ui/wildmonsearch.h +++ b/include/ui/wildmonsearch.h @@ -2,24 +2,11 @@ #define WILDMONSEARCH_H #include -#include -#include + +#include "numericsorttableitem.h" class Project; -class NumericSortTableItem : public QTableWidgetItem -{ -public: - explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {}; - -protected: - virtual bool operator<(const QTableWidgetItem &other) const override { - QCollator collator; - collator.setNumericMode(true); - return collator.compare(text(), other.text()) < 0; - } -}; - namespace Ui { class WildMonSearch; } diff --git a/porymap.pro b/porymap.pro index 9c6327b7..6b2245c1 100644 --- a/porymap.pro +++ b/porymap.pro @@ -116,6 +116,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/montabwidget.cpp \ src/ui/encountertablemodel.cpp \ src/ui/encountertabledelegates.cpp \ + src/ui/palettecolorsearch.cpp \ src/ui/paletteeditor.cpp \ src/ui/selectablepixmapitem.cpp \ src/ui/tileseteditor.cpp \ @@ -234,6 +235,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/encountertablemodel.h \ include/ui/encountertabledelegates.h \ include/ui/adjustingstackedwidget.h \ + include/ui/palettecolorsearch.h \ include/ui/paletteeditor.h \ include/ui/selectablepixmapitem.h \ include/ui/tileseteditor.h \ @@ -287,6 +289,7 @@ FORMS += forms/mainwindow.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ forms/tileseteditor.ui \ + forms/palettecolorsearch.ui \ forms/paletteeditor.ui \ forms/regionmapeditor.ui \ forms/newmapdialog.ui \ diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index f6c0d382..00fff2ba 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -48,7 +48,7 @@ QString Metatile::getMetatileIdString(uint16_t metatileId) { return Util::toHexString(metatileId, numMetatileIdChars); }; -QString Metatile::getMetatileIdStrings(const QList metatileIds) { +QString Metatile::getMetatileIdStrings(const QList &metatileIds) { QStringList metatiles; for (auto metatileId : metatileIds) metatiles << Metatile::getMetatileIdString(metatileId); diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index f5b7f440..d76b0138 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -8,6 +8,7 @@ #include #include +#include Tileset::Tileset(const Tileset &other) @@ -110,6 +111,20 @@ int Tileset::maxTiles() const { return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary(); } +Tileset* Tileset::getPaletteTileset(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset) { + return const_cast(getPaletteTileset(paletteId, static_cast(primaryTileset), static_cast(secondaryTileset))); +} + +const Tileset* Tileset::getPaletteTileset(int paletteId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { + if (paletteId < Project::getNumPalettesPrimary()) { + return primaryTileset; + } else if (paletteId < Project::getNumPalettesTotal()) { + return secondaryTileset; + } else { + return nullptr; + } +} + Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { return const_cast(getTileTileset(tileId, static_cast(primaryTileset), static_cast(secondaryTileset))); } @@ -125,8 +140,12 @@ const Tileset* Tileset::getTileTileset(int tileId, const Tileset *primaryTileset } } -// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset. Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { + return const_cast(getMetatileTileset(metatileId, static_cast(primaryTileset), static_cast(secondaryTileset))); +} + +// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset. +const Tileset* Tileset::getMetatileTileset(int metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { if (metatileId < Project::getNumMetatilesPrimary()) { return primaryTileset; } else if (metatileId < Project::getNumMetatilesTotal()) { @@ -137,7 +156,11 @@ Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Ti } Metatile* Tileset::getMetatile(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { - Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset); + return const_cast(getMetatile(metatileId, static_cast(primaryTileset), static_cast(secondaryTileset))); +} + +const Metatile* Tileset::getMetatile(int metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { + const Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset); if (!tileset) { return nullptr; } @@ -226,12 +249,12 @@ QString Tileset::getMetatileLabelPrefix(const QString &name) return QString("%1%2_").arg(labelPrefix).arg(Tileset::stripPrefix(name)); } -bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { +bool Tileset::metatileIsValid(uint16_t metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { return (primaryTileset && primaryTileset->contains(metatileId)) || (secondaryTileset && secondaryTileset->contains(metatileId)); } -QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) { +QList> Tileset::getBlockPalettes(const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) { QList> palettes; QList> primaryPalettes; @@ -253,9 +276,9 @@ QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *s return palettes; } -QList Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) { +QList Tileset::getPalette(int paletteId, const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) { QList paletteTable; - Tileset *tileset = paletteId < Project::getNumPalettesPrimary() + const Tileset *tileset = paletteId < Project::getNumPalettesPrimary() ? primaryTileset : secondaryTileset; if (!tileset) { @@ -669,7 +692,7 @@ QString Tileset::stripPrefix(const QString &fullName) { // Find which of the specified color IDs in 'searchColors' are not used by any of this tileset's metatiles. // The 'pairedTileset' may be used to get the tile images for any tiles that don't belong to this tileset. // If 'searchColors' is empty, it will for search for all unused colors. -QSet Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet &searchColors) const { +QSet Tileset::getUnusedColorIds(int paletteId, const Tileset *pairedTileset, const QSet &searchColors) const { QSet unusedColors = searchColors; if (unusedColors.isEmpty()) { // Search for all colors @@ -707,3 +730,43 @@ QSet Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, cons } return unusedColors; } + +// Returns the list of metatile IDs representing all the metatiles in this tileset that use the specified color ID. +QList Tileset::findMetatilesUsingColor(int paletteId, int colorId, const Tileset *pairedTileset) const { + const Tileset *primaryTileset = this->is_secondary ? pairedTileset : this; + const Tileset *secondaryTileset = this->is_secondary ? this : pairedTileset; + QSet metatileIdSet; + QHash tileContainsColor; + uint16_t metatileIdBase = firstMetatileId(); + for (int i = 0; i < m_metatiles.length(); i++) { + uint16_t metatileId = i + metatileIdBase; + for (const auto &tile : m_metatiles.at(i)->tiles) { + if (tile.palette != paletteId) + continue; + + // Save time on tiles we've already inspected by getting the cached result. + auto tileIt = tileContainsColor.constFind(tile.tileId); + if (tileIt != tileContainsColor.constEnd()) { + if (tileIt.value()) metatileIdSet.insert(metatileId); + continue; + } + tileContainsColor[tile.tileId] = false; + + QImage image = getTileImage(tile.tileId, primaryTileset, secondaryTileset); + if (image.isNull() || image.sizeInBytes() < Tile::numPixels()) + continue; + + const uchar * pixels = image.constBits(); + for (int j = 0; j < Tile::numPixels(); j++) { + if (pixels[j] == colorId) { + metatileIdSet.insert(metatileId); + tileContainsColor[tile.tileId] = true; + break; + } + } + } + } + QList metatileIds(metatileIdSet.constBegin(), metatileIdSet.constEnd()); + std::sort(metatileIds.begin(), metatileIds.end()); + return metatileIds; +} diff --git a/src/core/utility.cpp b/src/core/utility.cpp index ff06838f..92cd1a4a 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -112,3 +112,16 @@ void Util::setErrorStylesheet(QLineEdit *lineEdit, bool isError) { static const QString stylesheet = QStringLiteral("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); lineEdit->setStyleSheet(isError ? stylesheet : ""); } + +void Util::show(QWidget *w) { + if (!w) return; + + if (!w->isVisible()) { + w->show(); + } else if (w->isMinimized()) { + w->showNormal(); + } else { + w->raise(); + w->activateWindow(); + } +} diff --git a/src/editor.cpp b/src/editor.cpp index 5f1dfe0f..b4f97576 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2060,12 +2060,6 @@ void Editor::updateCustomMapAttributes() map->modify(); } -Tileset* Editor::getCurrentMapPrimaryTileset() -{ - QString tilesetLabel = this->layout->tileset_primary_label; - return project->getTileset(tilesetLabel); -} - void Editor::redrawAllEvents() { if (this->map) redrawEvents(this->map->getEvents()); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c191ea07..ac7c2385 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -345,7 +345,7 @@ void MainWindow::checkForUpdates(bool requestedByUser) { if (requestedByUser) { - openSubWindow(this->updatePromoter); + Util::show(this->updatePromoter); } else { // This is an automatic update check. Only run if we haven't done one in the last 5 minutes QDateTime lastCheck = porymapConfig.lastUpdateCheckTime; @@ -419,7 +419,7 @@ void MainWindow::initEditor() { } void MainWindow::openEditHistory() { - openSubWindow(this->undoView); + Util::show(this->undoView); } void MainWindow::initMiscHeapObjects() { @@ -936,19 +936,6 @@ void MainWindow::refreshRecentProjectsMenu() { clearAction->setEnabled(!recentProjects.isEmpty()); } -void MainWindow::openSubWindow(QWidget * window) { - if (!window) return; - - if (!window->isVisible()) { - window->show(); - } else if (window->isMinimized()) { - window->showNormal(); - } else { - window->raise(); - window->activateWindow(); - } -} - void MainWindow::showFileWatcherWarning() { if (!porymapConfig.monitorFiles || !isProjectOpen()) return; @@ -2266,7 +2253,7 @@ void MainWindow::on_actionGrid_Settings_triggered() { connect(this->gridSettingsDialog, &GridSettingsDialog::changedGridSettings, this->editor, &Editor::updateMapGrid); connect(this->gridSettingsDialog, &GridSettingsDialog::accepted, [this] { porymapConfig.gridSettings = this->editor->gridSettings; }); } - openSubWindow(this->gridSettingsDialog); + Util::show(this->gridSettingsDialog); } void MainWindow::on_actionShortcuts_triggered() @@ -2274,7 +2261,7 @@ void MainWindow::on_actionShortcuts_triggered() if (!shortcutsEditor) initShortcutsEditor(); - openSubWindow(shortcutsEditor); + Util::show(shortcutsEditor); } void MainWindow::initShortcutsEditor() { @@ -2698,7 +2685,7 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { } } - openSubWindow(this->mapImageExporter); + Util::show(this->mapImageExporter); } void MainWindow::on_pushButton_AddConnection_clicked() { @@ -2726,7 +2713,7 @@ void MainWindow::on_pushButton_SummaryChart_clicked() { connect(this->editor, &Editor::wildMonTableClosed, this->wildMonChart, &WildMonChart::clearTable); connect(this->editor, &Editor::wildMonTableEdited, this->wildMonChart, &WildMonChart::refresh); } - openSubWindow(this->wildMonChart); + Util::show(this->wildMonChart); } void MainWindow::on_toolButton_WildMonSearch_clicked() { @@ -2735,7 +2722,7 @@ void MainWindow::on_toolButton_WildMonSearch_clicked() { connect(this->wildMonSearch, &WildMonSearch::openWildMonTableRequested, this, &MainWindow::openWildMonTable); connect(this->editor, &Editor::wildMonTableEdited, this->wildMonSearch, &WildMonSearch::refresh); } - openSubWindow(this->wildMonSearch); + Util::show(this->wildMonSearch); } void MainWindow::openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName) { @@ -2843,7 +2830,7 @@ void MainWindow::on_actionTileset_Editor_triggered() initTilesetEditor(); } - openSubWindow(this->tilesetEditor); + Util::show(this->tilesetEditor); MetatileSelection selection = this->editor->metatile_selector_item->getMetatileSelection(); if (!selection.metatileItems.isEmpty()) { @@ -2895,7 +2882,7 @@ void MainWindow::on_actionAbout_Porymap_triggered() { if (!this->aboutWindow) this->aboutWindow = new AboutPorymap(this); - openSubWindow(this->aboutWindow); + Util::show(this->aboutWindow); } void MainWindow::on_actionOpen_Log_File_triggered() { @@ -2927,7 +2914,7 @@ void MainWindow::on_actionPreferences_triggered() { connect(preferenceEditor, &PreferenceEditor::reloadProjectRequested, this, &MainWindow::on_action_Reload_Project_triggered); } - openSubWindow(preferenceEditor); + Util::show(preferenceEditor); } void MainWindow::togglePreferenceSpecificUi() { @@ -2944,7 +2931,7 @@ void MainWindow::openProjectSettingsEditor(int tab) { this, &MainWindow::on_action_Reload_Project_triggered); } this->projectSettingsEditor->setTab(tab); - openSubWindow(this->projectSettingsEditor); + Util::show(this->projectSettingsEditor); } void MainWindow::on_actionProject_Settings_triggered() { @@ -2982,7 +2969,7 @@ void MainWindow::on_actionCustom_Scripts_triggered() { if (!this->customScriptsEditor) initCustomScriptsEditor(); - openSubWindow(this->customScriptsEditor); + Util::show(this->customScriptsEditor); } void MainWindow::initCustomScriptsEditor() { @@ -3062,7 +3049,7 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() { } } - openSubWindow(this->regionMapEditor); + Util::show(this->regionMapEditor); } void MainWindow::on_pushButton_CreatePrefab_clicked() { @@ -3120,7 +3107,7 @@ bool MainWindow::closeSupplementaryWindows() { if (widget != this && widget->isWindow()) { // Make sure the window is raised and activated before closing in case it has a confirmation prompt. if (widget->isVisible()) { - openSubWindow(widget); + Util::show(widget); } if (!widget->close()) { QString message = QStringLiteral("Aborted project close"); diff --git a/src/project.cpp b/src/project.cpp index 19d99eb8..9c5fd9af 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -186,6 +186,8 @@ bool Project::load() { this->parser.setUpdatesSplashScreen(true); resetFileWatcher(); resetFileCache(); + QPixmapCache::clear(); + this->disabledSettingsNames.clear(); bool success = readGlobalConstants() && readMapLayouts() @@ -273,6 +275,17 @@ void Project::clearTilesetCache() { this->tilesetCache.clear(); } +void Project::cacheTileset(const QString &name, Tileset *tileset) { + auto it = this->tilesetCache.constFind(name); + if (it != this->tilesetCache.constEnd() && it.value() && tileset != it.value()) { + // Callers of this function should ensure this doesn't happen, + // but in case it does we should avoid leaking memory. + logWarn(QString("New tileset %1 overwrote existing tileset.").arg(name)); + delete it.value(); + } + this->tilesetCache.insert(name, tileset); +} + Map* Project::loadMap(const QString &mapName) { if (mapName == getDynamicMapName()) { // Silently ignored, caller is expected to handle this if they want this to be an error. @@ -1180,7 +1193,21 @@ bool Project::loadLayoutTilesets(Layout *layout) { return layout->tileset_primary && layout->tileset_secondary; } -Tileset* Project::loadTileset(QString label, Tileset *tileset) { +Tileset* Project::getTileset(const QString &label, bool forceLoad) { + Tileset *tileset = nullptr; + + auto it = this->tilesetCache.constFind(label); + if (it != this->tilesetCache.constEnd()) { + tileset = it.value(); + if (!forceLoad) { + return tileset; + } + } else { + // Create a cache entry even if we don't end up loading the tileset successfully. + // This will prevent repeated file reads if the tileset fails to load. + cacheTileset(label, nullptr); + } + auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets); if (this->usingAsmTilesets) { // Read asm tileset header. Backwards compatibility @@ -1225,7 +1252,7 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { return nullptr; } - tilesetCache.insert(label, tileset); + cacheTileset(tileset->name, tileset); return tileset; } @@ -1617,15 +1644,14 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles)); } ignoreWatchedFilesTemporarily({headersFilepath, graphicsFilepath, metatilesFilepath}); - name = Tileset::stripPrefix(name); - tileset->appendToHeaders(headersFilepath, name, this->usingAsmTilesets); - tileset->appendToGraphics(graphicsFilepath, name, this->usingAsmTilesets); - tileset->appendToMetatiles(metatilesFilepath, name, this->usingAsmTilesets); + QString baseName = Tileset::stripPrefix(name); + tileset->appendToHeaders(headersFilepath, baseName, this->usingAsmTilesets); + tileset->appendToGraphics(graphicsFilepath, baseName, this->usingAsmTilesets); + tileset->appendToMetatiles(metatilesFilepath, baseName, this->usingAsmTilesets); tileset->save(); - this->tilesetCache.insert(tileset->name, tileset); - + cacheTileset(tileset->name, tileset); emit tilesetCreated(tileset); return tileset; } @@ -1680,20 +1706,6 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) { } } -Tileset* Project::getTileset(QString label, bool forceLoad) { - Tileset *existingTileset = nullptr; - if (tilesetCache.contains(label)) { - existingTileset = tilesetCache.value(label); - } - - if (existingTileset && !forceLoad) { - return existingTileset; - } else { - Tileset *tileset = loadTileset(label, existingTileset); - return tileset; - } -} - bool Project::saveTextFile(const QString &path, const QString &text) { QFile file(path); if (!file.open(QIODevice::WriteOnly)) { @@ -2266,6 +2278,7 @@ bool Project::readTilesetLabels() { this->primaryTilesetLabels.clear(); this->secondaryTilesetLabels.clear(); this->tilesetLabelsOrdered.clear(); + clearTilesetCache(); QString filename = projectConfig.getFilePath(ProjectFilePath::tilesets_headers); QFileInfo fileInfo(this->root + "/" + filename); @@ -3520,7 +3533,7 @@ bool Project::hasUnsavedChanges() { } // Searches the project's map layouts to find the names of the tilesets that the provided tileset gets paired with. -QSet Project::getPairedTilesetLabels(Tileset *tileset) const { +QSet Project::getPairedTilesetLabels(const Tileset *tileset) const { QSet pairedLabels; for (const auto &layout : this->mapLayouts) { if (tileset->is_secondary) { diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 2d4fee51..5efa1e91 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -12,14 +12,14 @@ QImage getCollisionMetatileImage(int collision, int elevation) { return image ? *image : QImage(); } -QImage getMetatileImage(uint16_t metatileId, Layout *layout, bool useTruePalettes) { +QImage getMetatileImage(uint16_t metatileId, const Layout *layout, bool useTruePalettes) { Metatile* metatile = Tileset::getMetatile(metatileId, layout ? layout->tileset_primary : nullptr, layout ? layout->tileset_secondary : nullptr); return getMetatileImage(metatile, layout, useTruePalettes); } -QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes) { +QImage getMetatileImage(const Metatile *metatile, const Layout *layout, bool useTruePalettes) { if (!layout) { return getMetatileImage(metatile, nullptr, nullptr, {}, {}, useTruePalettes); } @@ -33,8 +33,8 @@ QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes QImage getMetatileImage( uint16_t metatileId, - Tileset *primaryTileset, - Tileset *secondaryTileset, + const Tileset *primaryTileset, + const Tileset *secondaryTileset, const QList &layerOrder, const QList &layerOpacity, bool useTruePalettes) @@ -54,9 +54,9 @@ QColor getInvalidImageColor() { } QImage getMetatileImage( - Metatile *metatile, - Tileset *primaryTileset, - Tileset *secondaryTileset, + const Metatile *metatile, + const Tileset *primaryTileset, + const Tileset *secondaryTileset, const QList &layerOrder, const QList &layerOpacity, bool useTruePalettes) @@ -146,7 +146,7 @@ QImage getTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tilese return tileset ? tileset->tileImage(tileId) : QImage(); } -QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) { +QImage getColoredTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset, const QList &palette) { QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset); if (tileImage.isNull()) { // Some tiles specify tile IDs or palette IDs that are outside the valid range. @@ -162,12 +162,12 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se return tileImage; } -QImage getPalettedTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, bool useTruePalettes) { +QImage getPalettedTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset, int paletteId, bool useTruePalettes) { QList palette = Tileset::getPalette(paletteId, primaryTileset, secondaryTileset, useTruePalettes); return getColoredTileImage(tileId, primaryTileset, secondaryTileset, palette); } -QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { +QImage getGreyscaleTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) { return getColoredTileImage(tileId, primaryTileset, secondaryTileset, greyscalePalette); } @@ -181,8 +181,8 @@ void flattenTo4bppImage(QImage * image) { } // Constructs a grid image of the metatiles in the specified ID range. -QImage getMetatileSheetImage(Tileset *primaryTileset, - Tileset *secondaryTileset, +QImage getMetatileSheetImage(const Tileset *primaryTileset, + const Tileset *secondaryTileset, uint16_t metatileIdStart, uint16_t metatileIdEnd, int numMetatilesWide, @@ -219,15 +219,15 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, // Constructs a grid image of the metatiles in the primary and secondary tileset, // rounding as necessary to keep the two tilesets on separate rows. // The unused metatiles (if any) between the primary and secondary tilesets are skipped. -QImage getMetatileSheetImage(Tileset *primaryTileset, - Tileset *secondaryTileset, +QImage getMetatileSheetImage(const Tileset *primaryTileset, + const Tileset *secondaryTileset, int numMetatilesWide, const QList &layerOrder, const QList &layerOpacity, const QSize &metatileSize, bool useTruePalettes) { - auto createSheetImage = [=](uint16_t start, Tileset *tileset) { + auto createSheetImage = [=](uint16_t start, const Tileset *tileset) { uint16_t end = start; if (tileset) { if (tileset->numMetatiles() == 0) @@ -259,7 +259,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset, return image; } -QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) { +QImage getMetatileSheetImage(const Layout *layout, int numMetatilesWide, bool useTruePalettes) { if (!layout) return QImage(); return getMetatileSheetImage(layout->tileset_primary, diff --git a/src/ui/palettecolorsearch.cpp b/src/ui/palettecolorsearch.cpp new file mode 100644 index 00000000..e1f47a90 --- /dev/null +++ b/src/ui/palettecolorsearch.cpp @@ -0,0 +1,207 @@ +#include "palettecolorsearch.h" +#include "ui_palettecolorsearch.h" +#include "project.h" +#include "tileset.h" +#include "imageproviders.h" +#include "eventfilters.h" +#include "log.h" +#include "numericsorttableitem.h" + +enum ResultsDataRole { + PairedTilesetName = Qt::UserRole, +}; + +PaletteColorSearch::PaletteColorSearch(Project *project, const Tileset *primaryTileset, const Tileset *secondaryTileset, QWidget *parent) : + QDialog(parent), + ui(new Ui::PaletteColorSearch), + m_project(project), + m_primaryTileset(primaryTileset), + m_secondaryTileset(secondaryTileset) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + ui->buttonBox->setVisible(isModal()); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close); + + // Rather than try to keep track of metatile/tile changes that affect which colors are used, + // we'll just refresh when the window is activated. + ActiveWindowFilter *filter = new ActiveWindowFilter(this); + connect(filter, &ActiveWindowFilter::activated, this, &PaletteColorSearch::refresh); + this->installEventFilter(filter); + + ui->spinBox_ColorId->setRange(0, Tileset::numColorsPerPalette() - 1); + connect(ui->spinBox_ColorId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::updateResults); + + ui->spinBox_PaletteId->setRange(0, Project::getNumPalettesTotal() - 1); + connect(ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::updateResults); + connect(ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::paletteIdChanged); + + // Set up table header + static const QStringList labels = {"Tileset", "Metatile"}; + ui->table_Results->setHorizontalHeaderLabels(labels); + ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::TilesetName, QHeaderView::ResizeToContents); + ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Metatile, QHeaderView::Stretch); + + // Table is read-only + ui->table_Results->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->table_Results->setSelectionMode(QAbstractItemView::NoSelection); + + connect(ui->table_Results, &QTableWidget::cellDoubleClicked, this, &PaletteColorSearch::cellDoubleClicked); +} + +PaletteColorSearch::~PaletteColorSearch() { + delete ui; +} + +void PaletteColorSearch::setPaletteId(int paletteId) { + ui->spinBox_PaletteId->setValue(paletteId); +} + +int PaletteColorSearch::currentPaletteId() const { + return ui->spinBox_PaletteId->value(); +} + +void PaletteColorSearch::setColorId(int colorId) { + ui->spinBox_ColorId->setValue(colorId); +} + +int PaletteColorSearch::currentColorId() const { + return ui->spinBox_ColorId->value(); +} + +void PaletteColorSearch::setTilesets(const Tileset *primaryTileset, const Tileset *secondaryTileset) { + m_primaryTileset = primaryTileset; + m_secondaryTileset = secondaryTileset; + refresh(); +} + +const Tileset* PaletteColorSearch::currentTileset() const { + return Tileset::getPaletteTileset(currentPaletteId(), m_primaryTileset, m_secondaryTileset); +} + +void PaletteColorSearch::addTableEntry(const RowData &rowData) { + int row = ui->table_Results->rowCount(); + ui->table_Results->insertRow(row); + + auto tilesetNameItem = new NumericSortTableItem(rowData.tilesetName); + tilesetNameItem->setData(ResultsDataRole::PairedTilesetName, rowData.pairedTilesetName); + + ui->table_Results->setItem(row, ResultsColumn::TilesetName, tilesetNameItem); + ui->table_Results->setItem(row, ResultsColumn::Metatile, new QTableWidgetItem(rowData.metatileIcon, rowData.metatileId)); +} + +QList PaletteColorSearch::search(int colorId) const { + QList results; + + // Check our current tilesets for color usage. + results.append(search(colorId, m_primaryTileset, m_secondaryTileset)); + results.append(search(colorId, m_secondaryTileset, m_primaryTileset)); + + // The current palette comes from either the primary or secondary tileset. + // We need to check all the other tilesets that are paired with the tileset that owns this palette. + const Tileset *paletteTileset = currentTileset(); + QSet tilesetsToSearch = m_project->getPairedTilesetLabels(paletteTileset); + + // We exclude the currently-loaded pair (we already checked them, and because they're being + // edited in the Tileset Editor they may differ from their copies saved in the layout). + tilesetsToSearch.remove(m_primaryTileset->name); + tilesetsToSearch.remove(m_secondaryTileset->name); + + for (const auto &label : tilesetsToSearch) { + Tileset *searchTileset = m_project->getTileset(label); + if (searchTileset) { + results.append(search(colorId, searchTileset, paletteTileset)); + } + } + return results; +} + +QList PaletteColorSearch::search(int colorId, const Tileset *tileset, const Tileset *pairedTileset) const { + QList results; + QList metatileIds = tileset->findMetatilesUsingColor(currentPaletteId(), colorId, pairedTileset); + auto primaryTileset = tileset->is_secondary ? pairedTileset : tileset; + auto secondaryTileset = tileset->is_secondary ? tileset : pairedTileset; + for (const auto &metatileId : metatileIds) { + QImage metatileImage = getMetatileImage(metatileId, primaryTileset, secondaryTileset); + RowData rowData = { + .tilesetName = tileset->name, + .pairedTilesetName = pairedTileset->name, + .metatileId = Metatile::getMetatileIdString(metatileId), + .metatileIcon = QIcon(QPixmap::fromImage(metatileImage)), + }; + results.append(rowData); + } + return results; +} + +void PaletteColorSearch::refresh() { + m_resultsCache.clear(); + updateResults(); +} + +void PaletteColorSearch::updateResults() { + const Tileset *tileset = currentTileset(); + int paletteId = currentPaletteId(); + int colorId = currentColorId(); + + // Update color icon + QRgb color = tileset->palettePreviews.value(paletteId).value(colorId); + ui->frame_Color->setStyleSheet(QString("background-color: rgb(%1, %2, %3);").arg(qRed(color)).arg(qGreen(color)).arg(qBlue(color))); + + // Update title + ui->label_Title->setText(QString("Searching for usage of %1's palette %2.").arg(tileset->name).arg(paletteId)); + + // Update table + ui->table_Results->clearContents(); + ui->table_Results->setRowCount(0); + + QString cacheKey = QString("%1#%2").arg(paletteId).arg(colorId); + auto it = m_resultsCache.constFind(cacheKey); + bool inCache = (it != m_resultsCache.constEnd()); + const QList results = inCache ? it.value() : search(colorId); + + if (results.isEmpty()) { + static const RowData noResults = { + .tilesetName = QStringLiteral("This color is unused."), + .pairedTilesetName = "", + .metatileId = QStringLiteral("--"), + .metatileIcon = QIcon(), + }; + addTableEntry(noResults); + } else { + for (const auto &entry : results) { + addTableEntry(entry); + } + } + + ui->table_Results->sortByColumn(ResultsColumn::TilesetName, Qt::AscendingOrder); + + if (!inCache) m_resultsCache.insert(cacheKey, results); +} + +// Double-clicking row data selects the corresponding metatile in the Tileset Editor. +void PaletteColorSearch::cellDoubleClicked(int row, int) { + auto tilesetNameItem = ui->table_Results->item(row, ResultsColumn::TilesetName); + auto metatileItem = ui->table_Results->item(row, ResultsColumn::Metatile); + if (!tilesetNameItem || !metatileItem) + return; + + // The Tileset Editor (as of writing) has no way to change the selected tilesets independently of + // the main editor's layout, so if the metatile is not in the current tileset we do nothing. + // To compare the tileset names, rather than sort out which was the primary or secondary we + // just make sure it's the same set of names. + QSet currentTilesets; + currentTilesets.insert(m_primaryTileset->name); + currentTilesets.insert(m_secondaryTileset->name); + + QSet metatileTilesets; + metatileTilesets.insert(tilesetNameItem->text()); + metatileTilesets.insert(tilesetNameItem->data(ResultsDataRole::PairedTilesetName).toString()); + if (currentTilesets != metatileTilesets) + return; + + bool ok; + uint16_t metatileId = metatileItem->text().toUInt(&ok, 0); + if (ok) emit metatileSelected(metatileId); +} diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp index 035e4397..571185d7 100644 --- a/src/ui/paletteeditor.cpp +++ b/src/ui/paletteeditor.cpp @@ -6,20 +6,19 @@ #include "filedialog.h" #include "message.h" #include "eventfilters.h" +#include "utility.h" PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) : QMainWindow(parent), - ui(new Ui::PaletteEditor) + ui(new Ui::PaletteEditor), + project(project), + primaryTileset(primaryTileset), + secondaryTileset(secondaryTileset) { - this->project = project; - this->primaryTileset = primaryTileset; - this->secondaryTileset = secondaryTileset; + setAttribute(Qt::WA_DeleteOnClose); this->ui->setupUi(this); - this->ui->spinBox_PaletteId->setMinimum(0); - this->ui->spinBox_PaletteId->setMaximum(Project::getNumPalettesTotal() - 1); - this->colorInputs.clear(); const int numColorsPerRow = 4; for (int i = 0; i < this->numColors; i++) { auto colorInput = new ColorInputWidget; @@ -43,43 +42,48 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset setBitDepth(bitDepth); // Connect bit depth buttons - connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(15); }); - connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(24); }); + connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) setBitDepth(15); }); + connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) setBitDepth(24); }); this->ui->actionShow_Unused_Colors->setChecked(porymapConfig.showPaletteEditorUnusedColors); connect(this->ui->actionShow_Unused_Colors, &QAction::toggled, this, &PaletteEditor::setColorInputTitles); + connect(this->ui->toolButton_ColorSearch, &QToolButton::clicked, this, &PaletteEditor::openColorSearch); + connect(this->ui->actionFind_Color_Usage, &QAction::triggered, this, &PaletteEditor::openColorSearch); + + // Rather than try to keep track of metatile/tile changes that affect which colors are used, + // we'll just refresh when the window is activated. ActiveWindowFilter *filter = new ActiveWindowFilter(this); - connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::onWindowActivated); + connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::invalidateCache); this->installEventFilter(filter); - this->setPaletteId(paletteId); - this->commitEditHistory(); - this->restoreWindowState(); + this->ui->spinBox_PaletteId->setRange(0, Project::getNumPalettesTotal() - 1); + this->ui->spinBox_PaletteId->setValue(paletteId); + connect(this->ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteEditor::refreshPaletteId); + connect(this->ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteEditor::changedPalette); + + refreshPaletteId(); + restoreWindowState(); } PaletteEditor::~PaletteEditor() { delete ui; } -void PaletteEditor::onWindowActivated() { - // Rather than try to keep track of metatile/tile changes that affect which colors are used, - // we'll just refresh when the window is activated. - invalidateCache(); -} - int PaletteEditor::currentPaletteId() const { return ui->spinBox_PaletteId->value(); } +void PaletteEditor::setPaletteId(int paletteId) { + ui->spinBox_PaletteId->setValue(paletteId); +} + bool PaletteEditor::showingUnusedColors() const { return ui->actionShow_Unused_Colors->isChecked(); } Tileset* PaletteEditor::getTileset(int paletteId) const { - return (paletteId < Project::getNumPalettesPrimary()) - ? this->primaryTileset - : this->secondaryTileset; + return Tileset::getPaletteTileset(paletteId, this->primaryTileset, this->secondaryTileset); } void PaletteEditor::setBitDepth(int bits) { @@ -95,7 +99,6 @@ void PaletteEditor::setRgb(int colorIndex, QRgb rgb) { Tileset *tileset = getTileset(paletteId); tileset->palettes[paletteId][colorIndex] = rgb; tileset->palettePreviews[paletteId][colorIndex] = rgb; - emit changedPaletteColor(); } @@ -120,25 +123,26 @@ void PaletteEditor::refreshColorInputs() { setColorInputTitles(showingUnusedColors()); } -void PaletteEditor::setPaletteId(int paletteId) { - const QSignalBlocker b(ui->spinBox_PaletteId); - this->ui->spinBox_PaletteId->setValue(paletteId); - this->refreshColorInputs(); +void PaletteEditor::refreshPaletteId() { + refreshColorInputs(); + + int paletteId = currentPaletteId(); + if (!this->palettesHistory[paletteId].current()) { + commitEditHistory(paletteId); + } + if (this->colorSearchWindow) { + this->colorSearchWindow->setPaletteId(paletteId); + } } void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; - this->invalidateCache(); - this->refreshColorInputs(); -} - -void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) { - this->refreshColorInputs(); - if (!this->palettesHistory[paletteId].current()) { - this->commitEditHistory(paletteId); + invalidateCache(); + if (this->colorSearchWindow) { + this->colorSearchWindow->setTilesets(primaryTileset, secondaryTileset); } - emit this->changedPalette(paletteId); + refreshColorInputs(); } void PaletteEditor::commitEditHistory() { @@ -157,8 +161,8 @@ void PaletteEditor::commitEditHistory(int paletteId) { void PaletteEditor::restoreWindowState() { logInfo("Restoring palette editor geometry from previous session."); QMap geometry = porymapConfig.getPaletteEditorGeometry(); - this->restoreGeometry(geometry.value("palette_editor_geometry")); - this->restoreState(geometry.value("palette_editor_state")); + restoreGeometry(geometry.value("palette_editor_geometry")); + restoreState(geometry.value("palette_editor_state")); } void PaletteEditor::on_actionUndo_triggered() { @@ -195,6 +199,16 @@ void PaletteEditor::on_actionImport_Palette_triggered() { commitEditHistory(paletteId); } +void PaletteEditor::openColorSearch() { + if (!this->colorSearchWindow) { + this->colorSearchWindow = new PaletteColorSearch(this->project, this->primaryTileset, this->secondaryTileset, this); + this->colorSearchWindow->setPaletteId(currentPaletteId()); + connect(this->colorSearchWindow, &PaletteColorSearch::metatileSelected, this, &PaletteEditor::metatileSelected); + connect(this->colorSearchWindow, &PaletteColorSearch::paletteIdChanged, this, &PaletteEditor::setPaletteId); + } + Util::show(this->colorSearchWindow); +} + void PaletteEditor::invalidateCache() { this->unusedColorCache.clear(); if (showingUnusedColors()) { @@ -202,7 +216,7 @@ void PaletteEditor::invalidateCache() { } } -QSet PaletteEditor::getUnusedColorIds() const { +QSet PaletteEditor::getUnusedColorIds() { const int paletteId = currentPaletteId(); if (this->unusedColorCache.contains(paletteId)) { @@ -256,7 +270,7 @@ void PaletteEditor::setColorInputTitles(bool showUnused) { void PaletteEditor::closeEvent(QCloseEvent*) { porymapConfig.setPaletteEditorGeometry( - this->saveGeometry(), - this->saveState() + saveGeometry(), + saveState() ); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 4df24065..b676c9ee 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -26,14 +26,23 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); + ui->spinBox_paletteSelector->setRange(0, Project::getNumPalettesTotal() - 1); + + auto validator = new IdentifierValidator(this); + validator->setAllowEmpty(true); + ui->lineEdit_metatileLabel->setValidator(validator); + + ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); + ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes); + + ActiveWindowFilter *filter = new ActiveWindowFilter(this); + connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated); + this->installEventFilter(filter); + setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); - connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip); - connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip); - - this->tileXFlip = ui->checkBox_xFlip->isChecked(); - this->tileYFlip = ui->checkBox_yFlip->isChecked(); - this->paletteId = ui->spinBox_paletteSelector->value(); + connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::refreshTileFlips); + connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::refreshTileFlips); connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save); @@ -51,19 +60,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) connect(ui->actionExport_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); }); - ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); - ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes); - - ui->spinBox_paletteSelector->setMinimum(0); - ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); - - auto validator = new IdentifierValidator(this); - validator->setAllowEmpty(true); - ui->lineEdit_metatileLabel->setValidator(validator); - - ActiveWindowFilter *filter = new ActiveWindowFilter(this); - connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated); - this->installEventFilter(filter); + connect(ui->spinBox_paletteSelector, QOverload::of(&QSpinBox::valueChanged), this, &TilesetEditor::refreshPaletteId); initAttributesUi(); initMetatileSelector(); @@ -131,11 +128,11 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi this->metatileReloadQueue.clear(); Tileset *primaryTileset = project->getTileset(primaryTilesetLabel); Tileset *secondaryTileset = project->getTileset(secondaryTilesetLabel); - if (this->primaryTileset) delete this->primaryTileset; - if (this->secondaryTileset) delete this->secondaryTileset; + delete this->primaryTileset; + delete this->secondaryTileset; this->primaryTileset = new Tileset(*primaryTileset); this->secondaryTileset = new Tileset(*secondaryTileset); - if (paletteEditor) paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset); + if (this->paletteEditor) this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset); this->initMetatileHistory(); } @@ -265,10 +262,8 @@ void TilesetEditor::initTileSelector() connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) { onHoveredTileChanged(tileId); }); - connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared, - this, &TilesetEditor::onHoveredTileCleared); - connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, - this, &TilesetEditor::onSelectedTilesChanged); + connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared); + connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, this, &TilesetEditor::drawSelectedTiles); this->tileSelector->showDivider = this->ui->actionShow_Tileset_Divider->isChecked(); @@ -475,10 +470,6 @@ void TilesetEditor::onHoveredTileCleared() { this->ui->statusbar->clearMessage(); } -void TilesetEditor::onSelectedTilesChanged() { - this->drawSelectedTiles(); -} - void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { static const QList tileCoords = QList{ QPoint(0, 0), @@ -543,39 +534,32 @@ void TilesetEditor::onMetatileLayerSelectionChanged(QPoint selectionOrigin, int this->tileSelector->setExternalSelection(width, height, tiles, tileIdxs); if (width == 1 && height == 1) { - ui->spinBox_paletteSelector->setValue(tiles[0].palette); + setPaletteId(tiles[0].palette); this->tileSelector->highlight(static_cast(tiles[0].tileId)); this->redrawTileSelector(); } this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId) -{ - this->ui->spinBox_paletteSelector->blockSignals(true); - this->ui->spinBox_paletteSelector->setValue(paletteId); - this->ui->spinBox_paletteSelector->blockSignals(false); - this->paletteId = paletteId; - this->tileSelector->setPaletteId(paletteId); +void TilesetEditor::setPaletteId(int paletteId) { + ui->spinBox_paletteSelector->setValue(paletteId); +} + +int TilesetEditor::paletteId() const { + return ui->spinBox_paletteSelector->value(); +} + +void TilesetEditor::refreshPaletteId() { + this->tileSelector->setPaletteId(paletteId()); this->drawSelectedTiles(); if (this->paletteEditor) { - this->paletteEditor->setPaletteId(paletteId); + this->paletteEditor->setPaletteId(paletteId()); } this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::setXFlip(bool enabled) -{ - this->tileXFlip = enabled; - this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); - this->drawSelectedTiles(); - this->metatileLayersItem->clearLastModifiedCoords(); -} - -void TilesetEditor::setYFlip(bool enabled) -{ - this->tileYFlip = enabled; - this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); +void TilesetEditor::refreshTileFlips() { + this->tileSelector->setTileFlips(ui->checkBox_xFlip->isChecked(), ui->checkBox_yFlip->isChecked()); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); } @@ -872,21 +856,12 @@ void TilesetEditor::on_actionChange_Palettes_triggered() { if (!this->paletteEditor) { this->paletteEditor = new PaletteEditor(this->project, this->primaryTileset, - this->secondaryTileset, this->paletteId, this); - connect(this->paletteEditor, &PaletteEditor::changedPaletteColor, - this, &TilesetEditor::onPaletteEditorChangedPaletteColor); - connect(this->paletteEditor, &PaletteEditor::changedPalette, - this, &TilesetEditor::onPaletteEditorChangedPalette); - } - - if (!this->paletteEditor->isVisible()) { - this->paletteEditor->show(); - } else if (this->paletteEditor->isMinimized()) { - this->paletteEditor->showNormal(); - } else { - this->paletteEditor->raise(); - this->paletteEditor->activateWindow(); + this->secondaryTileset, this->paletteId(), this); + connect(this->paletteEditor, &PaletteEditor::changedPaletteColor, this, &TilesetEditor::onPaletteEditorChangedPaletteColor); + connect(this->paletteEditor, &PaletteEditor::changedPalette, this, &TilesetEditor::setPaletteId); + connect(this->paletteEditor, &PaletteEditor::metatileSelected, this, &TilesetEditor::selectMetatile); } + Util::show(this->paletteEditor); } void TilesetEditor::onPaletteEditorChangedPaletteColor() { @@ -894,10 +869,6 @@ void TilesetEditor::onPaletteEditorChangedPaletteColor() { this->hasUnsavedChanges = true; } -void TilesetEditor::onPaletteEditorChangedPalette(int paletteId) { - this->on_spinBox_paletteSelector_valueChanged(paletteId); -} - bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, QString newLabel) { Metatile * dest = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset); @@ -996,7 +967,7 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel) void TilesetEditor::exportTilesImage(Tileset *tileset) { bool primary = !tileset->is_secondary; - QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId); + QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId()); QString filepath = FileDialog::getSaveFileName(this, QString("Export %1 Tiles Image").arg(primary ? "Primary" : "Secondary"), defaultFilepath, "Image Files (*.png)"); if (!filepath.isEmpty()) { QImage image = primary ? this->tileSelector->buildPrimaryTilesIndexedImage() : this->tileSelector->buildSecondaryTilesIndexedImage(); diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index 9957f33a..c8053bdb 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -122,7 +122,10 @@ void WildMonSearch::updateResults(const QString &species) { if (ui->comboBox_Search->findText(species) < 0) return; // Not a species name, no need to search wild encounter data. - const QList results = this->resultsCache.value(species, search(species)); + auto it = this->resultsCache.constFind(species); + bool inCache = (it != this->resultsCache.constEnd()); + const QList results = inCache ? it.value() : search(species); + if (results.isEmpty()) { static const RowData noResults = { .mapName = "", @@ -140,7 +143,7 @@ void WildMonSearch::updateResults(const QString &species) { ui->table_Results->setSortingEnabled(true); - this->resultsCache.insert(species, results); + if (!inCache) this->resultsCache.insert(species, results); } // Double-clicking row data opens the corresponding map/table on the Wild Pokémon tab. From 9ce865a9c17de48e5e7f6190295ad5c1d2b4ff5b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 28 Jul 2025 11:58:44 -0400 Subject: [PATCH 36/71] 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 37/71] 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 5584a2d47b96674b3b5a9e4fe5c1fc1398171677 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Jul 2025 14:12:51 -0400 Subject: [PATCH 38/71] Fix possible file watcher crash --- CHANGELOG.md | 1 + src/core/map.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e6dc886..da29c074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix warning not appearing when the log file exceeds maximum size. - Fix possible lag while using the Tileset Editor's tile selector. - Fix unnecessary resources being used to watch files. +- Fix possible crash on Linux if too many inotify instances are requested. ## [6.1.0] - 2025-06-09 ### Added diff --git a/src/core/map.cpp b/src/core/map.cpp index 079f336a..01ed855f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -142,17 +142,27 @@ QStringList Map::getScriptLabels(Event::Group group) { .arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/")) .arg(m_name) .arg(error)); + + // Setting this flag here (and below) lets us skip some steps and logging if we already know it failed. + // Script labels may be re-requested often, so we don't want to fill the log with warnings. m_loggedScriptsFileError = true; } - if (porymapConfig.monitorFiles) { + if (porymapConfig.monitorFiles && !m_loggedScriptsFileError) { if (!m_scriptFileWatcher) { // Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources). // The other option would be for Porymap to have a single global QFileSystemWatcher, but that has complications of its own. m_scriptFileWatcher = new QFileSystemWatcher(this); connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); } - if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath) && !m_loggedScriptsFileError) { + // m_scriptFileWatcher can stil be nullptr here if the inotify limit was reached on Linux. + // Porymap isn't using enough resources in general for this to be a problem, but the user may have lowered the inotify limit. + if (!m_scriptFileWatcher) { + logWarn(QString("Failed to add scripts file '%1' to file watcher for %2: Reached system resource limit.") + .arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/")) + .arg(m_name)); + m_loggedScriptsFileError = true; + } else if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath)) { logWarn(QString("Failed to add scripts file '%1' to file watcher for %2.") .arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/")) .arg(m_name)); From f6f07ca5fc85556b5b404a2d12f973d1d05edb8d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Jul 2025 03:10:46 -0400 Subject: [PATCH 39/71] 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 b66bfd0fd5dd5a025ca11db35e8fce1201fb3d78 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 1 Aug 2025 02:47:59 -0400 Subject: [PATCH 40/71] Fix some issues with the new layout dialog --- CHANGELOG.md | 2 ++ include/ui/newlayoutform.h | 2 +- src/core/advancemapparser.cpp | 22 ++++++++++++++-------- src/ui/newlayoutdialog.cpp | 4 ++-- src/ui/newlayoutform.cpp | 5 ++--- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da29c074..b29c7d54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix the shortcut for duplicating events working while on the Connections tab. - Fix Undo/Redo ignoring the automatic resizing that occurs if a layout/border was an unexpected size. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. +- Fix broken error message for the primary tileset on the new map/layout dialogs. +- Fix the dialog for duplicating/importing a map layout not allowing the tilesets to be changed. - Fix warning not appearing when the log file exceeds maximum size. - Fix possible lag while using the Tileset Editor's tile selector. - Fix unnecessary resources being used to watch files. diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index 3e77af18..99640444 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -24,7 +24,7 @@ public: void setSettings(const Layout::Settings &settings); Layout::Settings settings() const; - void setDisabled(bool disabled); + void setDimensionsDisabled(bool disabled); bool validate(); diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp index 2e4ef7cd..04348e96 100644 --- a/src/core/advancemapparser.cpp +++ b/src/core/advancemapparser.cpp @@ -73,15 +73,21 @@ Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, cons const QList tilesets = project->tilesetLabelsOrdered; - if (mapPrimaryTilesetNum > tilesets.size()) - mapLayout->tileset_primary_label = project->getDefaultPrimaryTilesetLabel(); - else - mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); + const QString defaultPrimaryTileset = project->getDefaultPrimaryTilesetLabel(); + QString primaryTilesetLabel = tilesets.value(mapPrimaryTilesetNum, defaultPrimaryTileset); + if (!project->primaryTilesetLabels.contains(primaryTilesetLabel)) { + // AdvanceMap's primary tileset value points to a secondary tileset. Ignore it. + primaryTilesetLabel = defaultPrimaryTileset; + } + const QString defaultSecondaryTileset = project->getDefaultSecondaryTilesetLabel(); + QString secondaryTilesetLabel = tilesets.value(mapSecondaryTilesetNum, defaultSecondaryTileset); + if (!project->secondaryTilesetLabels.contains(secondaryTilesetLabel)) { + // AdvanceMap's secondary tileset value points to a primary tileset. Ignore it. + secondaryTilesetLabel = defaultSecondaryTileset; + } - if (mapSecondaryTilesetNum > tilesets.size()) - mapLayout->tileset_secondary_label = project->getDefaultSecondaryTilesetLabel(); - else - mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); + mapLayout->tileset_primary_label = primaryTilesetLabel; + mapLayout->tileset_secondary_label = secondaryTilesetLabel; mapLayout->blockdata = blockdata; diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 785f0389..98d16e62 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -62,10 +62,10 @@ void NewLayoutDialog::refresh() { if (this->layoutToCopy) { // If we're importing a layout then some settings will be enforced. ui->newLayoutForm->setSettings(this->layoutToCopy->settings()); - ui->newLayoutForm->setDisabled(true); + ui->newLayoutForm->setDimensionsDisabled(true); } else { ui->newLayoutForm->setSettings(*settings); - ui->newLayoutForm->setDisabled(false); + ui->newLayoutForm->setDimensionsDisabled(false); } ui->lineEdit_Name->setText(settings->name); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index bc750ddd..11bdc0be 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -40,10 +40,9 @@ void NewLayoutForm::initUi(Project *project) { } } -void NewLayoutForm::setDisabled(bool disabled) { +void NewLayoutForm::setDimensionsDisabled(bool disabled) { ui->groupBox_MapDimensions->setDisabled(disabled); ui->groupBox_BorderDimensions->setDisabled(disabled); - ui->groupBox_Tilesets->setDisabled(disabled); } void NewLayoutForm::setSettings(const Layout::Settings &settings) { @@ -111,7 +110,7 @@ bool NewLayoutForm::validatePrimaryTileset(bool allowEmpty) { if (name.isEmpty()) { if (!allowEmpty) errorText = QString("The Primary Tileset cannot be empty."); } else if (ui->comboBox_PrimaryTileset->findText(name) < 0) { - errorText = QString("The Primary Tileset '%1' does not exist.").arg(ui->label_PrimaryTileset->text()).arg(name); + errorText = QString("The Primary Tileset '%1' does not exist.").arg(name); } bool isValid = errorText.isEmpty(); From d0337a7ae383e3597c4b64f2b1f79896ea15bd37 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Jul 2025 16:45:31 -0400 Subject: [PATCH 41/71] 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 2a74f0dc0e35d53b7e8c1fdc3d6a2c469c545e3b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 1 Aug 2025 21:12:27 -0400 Subject: [PATCH 42/71] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b29c7d54..a24cbfc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ## [Unreleased] ### Added +- Add `View > Show Unused Colors` to the Palette Editor. +- Add `Tools > Find Color Usage` to the Palette Editor. This opens a dialog showing which metatiles use a particular color. - Add an `Export Metatiles Image` option to the Tileset Editor that provides many more options for customizing metatile images. - Add an `Export Porytiles Layer Images` option to the Tileset Editor, which is a shortcut for individually exporting layer images that Porytiles can use. - Add an option under `Preferences` to include common scripts in the autocomplete for Script labels. From db9ecd744f982791cf627366788920753694fd50 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 1 Aug 2025 21:51:01 -0400 Subject: [PATCH 43/71] 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 44/71] 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 45/71] 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 46/71] 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 { From 9f4125b6bd28bd2f073271f133442bb31b14bbe9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 5 Aug 2025 16:55:51 -0400 Subject: [PATCH 47/71] Prefix shortcut names with menu path --- include/ui/shortcutseditor.h | 1 + src/mainwindow.cpp | 7 +++++- src/ui/shortcutseditor.cpp | 45 ++++++++++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/include/ui/shortcutseditor.h b/include/ui/shortcutseditor.h index e44c8896..0654c2a2 100644 --- a/include/ui/shortcutseditor.h +++ b/include/ui/shortcutseditor.h @@ -40,6 +40,7 @@ private: QHash> multiKeyEdits_objects; void parseObjectList(const QObjectList &objectList); + void parseObject(const QObject *object, QMap *objects_labels, QMap *objects_prefixes); QString getLabel(const QObject *object) const; bool stringPropertyIsNotEmpty(const QObject *object, const char *name) const; void populateMainContainer(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac7c2385..8ba318b9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2271,7 +2271,12 @@ void MainWindow::initShortcutsEditor() { connectSubEditorsToShortcutsEditor(); - shortcutsEditor->setShortcutableObjects(shortcutableObjects()); + auto objectList = shortcutableObjects(); + for (auto *menu : findChildren()) { + if (!menu->objectName().isEmpty()) + objectList.append(qobject_cast(menu)); + } + shortcutsEditor->setShortcutableObjects(objectList); } void MainWindow::connectSubEditorsToShortcutsEditor() { diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index d0ef943d..8ae1f67d 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -12,6 +12,7 @@ #include #include #include +#include ShortcutsEditor::ShortcutsEditor(QWidget *parent) : @@ -78,11 +79,43 @@ void ShortcutsEditor::resetShortcuts() { } } +void ShortcutsEditor::parseObject(const QObject *object, QMap *objects_labels, QMap *objects_prefixes) { + auto menu = dynamic_cast(object); + if (menu) { + // If a menu is provided we'll use it to create prefixes for any of the menu's actions, + // and automatically insert its actions in the shortcut list (if they weren't present already). + // The prefixing assumes the provided object list is in inheritance order. + // These prefixes are important for differentiating actions that may have the same display text + // but appear in different menus. + for (const auto &action : menu->actions()) { + if (!menu->title().isEmpty()) { + auto prefix = QString("%1%2 > ") + .arg(objects_prefixes->value(menu->menuAction())) // If this is a sub-menu, it may itself have a prefix. + .arg(menu->title()); + objects_prefixes->insert(action, prefix); + } + parseObject(action, objects_labels, objects_prefixes); + } + } else if (object && !object->objectName().isEmpty() && !object->objectName().startsWith("_q_")) { + QString label = getLabel(object); + if (!label.isEmpty()) { + objects_labels->insert(object, label); + } + } +} + void ShortcutsEditor::parseObjectList(const QObjectList &objectList) { - for (auto *object : objectList) { - const auto label = getLabel(object); - if (!label.isEmpty() && !object->objectName().isEmpty() && !object->objectName().startsWith("_q_")) - labels_objects.insert(label, object); + QMap objects_labels; + QMap objects_prefixes; + for (const auto &object : objectList) { + parseObject(object, &objects_labels, &objects_prefixes); + } + + // Sort alphabetically by label + this->labels_objects.clear(); + for (auto it = objects_labels.constBegin(); it != objects_labels.constEnd(); it++) { + QString fullLabel = objects_prefixes.value(it.key()) + it.value(); + this->labels_objects.insert(fullLabel, it.key()); } } @@ -164,7 +197,9 @@ QList ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) cons void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyEdit *sibling) { const auto duplicateKeySequence = sender->keySequences().last(); - const auto siblingLabel = getLabel(multiKeyEdits_objects.value(sibling)); + const auto siblingLabel = this->labels_objects.key(multiKeyEdits_objects.value(sibling)); + if (siblingLabel.isEmpty()) + return; const auto message = QString( "Shortcut '%1' is already used by '%2', would you like to replace it?") .arg(duplicateKeySequence.toString()).arg(siblingLabel); From 7f3ef485158d061066f3a928cb4121386640ea1c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 5 Aug 2025 16:57:42 -0400 Subject: [PATCH 48/71] Fix Porytiles sub-menu text --- forms/tileseteditor.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 895fae0f..371e8623 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -648,7 +648,7 @@ - Export Porytiles Layer Images... + Export Porytiles Layer Images From b8c9fb8ccb15aa9a343736df32f7d5f669a0714e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 5 Aug 2025 16:59:29 -0400 Subject: [PATCH 49/71] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a24cbfc7..d7c95148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - The scroll position of the map view now remains the same between the Connections tab and the Map/Events tabs. - The Move tool now behaves more like a traditional pan tool (with no momentum). - The map image exporter now uses a checkered background to indicate transparency. +- Invalid tile IDs are now rendered as magenta (like invalid metatiles), instead of rendering the same as a transparent tile. +- Full menu paths are now listed for shortcuts in the Shortcuts Editor. ### Fixed - Fix crash when rendering tiles with invalid palette numbers. From 301666e5bd70a9d845e3cd90ceaf9f05743e5e3b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 5 Aug 2025 22:59:39 -0400 Subject: [PATCH 50/71] Fix Shortcuts Editor displaying duplicate shortcut prompt repeatedly --- include/ui/shortcutseditor.h | 1 - src/ui/shortcutseditor.cpp | 38 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/include/ui/shortcutseditor.h b/include/ui/shortcutseditor.h index 0654c2a2..6f136773 100644 --- a/include/ui/shortcutseditor.h +++ b/include/ui/shortcutseditor.h @@ -49,7 +49,6 @@ private: void addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext); QList siblings(MultiKeyEdit *multiKeyEdit) const; void promptUserOnDuplicateFound(MultiKeyEdit *current, MultiKeyEdit *sender); - void removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit); void saveShortcuts(); void resetShortcuts(); diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index 8ae1f67d..4bbc7c25 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -184,9 +184,12 @@ void ShortcutsEditor::checkForDuplicates(const QKeySequence &keySequence) { if (!sender_multiKeyEdit) return; - for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit)) - if (sibling_multiKeyEdit->contains(keySequence)) + for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit)) { + if (sibling_multiKeyEdit->contains(keySequence)) { promptUserOnDuplicateFound(sender_multiKeyEdit, sibling_multiKeyEdit); + break; + } + } } QList ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) const { @@ -200,25 +203,22 @@ void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyE const auto siblingLabel = this->labels_objects.key(multiKeyEdits_objects.value(sibling)); if (siblingLabel.isEmpty()) return; - const auto message = QString( - "Shortcut '%1' is already used by '%2', would you like to replace it?") - .arg(duplicateKeySequence.toString()).arg(siblingLabel); + const auto message = QString("Shortcut '%1' is already used by '%2', would you like to replace it?") + .arg(duplicateKeySequence.toString()) + .arg(siblingLabel); - const auto result = QMessageBox::question( - this, QApplication::applicationName(), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + // QKeySequenceEdits::keySequenceChange fires when editing finishes on a QKeySequenceEdit, + // even if no change occurs. Displaying our question prompt will cause the edit to lose focus + // and fire another signal, which would cause another "duplicate shortcut" prompt to appear. + // For this reason we need to block their signals before the message is displayed. + const QSignalBlocker b_Sender(sender); + const QSignalBlocker b_Sibling(sibling); - if (result == QMessageBox::Yes) - removeKeySequence(duplicateKeySequence, sibling); - else - removeKeySequence(duplicateKeySequence, sender); - - activateWindow(); -} - -void ShortcutsEditor::removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit) { - multiKeyEdit->blockSignals(true); - multiKeyEdit->removeOne(keySequence); - multiKeyEdit->blockSignals(false); + if (QuestionMessage::show(message, this) == QMessageBox::Yes) { + sibling->removeOne(duplicateKeySequence); + } else { + sender->removeOne(duplicateKeySequence); + } } void ShortcutsEditor::dialogButtonClicked(QAbstractButton *button) { From 66c8302c005959393a85effe2f570bec1fb883b6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 5 Aug 2025 23:30:05 -0400 Subject: [PATCH 51/71] Fix Shortcuts Editor clear text buttons --- CHANGELOG.md | 2 ++ src/ui/multikeyedit.cpp | 8 -------- src/ui/shortcutseditor.cpp | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c95148..c4ee2fa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix click-drag map selections behaving unexpectedly when the cursor is outside the map grid. - Fix events being dragged in negative coordinates lagging behind the cursor. - Fix the shortcut for duplicating events working while on the Connections tab. +- Fix the Shortcuts Editor displaying the duplicate shortcut prompt repeatedly. +- Fix clear text button on the left in each row of the Shortcuts Editor also clearing the shortcut on the right. - Fix Undo/Redo ignoring the automatic resizing that occurs if a layout/border was an unexpected size. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. - Fix broken error message for the primary tileset on the new map/layout dialogs. diff --git a/src/ui/multikeyedit.cpp b/src/ui/multikeyedit.cpp index cf11210f..ec76e918 100644 --- a/src/ui/multikeyedit.cpp +++ b/src/ui/multikeyedit.cpp @@ -137,14 +137,6 @@ void MultiKeyEdit::addNewKeySequenceEdit() { connect(lineEdit, &QLineEdit::customContextMenuRequested, this, &MultiKeyEdit::customContextMenuRequested); - // Gross way to connect the line edit's clear button. - auto actions = lineEdit->findChildren(); - if (!actions.isEmpty()) { - connect(actions.first(), &QAction::triggered, this, [this, keySequenceEdit]() { - removeOne(keySequenceEdit->keySequence()); - }); - } - layout()->addWidget(keySequenceEdit); this->keySequenceEdits.append(keySequenceEdit); } diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index 4bbc7c25..f2f8083b 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -207,7 +207,7 @@ void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyE .arg(duplicateKeySequence.toString()) .arg(siblingLabel); - // QKeySequenceEdits::keySequenceChange fires when editing finishes on a QKeySequenceEdit, + // QKeySequenceEdit::keySequenceChanged fires when editing finishes on a QKeySequenceEdit, // even if no change occurs. Displaying our question prompt will cause the edit to lose focus // and fire another signal, which would cause another "duplicate shortcut" prompt to appear. // For this reason we need to block their signals before the message is displayed. From 73b774be7d1f36dc5407e0a0da69796d2d82a161 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 10:40:48 -0400 Subject: [PATCH 52/71] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ee2fa5..4f6fb02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ### Added - Add `View > Show Unused Colors` to the Palette Editor. - Add `Tools > Find Color Usage` to the Palette Editor. This opens a dialog showing which metatiles use a particular color. +- Add `Edit > Swap Metatiles` to the Tileset Editor. While in this mode, selecting two metatiles in the selector will swap their positions. When changes to the tilesets are saved these relocations will be applied to all layouts that use the relevant tileset(s). +- Add `View > Layer Arrangement` to the Tileset Editor, which changes whether the metatile layer view is oriented vertically (default) or horizontally. - Add an `Export Metatiles Image` option to the Tileset Editor that provides many more options for customizing metatile images. - Add an `Export Porytiles Layer Images` option to the Tileset Editor, which is a shortcut for individually exporting layer images that Porytiles can use. - Add an option under `Preferences` to include common scripts in the autocomplete for Script labels. @@ -22,6 +24,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - The Move tool now behaves more like a traditional pan tool (with no momentum). - The map image exporter now uses a checkered background to indicate transparency. - Invalid tile IDs are now rendered as magenta (like invalid metatiles), instead of rendering the same as a transparent tile. +- While holding down `Ctrl` (`Cmd` on macOS) painting on the metatile layer view will now only change the tile's palette. - Full menu paths are now listed for shortcuts in the Shortcuts Editor. ### Fixed @@ -38,6 +41,9 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix the Shortcuts Editor displaying the duplicate shortcut prompt repeatedly. - Fix clear text button on the left in each row of the Shortcuts Editor also clearing the shortcut on the right. - Fix Undo/Redo ignoring the automatic resizing that occurs if a layout/border was an unexpected size. +- Fix Undo/Redo in the Tileset and Palette Editors and Paste in the Tileset Editor appearing enabled even when they don't do anything. +- Fix `Ctrl+Shift+Z` not being set as a default shortcut for Redo in the Palette Editor like it is for other windows. +- Fix the Tileset Editor's status bar not updating while selecting tiles in the metatile layer view. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. - Fix broken error message for the primary tileset on the new map/layout dialogs. - Fix the dialog for duplicating/importing a map layout not allowing the tilesets to be changed. From a5823f04f183e9ca0273f67391a5f5b4d27b6784 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 11:33:02 -0400 Subject: [PATCH 53/71] Fix new event data being discarded on maps with shared_events_map --- CHANGELOG.md | 1 + forms/mainwindow.ui | 3 -- include/core/events.h | 1 + include/core/map.h | 3 ++ src/core/events.cpp | 10 +++++++ src/core/map.cpp | 13 +++++++-- src/mainwindow.cpp | 9 ++++++ src/project.cpp | 64 ++++++++++++++++--------------------------- 8 files changed, 59 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6fb02f..585f2b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Invalid tile IDs are now rendered as magenta (like invalid metatiles), instead of rendering the same as a transparent tile. - While holding down `Ctrl` (`Cmd` on macOS) painting on the metatile layer view will now only change the tile's palette. - Full menu paths are now listed for shortcuts in the Shortcuts Editor. +- Adding new event data to a map that has a `shared_events_map` will now remove the `shared_events_map`, rather than discard the event data. ### Fixed - Fix crash when rendering tiles with invalid palette numbers. diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 30520979..96567305 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1638,9 +1638,6 @@ - - There are no events on the current map. - Qt::AlignmentFlag::AlignCenter diff --git a/include/core/events.h b/include/core/events.h index 0e8af1f7..816cf953 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -161,6 +161,7 @@ public: QString getIdName() const { return this->idName; } static QString groupToString(Event::Group group); + static QString groupToJsonKey(Event::Group group); static QString typeToString(Event::Type type); static QString typeToJsonKey(Event::Type type); static Event::Type typeFromJsonKey(QString type); diff --git a/include/core/map.h b/include/core/map.h index 734d5945..f3a377c1 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -64,6 +64,8 @@ public: void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } void setSharedScriptsMap(const QString &sharedScriptsMap); + bool isInheritingEvents() const { return !m_sharedEventsMap.isEmpty() && !hasEvents(); } + bool isInheritingScripts() const { return !m_sharedScriptsMap.isEmpty(); } QString sharedEventsMap() const { return m_sharedEventsMap; } QString sharedScriptsMap() const { return m_sharedScriptsMap; } @@ -85,6 +87,7 @@ public: void addEvent(Event *); int getIndexOfEvent(Event *) const; bool hasEvent(Event *) const; + bool hasEvents() const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilepath() const; diff --git a/src/core/events.cpp b/src/core/events.cpp index 58a2b997..194a7ab1 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -63,6 +63,16 @@ void Event::modify() { this->map->modify(); } +QString Event::groupToJsonKey(Event::Group group) { + static const QMap map = { + {Event::Group::Object, "object_events"}, + {Event::Group::Warp, "warp_events"}, + {Event::Group::Coord, "coord_events"}, + {Event::Group::Bg, "bg_events"}, + }; + return map.value(group); +} + const QMap groupToStringMap = { {Event::Group::Object, "Object"}, {Event::Group::Warp, "Warp"}, diff --git a/src/core/map.cpp b/src/core/map.cpp index 01ed855f..f7b75838 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -280,14 +280,23 @@ int Map::getNumEvents(Event::Group group) const { if (group == Event::Group::None) { // Total number of events int numEvents = 0; - for (auto i = m_events.constBegin(); i != m_events.constEnd(); i++) { - numEvents += i.value().length(); + for (auto it = m_events.constBegin(); it != m_events.constEnd(); it++) { + numEvents += it.value().length(); } return numEvents; } return m_events[group].length(); } +bool Map::hasEvents() const { + for (auto it = m_events.constBegin(); it != m_events.constEnd(); it++) { + if (!it.value().isEmpty()) { + return true; + } + } + return false; +} + void Map::removeEvent(Event *event) { for (auto i = m_events.begin(); i != m_events.end(); i++) { i.value().removeAll(event); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f3bc15f8..baf4c056 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1461,6 +1461,7 @@ void MainWindow::clearProjectUI() { ui->comboBox_LayoutSelector->clear(); this->mapHeaderForm->clear(); + ui->label_NoEvents->setText(""); prefab.clearPrefabUi(); @@ -2479,6 +2480,14 @@ void MainWindow::updateSelectedEvents() { } else { ui->tabWidget_EventType->hide(); + + if (this->editor->map && this->editor->map->isInheritingEvents()) { + QString message = QString("NOTE: This map inherits events from %1." + "
Adding any events will separate it from that map.").arg(this->editor->map->sharedEventsMap()); + ui->label_NoEvents->setText(message); + } else { + ui->label_NoEvents->setText(QStringLiteral("There are no events on the current map.")); + } ui->label_NoEvents->show(); } } diff --git a/src/project.cpp b/src/project.cpp index f26a49ad..08ed9611 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -360,10 +360,10 @@ QSet Project::getTopLevelMapFields() const { "show_map_name", "battle_scene", "connections", - "object_events", - "warp_events", - "coord_events", - "bg_events", + Event::groupToJsonKey(Event::Group::Object), + Event::groupToJsonKey(Event::Group::Warp), + Event::groupToJsonKey(Event::Group::Coord), + Event::groupToJsonKey(Event::Group::Bg), "shared_events_map", "shared_scripts_map", }; @@ -462,10 +462,10 @@ bool Project::loadMapData(Map* map) { static const QMap defaultEventTypes = { // Map of the expected keys for each event group, and the default type of that group. // If the default type is Type::None then each event must specify its type, or its an error. - {"object_events", Event::Type::Object}, - {"warp_events", Event::Type::Warp}, - {"coord_events", Event::Type::None}, - {"bg_events", Event::Type::None}, + {Event::groupToJsonKey(Event::Group::Object), Event::Type::Object}, + {Event::groupToJsonKey(Event::Group::Warp), Event::Type::Warp}, + {Event::groupToJsonKey(Event::Group::Coord), Event::Type::None}, + {Event::groupToJsonKey(Event::Group::Bg), Event::Type::None}, }; for (auto i = defaultEventTypes.constBegin(); i != defaultEventTypes.constEnd(); i++) { QString eventGroupKey = i.key(); @@ -1396,43 +1396,27 @@ bool Project::saveMap(Map *map, bool skipLayout) { mapObj["connections"] = OrderedJson(); } - if (map->sharedEventsMap().isEmpty()) { - // Object events - OrderedJson::array objectEventsArr; - for (const auto &event : map->getEvents(Event::Group::Object)){ - objectEventsArr.push_back(event->buildEventJson(this)); - } - mapObj["object_events"] = objectEventsArr; - - - // Warp events - OrderedJson::array warpEventsArr; - for (const auto &event : map->getEvents(Event::Group::Warp)) { - warpEventsArr.push_back(event->buildEventJson(this)); - } - mapObj["warp_events"] = warpEventsArr; - - // Coord events - OrderedJson::array coordEventsArr; - for (const auto &event : map->getEvents(Event::Group::Coord)) { - coordEventsArr.push_back(event->buildEventJson(this)); - } - mapObj["coord_events"] = coordEventsArr; - - // Bg Events - OrderedJson::array bgEventsArr; - for (const auto &event : map->getEvents(Event::Group::Bg)) { - bgEventsArr.push_back(event->buildEventJson(this)); - } - mapObj["bg_events"] = bgEventsArr; - } else { + if (map->isInheritingEvents()) { mapObj["shared_events_map"] = map->sharedEventsMap(); } - - if (!map->sharedScriptsMap().isEmpty()) { + if (map->isInheritingScripts()) { mapObj["shared_scripts_map"] = map->sharedScriptsMap(); } + if (!map->isInheritingEvents()) { + auto buildEventsJson = [this, map](Event::Group group, OrderedJson::object *json) { + OrderedJson::array arr; + for (const auto &event : map->getEvents(group)){ + arr.push_back(event->buildEventJson(this)); + } + (*json)[Event::groupToJsonKey(group)] = arr; + }; + buildEventsJson(Event::Group::Object, &mapObj); + buildEventsJson(Event::Group::Warp, &mapObj); + buildEventsJson(Event::Group::Coord, &mapObj); + buildEventsJson(Event::Group::Bg, &mapObj); + } + // Update the global heal locations array using the Map's heal location events. // This won't get saved to disc until Project::saveHealLocations is called. QList hlEvents; From bc9fbe6e72461bd3b594848de310122c1d37c6a6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 13:44:29 -0400 Subject: [PATCH 54/71] Remove some unprotected usage of QList::at --- include/ui/layoutpixmapitem.h | 6 +++ include/ui/metatileselector.h | 10 ++--- src/editor.cpp | 2 +- src/project.cpp | 2 +- src/scriptapi/scripting.cpp | 2 +- src/ui/bordermetatilespixmapitem.cpp | 2 +- src/ui/currentselectedmetatilespixmapitem.cpp | 2 +- src/ui/cursortilerect.cpp | 3 +- src/ui/customscriptseditor.cpp | 2 +- src/ui/layoutpixmapitem.cpp | 39 ++++++++++--------- src/ui/prefab.cpp | 2 +- src/ui/tileseteditor.cpp | 4 +- src/ui/tileseteditortileselector.cpp | 13 +------ src/ui/wildmonchart.cpp | 8 ++-- 14 files changed, 49 insertions(+), 48 deletions(-) diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h index 9d908cc9..04f0500a 100644 --- a/include/ui/layoutpixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -75,6 +75,8 @@ public: bool fromScriptCall = false); void floodFillSmartPath(int initialX, int initialY, bool fromScriptCall = false); + static bool isSmartPathSize(const QSize &size) { return size.width() == smartPathWidth && size.height() == smartPathHeight; } + virtual void pick(QGraphicsSceneMouseEvent*); virtual void select(QGraphicsSceneMouseEvent*); virtual void shift(QGraphicsSceneMouseEvent*); @@ -88,7 +90,11 @@ public: private: void paintSmartPath(int x, int y, bool fromScriptCall = false); + static bool isValidSmartPathSelection(MetatileSelection selection); static QList smartPathTable; + static constexpr int smartPathWidth = 3; + static constexpr int smartPathHeight = 3; + static constexpr int smartPathMiddleIndex = (smartPathWidth / 2) + ((smartPathHeight / 2) * smartPathWidth); QPoint lastMetatileSelectionPos = QPoint(-1,-1); unsigned actionId_ = 0; diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index 8bcb6d5c..663ccd5a 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -9,15 +9,15 @@ struct MetatileSelectionItem { - bool enabled; - uint16_t metatileId; + bool enabled = false; + uint16_t metatileId = 0; }; struct CollisionSelectionItem { - bool enabled; - uint16_t collision; - uint16_t elevation; + bool enabled = false; + uint16_t collision = 0; + uint16_t elevation = 0; }; struct MetatileSelection diff --git a/src/editor.cpp b/src/editor.cpp index 9c6a7d0a..f5657642 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -527,7 +527,7 @@ void Editor::configureEncounterJSON(QWidget *window) { auto createNewSlot = [&fieldSlots, &tempFields, &updateTotal](int index, EncounterField ¤tField) { QLabel *indexLabel = new QLabel(QString("Index: %1").arg(QString::number(index))); QSpinBox *chanceSpinner = new QSpinBox; - int chance = currentField.encounterRates.at(index); + int chance = currentField.encounterRates.value(index); chanceSpinner->setMinimum(1); chanceSpinner->setMaximum(9999); chanceSpinner->setValue(chance); diff --git a/src/project.cpp b/src/project.cpp index 08ed9611..3746e04f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1299,7 +1299,7 @@ void Project::setNewLayoutBorder(Layout *layout) { } else { // Fill the border with the default metatiles from the config. for (int i = 0; i < width * height; i++) { - layout->border.append(projectConfig.newMapBorderMetatileIds.at(i)); + layout->border.append(projectConfig.newMapBorderMetatileIds.value(i)); } } diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 148a75fe..be14a933 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -41,7 +41,7 @@ Scripting::Scripting(MainWindow *mainWindow) { const QStringList paths = userConfig.getCustomScriptPaths(); const QList enabled = userConfig.getCustomScriptsEnabled(); for (int i = 0; i < paths.length(); i++) { - if (enabled.at(i)) + if (enabled.value(i, true)) this->filepaths.append(paths.at(i)); } this->loadModules(this->filepaths); diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index 46b0a18c..97096423 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -22,7 +22,7 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) 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); + MetatileSelectionItem item = selection.metatileItems.value(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 627c5e12..221c48e0 100644 --- a/src/ui/currentselectedmetatilespixmapitem.cpp +++ b/src/ui/currentselectedmetatilespixmapitem.cpp @@ -15,7 +15,7 @@ QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) { int y = j * Metatile::pixelHeight(); QPoint metatile_origin = QPoint(x, y); int index = j * selection.dimensions.width() + i; - MetatileSelectionItem item = selection.metatileItems.at(index); + MetatileSelectionItem item = selection.metatileItems.value(index); if (item.enabled) { QImage metatile_image = getMetatileImage(item.metatileId, layout); painter.drawImage(metatile_origin, metatile_image); diff --git a/src/ui/cursortilerect.cpp b/src/ui/cursortilerect.cpp index 8b93ada1..d8c50a04 100644 --- a/src/ui/cursortilerect.cpp +++ b/src/ui/cursortilerect.cpp @@ -1,4 +1,5 @@ #include "cursortilerect.h" +#include "layoutpixmapitem.h" #include "log.h" CursorTileRect::CursorTileRect(const QSize &tileSize, const QRgb &color, QGraphicsItem *parent) @@ -46,7 +47,7 @@ void CursorTileRect::updateSelectionSize(const QSize &size) { } bool CursorTileRect::smartPathInEffect() const { - return !m_rightClickSelectionAnchored && m_smartPathMode && m_selectionSize == QSize(3,3); + return !m_rightClickSelectionAnchored && m_smartPathMode && LayoutPixmapItem::isSmartPathSize(m_selectionSize); } void CursorTileRect::updateLocation(int coordX, int coordY) { diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index 10d060c2..0b3dca1e 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -21,7 +21,7 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) : const QStringList paths = userConfig.getCustomScriptPaths(); const QList enabled = userConfig.getCustomScriptsEnabled(); for (int i = 0; i < paths.length(); i++) - this->displayScript(paths.at(i), enabled.at(i)); + this->displayScript(paths.at(i), enabled.value(i, true)); connect(ui->button_Help, &QAbstractButton::clicked, this, &CustomScriptsEditor::openManual); connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript); diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index c7489598..95571661 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -27,13 +27,13 @@ void LayoutPixmapItem::paint(QGraphicsSceneMouseEvent *event) { bool shiftPressed = event->modifiers() & Qt::ShiftModifier; QSize selectionDimensions = this->metatileSelector->getSelectionDimensions(); if (settings->smartPathsEnabled) { - if (!shiftPressed && selectionDimensions == QSize(3,3)) { + if (!shiftPressed && isSmartPathSize(selectionDimensions)) { paintSmartPath(pos.x(), pos.y()); } else { paintNormal(pos.x(), pos.y()); } } else { - if (shiftPressed && selectionDimensions == QSize(3,3)) { + if (shiftPressed && isSmartPathSize(selectionDimensions)) { paintSmartPath(pos.x(), pos.y()); } else { paintNormal(pos.x(), pos.y()); @@ -91,7 +91,7 @@ void LayoutPixmapItem::shift(int xDelta, int yDelta, bool fromScriptCall) { destY %= this->layout->getHeight(); int blockIndex = j * this->layout->getWidth() + i; - Block srcBlock = oldMetatiles.at(blockIndex); + Block srcBlock = oldMetatiles.value(blockIndex); this->layout->setBlock(destX, destY, srcBlock); } @@ -126,12 +126,12 @@ void LayoutPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { Block block; if (this->layout->getBlock(actualX, actualY, &block)) { int index = j * selection.dimensions.width() + i; - MetatileSelectionItem item = selection.metatileItems.at(index); + MetatileSelectionItem item = selection.metatileItems.value(index); if (!item.enabled) continue; block.setMetatileId(item.metatileId); if (selection.hasCollision && selection.collisionItems.length() == selection.metatileItems.length()) { - CollisionSelectionItem collisionItem = selection.collisionItems.at(index); + CollisionSelectionItem collisionItem = selection.collisionItems.value(index); block.setCollision(collisionItem.collision); block.setElevation(collisionItem.elevation); } @@ -177,8 +177,11 @@ bool isSmartPathTile(QList metatileItems, uint16_t metati return false; } -bool isValidSmartPathSelection(MetatileSelection selection) { - if (selection.dimensions != QSize(3,3)) +bool LayoutPixmapItem::isValidSmartPathSelection(MetatileSelection selection) { + if (!isSmartPathSize(selection.dimensions)) + return false; + + if (selection.metatileItems.length() != (LayoutPixmapItem::smartPathWidth * LayoutPixmapItem::smartPathHeight)) return false; for (int i = 0; i < selection.metatileItems.length(); i++) { @@ -195,13 +198,13 @@ void LayoutPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { return; // Shift to the middle tile of the smart path selection. - uint16_t openMetatileId = selection.metatileItems.at(4).metatileId; + uint16_t openMetatileId = selection.metatileItems.at(smartPathMiddleIndex).metatileId; uint16_t openCollision = 0; uint16_t openElevation = 0; bool setCollisions = false; if (selection.hasCollision && selection.collisionItems.length() == selection.metatileItems.length()) { - openCollision = selection.collisionItems.at(4).collision; - openElevation = selection.collisionItems.at(4).elevation; + openCollision = selection.collisionItems.at(smartPathMiddleIndex).collision; + openElevation = selection.collisionItems.at(smartPathMiddleIndex).elevation; setCollisions = true; } @@ -356,7 +359,7 @@ void LayoutPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) metatiles.append(block.metatileId()); } int blockIndex = y * this->layout->getWidth() + x; - block = this->layout->blockdata.at(blockIndex); + block = this->layout->blockdata.value(blockIndex); auto collision = block.collision(); auto elevation = block.elevation(); collisions.append(QPair(collision, elevation)); @@ -377,7 +380,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 == QSize(3,3)) + if ((this->settings->smartPathsEnabled || smartPathsEnabled) && isSmartPathSize(selection.dimensions)) this->floodFillSmartPath(pos.x(), pos.y()); else this->floodFill(pos.x(), pos.y()); @@ -435,7 +438,7 @@ void LayoutPixmapItem::magicFill( 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) { + if (index < selectedMetatiles.length() && selectedMetatiles.at(index).enabled) { block.setMetatileId(selectedMetatiles.at(index).metatileId); if (setCollisions) { CollisionSelectionItem item = selectedCollisions.at(index); @@ -495,12 +498,12 @@ void LayoutPixmapItem::floodFill( 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 metatileId = selectedMetatiles.value(index).metatileId; uint16_t old_metatileId = block.metatileId(); - if (selectedMetatiles.at(index).enabled && (selectedMetatiles.count() != 1 || old_metatileId != metatileId)) { + if (selectedMetatiles.value(index).enabled && (selectedMetatiles.count() != 1 || old_metatileId != metatileId)) { block.setMetatileId(metatileId); if (setCollisions) { - CollisionSelectionItem item = selectedCollisions.at(index); + CollisionSelectionItem item = selectedCollisions.value(index); block.setCollision(item.collision); block.setElevation(item.elevation); } @@ -535,12 +538,12 @@ void LayoutPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromS return; // Shift to the middle tile of the smart path selection. - uint16_t openMetatileId = selection.metatileItems.at(4).metatileId; + uint16_t openMetatileId = selection.metatileItems.at(smartPathMiddleIndex).metatileId; uint16_t openCollision = 0; uint16_t openElevation = 0; bool setCollisions = false; if (selection.hasCollision && selection.collisionItems.length() == selection.metatileItems.length()) { - CollisionSelectionItem item = selection.collisionItems.at(4); + CollisionSelectionItem item = selection.collisionItems.at(smartPathMiddleIndex); openCollision = item.collision; openElevation = item.elevation; setCollisions = true; diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index edbb7eac..7a1e96d2 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -120,7 +120,7 @@ void Prefab::savePrefabs() { 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); + auto metatileItem = item.selection.metatileItems.value(index); if (metatileItem.enabled) { OrderedJson::object metatileObj; metatileObj["x"] = x; diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 17074063..9dbfe6fd 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -459,7 +459,7 @@ void TilesetEditor::drawSelectedTiles() { int tileIndex = 0; for (int y = 0; y < dimensions.height(); y++) { for (int x = 0; x < dimensions.width(); x++) { - auto tile = tiles.at(tileIndex++); + auto tile = tiles.value(tileIndex++); QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(imgTileWidth, imgTileHeight); tile.flip(&tileImage); painter.drawImage(x * imgTileWidth, y * imgTileHeight, tileImage); @@ -543,7 +543,7 @@ void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) 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++); + const Tile srcTile = tiles.value(srcTileIndex++); if (paletteOnly) { if (srcTile.palette == destTile.palette) continue; // Ignore no-ops for edit history diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index e82e7dc6..026b3d58 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -146,7 +146,6 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, QList> tileMatrix; for (int j = 0; j < height; j++) { QList row; - QList layerRow; for (int i = 0; i < width; i++) { int index = i + j * width; Tile tile = selected.value(index); @@ -155,17 +154,9 @@ QList TilesetEditorTileSelector::buildSelectedTiles(int width, int height, if (this->paletteChanged) tile.palette = this->paletteId; if (this->xFlip) - layerRow.prepend(tile); + row.prepend(tile); else - layerRow.append(tile); - - // 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) { - row.append(layerRow); - layerRow.clear(); - } + row.append(tile); } if (this->yFlip) tileMatrix.prepend(row); diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index ea5b4814..30585418 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -392,10 +392,10 @@ void WildMonChart::updateTheme() { saveSpeciesColors(static_cast(chart->series().at(0))->barSets()); chart = ui->chartView_LevelDistribution->chart(); - if (chart) { - chart->setTheme(theme); - applySpeciesColors(static_cast(chart->series().at(0))->barSets()); - } + if (!chart || chart->series().isEmpty()) + return; + chart->setTheme(theme); + applySpeciesColors(static_cast(chart->series().at(0))->barSets()); } void WildMonChart::saveSpeciesColors(const QList &barSets) { From 5da0655a15edeb6d789d77c43d0ba39e86e97a12 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 16:41:31 -0400 Subject: [PATCH 55/71] Fix crash when changing dimensions in certain API callbacks --- CHANGELOG.md | 1 + include/mainwindow.h | 1 + src/scriptapi/apimap.cpp | 23 +++++++++++++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 585f2b16..13c899c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp ### Fixed - Fix crash when rendering tiles with invalid palette numbers. - Fix crash when opening the Tileset Editor for tilesets with no metatiles. +- Fix crash when changing the map/border size in certain API callbacks. - Fix metatile images exporting at 2x scale. - Fix display errors when a project's metatile limits are not divisible by 8. - Fix incorrect dividing line position for primary tiles images that are smaller than the maximum size. diff --git a/include/mainwindow.h b/include/mainwindow.h index 841cdcf6..be4cc258 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -59,6 +59,7 @@ public: // Scripting API Q_INVOKABLE QJSValue getBlock(int x, int y); void tryRedrawMapArea(bool forceRedraw); + void redrawResizedMapArea(); void tryCommitMapChanges(bool commitChanges); Q_INVOKABLE void setBlock(int x, int y, int metatileId, int collision, int elevation, bool forceRedraw = true, bool commitChanges = true); Q_INVOKABLE void setBlock(int x, int y, int rawValue, bool forceRedraw = true, bool commitChanges = true); diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index d87a2b0b..09e4c593 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -41,6 +41,17 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) { } } +void MainWindow::redrawResizedMapArea() { + // We're calling this on a timer because we want to defer the redraw + // until it's time to process events. This avoids some potential crashes, + // as redrawMapScene will destroy a handful of objects capable of triggering + // script API callbacks; we want to let them finish what they were doing + // before we destroying them. + // NOTE: This has the same problem as ScriptEditLayout, namely that it's + // doing more work than necessary and can be prohibitively slow. + QTimer::singleShot(0, this, &MainWindow::redrawMapScene); +} + void MainWindow::tryCommitMapChanges(bool commitChanges) { if (commitChanges) { Layout *layout = this->editor->layout; @@ -226,7 +237,7 @@ void MainWindow::setDimensions(int width, int height) { return; this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); - this->redrawMapScene(); + this->redrawResizedMapArea(); } void MainWindow::setWidth(int width) { @@ -236,7 +247,7 @@ void MainWindow::setWidth(int width) { return; this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); - this->redrawMapScene(); + this->redrawResizedMapArea(); } void MainWindow::setHeight(int height) { @@ -246,7 +257,7 @@ void MainWindow::setHeight(int height) { return; this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); - this->redrawMapScene(); + this->redrawResizedMapArea(); } //===================== @@ -296,7 +307,7 @@ void MainWindow::setBorderDimensions(int width, int height) { return; this->editor->layout->setBorderDimensions(width, height); this->tryCommitMapChanges(true); - this->redrawMapScene(); + this->redrawResizedMapArea(); } void MainWindow::setBorderWidth(int width) { @@ -306,7 +317,7 @@ void MainWindow::setBorderWidth(int width) { return; this->editor->layout->setBorderDimensions(width, this->editor->layout->getBorderHeight()); this->tryCommitMapChanges(true); - this->redrawMapScene(); + this->redrawResizedMapArea(); } void MainWindow::setBorderHeight(int height) { @@ -316,7 +327,7 @@ void MainWindow::setBorderHeight(int height) { return; this->editor->layout->setBorderDimensions(this->editor->layout->getBorderWidth(), height); this->tryCommitMapChanges(true); - this->redrawMapScene(); + this->redrawResizedMapArea(); } //====================== From d292dd05b4a319ebadcad6ebee3b328661c6aa16 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 17:29:30 -0400 Subject: [PATCH 56/71] Fix regression in resize layout popup --- src/ui/resizelayoutpopup.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 679e9ee2..afbf63fb 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -133,10 +133,11 @@ void ResizeLayoutPopup::setupLayoutView() { this->scene->setValidRect(rect); this->outline->setRect(rect); + // Rect may have changed, ensure spinners reflect final rect size. const QSignalBlocker b_Width(this->ui->spinBox_width); const QSignalBlocker b_Height(this->ui->spinBox_height); - this->ui->spinBox_width->setValue(metatilesWide); - this->ui->spinBox_height->setValue(metatilesTall); + this->ui->spinBox_width->setValue(rect.width() / Metatile::pixelWidth()); + this->ui->spinBox_height->setValue(rect.height() / Metatile::pixelHeight()); }); scene->addItem(outline); From b76c2ef31652d7396aebbeac883645b8fe73f4ff Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 17:36:26 -0400 Subject: [PATCH 57/71] Close metatile image exporter on save --- src/ui/metatileimageexporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp index 92dc5079..25f8f802 100644 --- a/src/ui/metatileimageexporter.cpp +++ b/src/ui/metatileimageexporter.cpp @@ -54,7 +54,7 @@ MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTi connect(ui->listWidget_Layers, &ReorderableListWidget::itemChanged, this, &MetatileImageExporter::updatePreview); connect(ui->listWidget_Layers, &ReorderableListWidget::reordered, this, &MetatileImageExporter::updatePreview); - connect(ui->pushButton_Save, &QPushButton::pressed, [this] { saveImage(); }); + connect(ui->pushButton_Save, &QPushButton::pressed, [this] { if (saveImage()) close(); }); connect(ui->pushButton_Close, &QPushButton::pressed, this, &MetatileImageExporter::close); connect(ui->pushButton_Reset, &QPushButton::pressed, this, &MetatileImageExporter::reset); From 9f5ac2935e6bce4a64ebf883d3f574290cbf93d4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 17:57:48 -0400 Subject: [PATCH 58/71] Disallow invalid text in tileset selectors --- CHANGELOG.md | 1 + include/mainwindow.h | 6 ++---- src/mainwindow.cpp | 28 +++++++++++++++++++++------- src/scriptapi/apimap.cpp | 8 -------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c899c6..41fe7e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix `Ctrl+Shift+Z` not being set as a default shortcut for Redo in the Palette Editor like it is for other windows. - Fix the Tileset Editor's status bar not updating while selecting tiles in the metatile layer view. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. +- Fix the Primary/Secondary Tileset selectors allowing invalid text, and considering a map unsaved if changed to invalid text then back again. - Fix broken error message for the primary tileset on the new map/layout dialogs. - Fix the dialog for duplicating/importing a map layout not allowing the tilesets to be changed. - Fix warning not appearing when the log file exceeds maximum size. diff --git a/include/mainwindow.h b/include/mainwindow.h index be4cc258..cd45fb5a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -119,8 +119,8 @@ public: Q_INVOKABLE int getNumSecondaryTilesetTiles(); Q_INVOKABLE QString getPrimaryTileset(); Q_INVOKABLE QString getSecondaryTileset(); - Q_INVOKABLE void setPrimaryTileset(QString tileset); - Q_INVOKABLE void setSecondaryTileset(QString tileset); + Q_INVOKABLE void setPrimaryTileset(const QString &tileset); + Q_INVOKABLE void setSecondaryTileset(const QString &tileset); void saveMetatilesByMetatileId(int metatileId); void saveMetatileAttributesByMetatileId(int metatileId); Metatile * getMetatile(int metatileId); @@ -251,8 +251,6 @@ private slots: void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); void on_button_OpenEmergeMap_clicked(); - void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); - void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); void resetMapViewScale(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index baf4c056..d1644528 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -320,10 +320,12 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog); connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); - connect(ui->comboBox_LayoutSelector->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); + connect(ui->comboBox_LayoutSelector, &NoScrollComboBox::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); connect(ui->checkBox_smartPaths, &QCheckBox::toggled, this, &MainWindow::setSmartPathsEnabled); connect(ui->checkBox_ToggleBorder, &QCheckBox::toggled, this, &MainWindow::setBorderVisibility); connect(ui->checkBox_MirrorConnections, &QCheckBox::toggled, this, &MainWindow::setMirrorConnectionsEnabled); + connect(ui->comboBox_PrimaryTileset, &NoScrollComboBox::editingFinished, [this] { setPrimaryTileset(ui->comboBox_PrimaryTileset->currentText()); }); + connect(ui->comboBox_SecondaryTileset, &NoScrollComboBox::editingFinished, [this] { setSecondaryTileset(ui->comboBox_SecondaryTileset->currentText()); }); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -2761,26 +2763,38 @@ void MainWindow::on_button_OpenEmergeMap_clicked() { userSetMap(ui->comboBox_EmergeMap->currentText()); } -void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) -{ - if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { +void MainWindow::setPrimaryTileset(const QString &tilesetLabel) { + if (!this->editor->layout || this->editor->layout->tileset_primary_label == tilesetLabel) + return; + + if (editor->project->primaryTilesetLabels.contains(tilesetLabel)) { editor->updatePrimaryTileset(tilesetLabel); redrawMapScene(); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); markLayoutEdited(); } + + // Restore valid text if input was invalid, or sync combo box with new valid setting. + const QSignalBlocker b(ui->comboBox_PrimaryTileset); + ui->comboBox_PrimaryTileset->setTextItem(this->editor->layout->tileset_primary_label); } -void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString &tilesetLabel) -{ - if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->layout) { +void MainWindow::setSecondaryTileset(const QString &tilesetLabel) { + if (!this->editor->layout || this->editor->layout->tileset_secondary_label == tilesetLabel) + return; + + if (editor->project->secondaryTilesetLabels.contains(tilesetLabel)) { editor->updateSecondaryTileset(tilesetLabel); redrawMapScene(); updateTilesetEditor(); prefab.updatePrefabUi(editor->layout); markLayoutEdited(); } + + // Restore valid text if input was invalid, or sync combo box with new valid setting. + const QSignalBlocker b(ui->comboBox_SecondaryTileset); + ui->comboBox_SecondaryTileset->setTextItem(this->editor->layout->tileset_secondary_label); } void MainWindow::on_pushButton_ChangeDimensions_clicked() { diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 09e4c593..e653cafd 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -566,14 +566,6 @@ QString MainWindow::getSecondaryTileset() { return this->editor->layout->tileset_secondary->name; } -void MainWindow::setPrimaryTileset(QString tileset) { - this->on_comboBox_PrimaryTileset_currentTextChanged(tileset); -} - -void MainWindow::setSecondaryTileset(QString tileset) { - this->on_comboBox_SecondaryTileset_currentTextChanged(tileset); -} - void MainWindow::saveMetatilesByMetatileId(int metatileId) { Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (tileset) From a4a30ae3c872535561317c3ace6444457aa77961 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 18:16:24 -0400 Subject: [PATCH 59/71] Add Escape as an alternative to exit swap metatiles mode --- include/ui/tileseteditor.h | 6 +++++- include/ui/tileseteditormetatileselector.h | 2 +- src/ui/tileseteditor.cpp | 10 +++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 80242ab8..4a88cb99 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -3,6 +3,7 @@ #include #include +#include #include "project.h" #include "history.h" #include "paletteeditor.h" @@ -104,6 +105,10 @@ private slots: void on_horizontalSlider_MetatilesZoom_valueChanged(int value); void on_horizontalSlider_TilesZoom_valueChanged(int value); +protected: + void keyPressEvent(QKeyEvent *event) override; + void closeEvent(QCloseEvent*) override; + private: void initAttributesUi(); void initMetatileSelector(); @@ -126,7 +131,6 @@ private: void exportMetatilesImage(); void refresh(); void commitMetatileLabel(); - void closeEvent(QCloseEvent*); void countMetatileUsage(); void countTileUsage(); void copyMetatile(bool cut); diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 542a67c5..a982d219 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -21,7 +21,7 @@ public: uint16_t getSelectedMetatileId() const { return this->selectedMetatileId; } QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId) const; - void setSwapMode(bool enabeled); + void setSwapMode(bool enabled); void addToSwapSelection(uint16_t metatileId); void removeFromSwapSelection(uint16_t metatileId); void clearSwapSelection(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 9dbfe6fd..7aa8b5b2 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -217,7 +217,7 @@ void TilesetEditor::initMetatileSelector() 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); + connect(ui->actionSwap_Metatiles, &QAction::toggled, this->metatileSelector, &TilesetEditorMetatileSelector::setSwapMode); bool showGrid = porymapConfig.showTilesetEditorMetatileGrid; this->ui->actionMetatile_Grid->setChecked(showGrid); @@ -1432,3 +1432,11 @@ void TilesetEditor::applyMetatileSwapToLayouts(uint16_t metatileIdA, uint16_t me } } } + +void TilesetEditor::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Escape && ui->actionSwap_Metatiles->isChecked()) { + ui->actionSwap_Metatiles->setChecked(false); + } else { + QMainWindow::keyPressEvent(event); + } +} From 5174dcb6eb9ac463d3d7afc76b9edb2762dbc42a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 6 Aug 2025 19:53:43 -0400 Subject: [PATCH 60/71] Fix Tileset Editor changes being missed --- CHANGELOG.md | 4 +++- src/project.cpp | 1 + src/ui/tileseteditor.cpp | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41fe7e4d..2352b6f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,11 +41,13 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix events being dragged in negative coordinates lagging behind the cursor. - Fix the shortcut for duplicating events working while on the Connections tab. - Fix the Shortcuts Editor displaying the duplicate shortcut prompt repeatedly. -- Fix clear text button on the left in each row of the Shortcuts Editor also clearing the shortcut on the right. +- Fix the clear text button on the left in each row of the Shortcuts Editor also clearing the shortcut on the right. - Fix Undo/Redo ignoring the automatic resizing that occurs if a layout/border was an unexpected size. - Fix Undo/Redo in the Tileset and Palette Editors and Paste in the Tileset Editor appearing enabled even when they don't do anything. - Fix `Ctrl+Shift+Z` not being set as a default shortcut for Redo in the Palette Editor like it is for other windows. - Fix the Tileset Editor's status bar not updating while selecting tiles in the metatile layer view. +- Fix cleared metatile labels not updating until the project is reloaded. +- Fix some changes in the Tileset Editor being discarded if the window is closed too quickly. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. - Fix the Primary/Secondary Tileset selectors allowing invalid text, and considering a map unsaved if changed to invalid text then back again. - Fix broken error message for the primary tileset on the new map/layout dialogs. diff --git a/src/project.cpp b/src/project.cpp index 3746e04f..af2bf0c2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1701,6 +1701,7 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) { QString metatileLabelPrefix = tileset->getMetatileLabelPrefix(); // Reverse map for faster lookup by metatile id + tileset->metatileLabels.clear(); for (auto it = this->metatileLabelsMap[tileset->name].constBegin(); it != this->metatileLabelsMap[tileset->name].constEnd(); it++) { QString labelName = it.key(); tileset->metatileLabels[it.value()] = labelName.replace(metatileLabelPrefix, ""); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 7aa8b5b2..50189112 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -840,6 +840,11 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset) { void TilesetEditor::closeEvent(QCloseEvent *event) { + // If focus is still on any input widgets, a user may have made changes + // but the widget hasn't had a chance to fire the 'editingFinished' signal. + // Make sure they lose focus before we close so that changes aren't missed. + setFocus(); + if (this->hasUnsavedChanges) { auto result = SaveChangesMessage::show(QStringLiteral("Tileset"), this); if (result == QMessageBox::Yes) { From 6f4f393a888e0523b8879fe7740bf707826324e0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 7 Aug 2025 13:29:05 -0400 Subject: [PATCH 61/71] Allow compiling without the qml module --- include/mainwindow.h | 12 ++++++--- include/scripting.h | 43 ++++++++++++++++++++++++++--- include/scriptutility.h | 18 ++++++++++--- include/ui/mapview.h | 5 ++++ include/ui/overlay.h | 15 +++++++++++ porymap.pro | 7 ++++- src/core/map.cpp | 1 + src/core/maplayout.cpp | 2 ++ src/mainwindow.cpp | 52 +++++++++++++++++++++++++++--------- src/scriptapi/apimap.cpp | 3 +++ src/scriptapi/apioverlay.cpp | 4 +++ src/scriptapi/apiutility.cpp | 8 +++--- src/scriptapi/scripting.cpp | 5 ++++ src/ui/overlay.cpp | 4 +++ src/ui/wildmonchart.cpp | 4 +-- 15 files changed, 154 insertions(+), 29 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index cd45fb5a..77d05632 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -11,7 +11,6 @@ #include #include #include -#include #include "project.h" #include "orderedjson.h" #include "config.h" @@ -36,6 +35,10 @@ #include "message.h" #include "resizelayoutpopup.h" +#if __has_include() +#include +#endif + namespace Ui { @@ -56,7 +59,11 @@ public: void initialize(); + Q_INVOKABLE void setPrimaryTileset(const QString &tileset); + Q_INVOKABLE void setSecondaryTileset(const QString &tileset); + // Scripting API +#ifdef QT_QML_LIB Q_INVOKABLE QJSValue getBlock(int x, int y); void tryRedrawMapArea(bool forceRedraw); void redrawResizedMapArea(); @@ -119,8 +126,6 @@ public: Q_INVOKABLE int getNumSecondaryTilesetTiles(); Q_INVOKABLE QString getPrimaryTileset(); Q_INVOKABLE QString getSecondaryTileset(); - Q_INVOKABLE void setPrimaryTileset(const QString &tileset); - Q_INVOKABLE void setSecondaryTileset(const QString &tileset); void saveMetatilesByMetatileId(int metatileId); void saveMetatileAttributesByMetatileId(int metatileId); Metatile * getMetatile(int metatileId); @@ -172,6 +177,7 @@ public: Q_INVOKABLE void setAllowEscaping(bool allow); Q_INVOKABLE int getFloorNumber(); Q_INVOKABLE void setFloorNumber(int floorNumber); +#endif // QT_QML_LIB public slots: void on_mainTabBar_tabBarClicked(int index); diff --git a/include/scripting.h b/include/scripting.h index b2c43ed2..b700df8f 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -2,12 +2,19 @@ #ifndef SCRIPTING_H #define SCRIPTING_H -#include "mainwindow.h" -#include "block.h" +#include #include "scriptutility.h" -#include +class Block; +class Tile; +class MainWindow; + +#if __has_include() #include +#endif + + +#ifdef QT_QML_LIB // !! New callback functions or changes to existing callback function names/arguments // should be synced to resources/text/script_template.txt and docsrc/manual/scripting-capabilities.rst @@ -78,4 +85,34 @@ private: void invokeCallback(CallbackType type, QJSValueList args); }; +#else + +class Scripting +{ +public: + Scripting(MainWindow *) {} + ~Scripting() {} + static void init(MainWindow *) {} + static void stop() {} + static void populateGlobalObject(MainWindow *) {} + + static void cb_ProjectOpened(QString) {}; + static void cb_ProjectClosed(QString) {}; + static void cb_MetatileChanged(int, int, Block, Block) {}; + static void cb_BorderMetatileChanged(int, int, uint16_t, uint16_t) {}; + static void cb_BlockHoverChanged(int, int) {}; + static void cb_BlockHoverCleared() {}; + static void cb_MapOpened(QString) {}; + static void cb_LayoutOpened(QString) {}; + static void cb_MapResized(int, int, const QMargins &) {}; + static void cb_BorderResized(int, int, int, int) {}; + static void cb_MapShifted(int, int) {}; + static void cb_TilesetUpdated(const QString &) {}; + static void cb_MainTabChanged(int, int) {}; + static void cb_MapViewTabChanged(int, int) {}; + static void cb_BorderVisibilityToggled(bool) {}; +}; + +#endif // QT_QML_LIB + #endif // SCRIPTING_H diff --git a/include/scriptutility.h b/include/scriptutility.h index 09bfaae6..ae60d316 100644 --- a/include/scriptutility.h +++ b/include/scriptutility.h @@ -2,14 +2,24 @@ #ifndef SCRIPTUTILITY_H #define SCRIPTUTILITY_H -#include "mainwindow.h" +#if __has_include() +#include +#endif + +#ifdef QT_QML_LIB + +#include +#include +#include +#include + +class MainWindow; class ScriptUtility : public QObject { Q_OBJECT - public: - ScriptUtility(MainWindow *mainWindow); + ScriptUtility(MainWindow *mainWindow) : window(mainWindow) {} ~ScriptUtility(); QString getActionFunctionName(int actionIndex); @@ -69,4 +79,6 @@ private: QHash actionMap; }; +#endif // QT_QML_LIB + #endif // SCRIPTUTILITY_H diff --git a/include/ui/mapview.h b/include/ui/mapview.h index 023e1cac..34610bb1 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -1,7 +1,10 @@ #ifndef MAPVIEW_H #define MAPVIEW_H +#if __has_include() #include +#endif + #include "graphicsview.h" #include "overlay.h" #include "tile.h" @@ -22,6 +25,7 @@ public: void clearOverlayMap(); // Overlay scripting API +#ifdef QT_QML_LIB Q_INVOKABLE void clear(int layer); Q_INVOKABLE void clear(); Q_INVOKABLE void hide(int layer); @@ -74,6 +78,7 @@ public: Q_INVOKABLE void addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int paletteId, bool setTransparency = false, int layer = 0); Q_INVOKABLE void addTileImage(int x, int y, QJSValue tileObj, bool setTransparency = false, int layer = 0); Q_INVOKABLE void addMetatileImage(int x, int y, int metatileId, bool setTransparency = false, int layer = 0); +#endif // QT_QML_LIB protected: virtual void drawForeground(QPainter *painter, const QRectF &rect) override; diff --git a/include/ui/overlay.h b/include/ui/overlay.h index 86fe0963..9be73ec8 100644 --- a/include/ui/overlay.h +++ b/include/ui/overlay.h @@ -8,6 +8,8 @@ #include #include +#ifdef QT_QML_LIB + class OverlayItem { public: OverlayItem() {} @@ -123,4 +125,17 @@ private: QRectF *clippingRect; }; +#else + +class Overlay +{ +public: + Overlay() {} + ~Overlay() {} + + void renderItems(QPainter *) {} +}; + +#endif // QT_QML_LIB + #endif // OVERLAY_H diff --git a/porymap.pro b/porymap.pro index 6b2245c1..4777644f 100644 --- a/porymap.pro +++ b/porymap.pro @@ -4,13 +4,18 @@ # #------------------------------------------------- -QT += core gui qml network +QT += core gui network qtHaveModule(charts) { QT += charts } else { warning("Qt module 'charts' not found, disabling chart features.") } +qtHaveModule(qml) { + QT += qml +} else { + warning("Qt module 'qml' not found, disabling plug-in features.") +} greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/src/core/map.cpp b/src/core/map.cpp index f7b75838..1f3b5916 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -4,6 +4,7 @@ #include "scripting.h" #include "utility.h" #include "editcommands.h" +#include "project.h" #include #include diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index b80bad0f..44616ab6 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -5,6 +5,8 @@ #include "scripting.h" #include "imageproviders.h" #include "utility.h" +#include "project.h" +#include "layoutpixmapitem.h" QList Layout::s_globalMetatileLayerOrder; QList Layout::s_globalMetatileLayerOpacity; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d1644528..91d93df9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -159,10 +159,24 @@ void MainWindow::initWindow() { ui->actionCheck_for_Updates->setVisible(false); #endif + QStringList missingModules; + #ifndef QT_CHARTS_LIB ui->pushButton_SummaryChart->setVisible(false); + missingModules.append(" 'charts'"); #endif +#ifndef QT_QML_LIB + ui->actionCustom_Scripts->setVisible(false); + missingModules.append(" 'qml'"); +#endif + + if (!missingModules.isEmpty()) { + logWarn(QString("Qt module%1%2 not found. Some features will be disabled.") + .arg(missingModules.length() > 1 ? "s" : "") + .arg(missingModules.join(","))); + } + setWindowDisabled(true); } @@ -2285,21 +2299,29 @@ void MainWindow::initShortcutsEditor() { void MainWindow::connectSubEditorsToShortcutsEditor() { /* Initialize sub-editors so that their children are added to MainWindow's object tree and will * be returned by shortcutableObjects() to be passed to ShortcutsEditor. */ - if (!tilesetEditor) + if (!this->tilesetEditor) { initTilesetEditor(); - connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved, - tilesetEditor, &TilesetEditor::applyUserShortcuts); + } + if (this->tilesetEditor) { + connect(this->shortcutsEditor, &ShortcutsEditor::shortcutsSaved, + this->tilesetEditor, &TilesetEditor::applyUserShortcuts); + } - if (!regionMapEditor) + if (!this->regionMapEditor){ initRegionMapEditor(true); - if (regionMapEditor) - connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved, - regionMapEditor, &RegionMapEditor::applyUserShortcuts); + } + if (this->regionMapEditor) { + connect(this->shortcutsEditor, &ShortcutsEditor::shortcutsSaved, + this->regionMapEditor, &RegionMapEditor::applyUserShortcuts); + } - if (!customScriptsEditor) + if (!this->customScriptsEditor) { initCustomScriptsEditor(); - connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved, - customScriptsEditor, &CustomScriptsEditor::applyUserShortcuts); + } + if (this->customScriptsEditor) { + connect(this->shortcutsEditor, &ShortcutsEditor::shortcutsSaved, + this->customScriptsEditor, &CustomScriptsEditor::applyUserShortcuts); + } } void MainWindow::resetMapViewScale() { @@ -2994,16 +3016,20 @@ void MainWindow::onWarpBehaviorWarningClicked() { } void MainWindow::on_actionCustom_Scripts_triggered() { - if (!this->customScriptsEditor) + if (!this->customScriptsEditor) { initCustomScriptsEditor(); - - Util::show(this->customScriptsEditor); + } + if (this->customScriptsEditor) { + Util::show(this->customScriptsEditor); + } } void MainWindow::initCustomScriptsEditor() { +#ifdef QT_QML_LIB this->customScriptsEditor = new CustomScriptsEditor(this); connect(this->customScriptsEditor, &CustomScriptsEditor::reloadScriptEngine, this, &MainWindow::reloadScriptEngine); +#endif } void MainWindow::reloadScriptEngine() { diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index e653cafd..dcd4fc33 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -1,3 +1,4 @@ +#ifdef QT_QML_LIB #include "mainwindow.h" #include "ui_mainwindow.h" #include "scripting.h" @@ -963,3 +964,5 @@ void MainWindow::setFloorNumber(int floorNumber) { this->editor->map->header()->setFloorNumber(floorNumber); } + +#endif // QT_QML_LIB diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index ffe7743a..e2ca888f 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -1,6 +1,8 @@ +#ifdef QT_QML_LIB #include "mapview.h" #include "scripting.h" #include "imageproviders.h" +#include "editor.h" void MapView::updateScene() { if (this->scene()) { @@ -300,3 +302,5 @@ void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparenc if (this->getOverlay(layer)->addImage(x, y, image)) this->updateScene(); } + +#endif // QT_QML_LIB diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index 5058e2de..8f205de2 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -1,12 +1,10 @@ +#ifdef QT_QML_LIB +#include "scriptutility.h" #include "mainwindow.h" #include "ui_mainwindow.h" #include "scripting.h" #include "config.h" -ScriptUtility::ScriptUtility(MainWindow *mainWindow) { - this->window = mainWindow; -} - ScriptUtility::~ScriptUtility() { if (window && window->ui && window->ui->menuTools) { for (auto action : this->registeredActions) { @@ -318,3 +316,5 @@ bool ScriptUtility::isPrimaryTileset(QString tilesetName) { bool ScriptUtility::isSecondaryTileset(QString tilesetName) { return getSecondaryTilesetNames().contains(tilesetName); } + +#endif // QT_QML_LIB diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index be14a933..ff614c1a 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -1,8 +1,10 @@ +#if __has_include() #include #include "scripting.h" #include "log.h" #include "config.h" +#include "mainwindow.h" const QMap callbackFunctions = { {OnProjectOpened, "onProjectOpened"}, @@ -422,3 +424,6 @@ const QImage * Scripting::getImage(const QString &inputFilepath, bool useCache) instance->imageCache.insert(inputFilepath, image); return image; } + + +#endif // __has_include() diff --git a/src/ui/overlay.cpp b/src/ui/overlay.cpp index d1ba4ef8..4fccb530 100644 --- a/src/ui/overlay.cpp +++ b/src/ui/overlay.cpp @@ -1,3 +1,4 @@ +#ifdef QT_QML_LIB #include "overlay.h" #include "scripting.h" #include "log.h" @@ -256,3 +257,6 @@ bool Overlay::addImage(int x, int y, QImage image) { this->items.append(new OverlayPixmap(x, y, QPixmap::fromImage(image))); return true; } + + +#endif // QT_QML_LIB diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 30585418..bc45415a 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -1,4 +1,4 @@ -#if __has_include() +#ifdef QT_CHARTS_LIB #include "wildmonchart.h" #include "ui_wildmonchart.h" #include "config.h" @@ -466,4 +466,4 @@ void WildMonChart::closeEvent(QCloseEvent *event) { QWidget::closeEvent(event); } -#endif // __has_include() +#endif // QT_CHARTS_LIB From e6a20c76bb1c0177f75a5fb3cc162bac6a09b40a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 7 Aug 2025 15:02:02 -0400 Subject: [PATCH 62/71] Allow compiling without the network module --- include/core/network.h | 6 ++++++ include/mainwindow.h | 3 +++ include/ui/updatepromoter.h | 4 ++++ porymap.pro | 7 ++++++- src/core/network.cpp | 3 +++ src/mainwindow.cpp | 11 +++++++++-- src/ui/updatepromoter.cpp | 3 +++ 7 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/core/network.h b/include/core/network.h index 321b3e99..23dc7eff 100644 --- a/include/core/network.h +++ b/include/core/network.h @@ -26,10 +26,14 @@ }); */ +#if __has_include() #include #include #include #include +#endif + +#ifdef QT_NETWORK_LIB class NetworkReplyData : public QObject { @@ -84,4 +88,6 @@ private: const QNetworkRequest getRequest(const QUrl &url); }; +#endif // QT_NETWORK_LIB + #endif // NETWORK_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 77d05632..d15bde31 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -331,8 +331,11 @@ private: QPointer layoutListProxyModel = nullptr; QPointer layoutTreeModel = nullptr; +#ifdef QT_NETWORK_LIB QPointer updatePromoter = nullptr; QPointer networkAccessManager = nullptr; +#endif + QPointer aboutWindow = nullptr; QPointer wildMonChart = nullptr; QPointer wildMonSearch = nullptr; diff --git a/include/ui/updatepromoter.h b/include/ui/updatepromoter.h index de73bcdd..76157c4d 100644 --- a/include/ui/updatepromoter.h +++ b/include/ui/updatepromoter.h @@ -1,6 +1,8 @@ #ifndef UPDATEPROMOTER_H #define UPDATEPROMOTER_H +#ifdef QT_NETWORK_LIB + #include "network.h" #include @@ -47,4 +49,6 @@ signals: void changedPreferences(); }; +#endif // QT_NETWORK_LIB + #endif // UPDATEPROMOTER_H diff --git a/porymap.pro b/porymap.pro index 4777644f..50aa83e2 100644 --- a/porymap.pro +++ b/porymap.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui network +QT += core gui qtHaveModule(charts) { QT += charts @@ -16,6 +16,11 @@ qtHaveModule(qml) { } else { warning("Qt module 'qml' not found, disabling plug-in features.") } +qtHaveModule(network) { + QT += network +} else { + warning("Qt module 'network' not found, disabling network features.") +} greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/src/core/network.cpp b/src/core/network.cpp index 4f2a1c19..ce0d5124 100644 --- a/src/core/network.cpp +++ b/src/core/network.cpp @@ -1,3 +1,4 @@ +#ifdef QT_NETWORK_LIB #include "network.h" #include "config.h" @@ -148,3 +149,5 @@ void NetworkAccessManager::processReply(QNetworkReply * reply, NetworkReplyData cacheEntry->data = data->m_body = reply->readAll(); } + +#endif // QT_NETWORK_LIB diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 91d93df9..478ca878 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -51,9 +51,11 @@ // We only publish release binaries for Windows and macOS. // This is relevant for the update promoter, which alerts users of a new release. +#ifdef QT_NETWORK_LIB #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #define RELEASE_PLATFORM #endif +#endif @@ -155,11 +157,14 @@ void MainWindow::initWindow() { this->initMapList(); this->initShortcuts(); + QStringList missingModules; + #ifndef RELEASE_PLATFORM ui->actionCheck_for_Updates->setVisible(false); #endif - - QStringList missingModules; +#ifndef QT_NETWORK_LIB + missingModules.append(" 'network'"); +#endif #ifndef QT_CHARTS_LIB ui->pushButton_SummaryChart->setVisible(false); @@ -2970,8 +2975,10 @@ void MainWindow::on_actionPreferences_triggered() { void MainWindow::togglePreferenceSpecificUi() { ui->actionOpen_Project_in_Text_Editor->setEnabled(!porymapConfig.textEditorOpenFolder.isEmpty()); +#ifdef QT_NETWORK_LIB if (this->updatePromoter) this->updatePromoter->updatePreferences(); +#endif } void MainWindow::openProjectSettingsEditor(int tab) { diff --git a/src/ui/updatepromoter.cpp b/src/ui/updatepromoter.cpp index 8afc8d9c..4b66fbff 100644 --- a/src/ui/updatepromoter.cpp +++ b/src/ui/updatepromoter.cpp @@ -1,3 +1,4 @@ +#ifdef QT_NETWORK_LIB #include "updatepromoter.h" #include "ui_updatepromoter.h" #include "log.h" @@ -188,3 +189,5 @@ void UpdatePromoter::dialogButtonClicked(QAbstractButton *button) { QDesktopServices::openUrl(this->downloadUrl); } } + +#endif // QT_NETWORK_LIB From c53d6eb105e689c2244169bbc4050dd5f407d277 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 7 Aug 2025 15:02:29 -0400 Subject: [PATCH 63/71] Add qml note to manual, add version details to log --- docsrc/manual/scripting-capabilities.rst | 3 +++ src/mainwindow.cpp | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index 4e016f6d..e18bd8e3 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -11,6 +11,9 @@ Porymap is extensible via scripting capabilities. This allows the user to write - Procedurally Generated Maps - Randomize Grass Patterns +.. note:: + If you are compiling Porymap yourself, these features will only be available if Qt's ``qml`` module is installed. + Custom Scripts Editor --------------------- diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 478ca878..1a33d4a5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -74,7 +74,8 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); logInit(); - logInfo(QString("Launching Porymap v%1").arg(QCoreApplication::applicationVersion())); + logInfo(QString("Launching Porymap v%1 (%2)").arg(QCoreApplication::applicationVersion()).arg(QStringLiteral(PORYMAP_LATEST_COMMIT))); + logInfo(QString("Using Qt v%2 (%3)").arg(QStringLiteral(QT_VERSION_STR)).arg(QSysInfo::buildCpuArchitecture())); } void MainWindow::initialize() { From 0b057a08a4c49cab748d8518f1660dbf236b2413 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 7 Aug 2025 19:30:45 -0400 Subject: [PATCH 64/71] Preserve setting from set_transparent_pixels_black --- src/config.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/config.cpp b/src/config.cpp index 51d63682..4322e4cd 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -974,6 +974,11 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveCallback = getConfigBool(key, value); } else if (key == "tilesets_have_is_compressed") { this->tilesetsHaveIsCompressed = getConfigBool(key, value); +#ifdef CONFIG_BACKWARDS_COMPATABILITY + // Old setting replaced by transparency_color + } else if (key == "set_transparent_pixels_black") { + this->transparencyColor = getConfigBool(key, value) ? QColor(Qt::black) : QColor(); +#endif } else if (key == "transparency_color") { this->transparencyColor = getConfigColor(key, value); } else if (key == "preserve_matching_only_data") { From b537375b15ddae4fca6b91ee0ab60805c95acbc1 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 7 Aug 2025 23:46:34 -0400 Subject: [PATCH 65/71] Fix regressions to tile usage count --- include/core/tileset.h | 2 +- src/ui/tileseteditor.cpp | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/core/tileset.h b/include/core/tileset.h index c804a0f9..dcf55ec1 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -94,7 +94,7 @@ public: uint16_t firstTileId() const; uint16_t lastTileId() const; - bool containsTileId(uint16_t tileId) const { return tileId > firstTileId() && tileId <= lastTileId(); } + 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/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 50189112..980f74c8 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -1234,17 +1234,26 @@ void TilesetEditor::countMetatileUsage() { } void TilesetEditor::countTileUsage() { - // check primary tiles this->tileSelector->usedTiles.resize(Project::getNumTilesTotal()); this->tileSelector->usedTiles.fill(0); 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); + QSet tilesets; + + // For the currently-loaded tilesets, make sure we use the Tileset Editor's versions + // (which may contain unsaved changes) and not the versions from the project. + tilesetNames.remove(this->primaryTileset->name); + tilesetNames.remove(this->secondaryTileset->name); + tilesets.insert(this->primaryTileset); + tilesets.insert(this->secondaryTileset); + for (const auto &tilesetName : tilesetNames) { Tileset *tileset = this->project->getTileset(tilesetName); - if (!tileset) continue; + if (tileset) tilesets.insert(tileset); + } + for (const auto &tileset : tilesets) { for (const auto &metatile : tileset->metatiles()) { for (const auto &tile : metatile->tiles) { if (searchTileset->containsTileId(tile.tileId)) { From 78dcebdd3eb8ead7835a6a76529aba9968494540 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Aug 2025 11:21:01 -0400 Subject: [PATCH 66/71] Remove network warning on non-release platforms --- src/mainwindow.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1a33d4a5..3519827b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -51,10 +51,11 @@ // We only publish release binaries for Windows and macOS. // This is relevant for the update promoter, which alerts users of a new release. -#ifdef QT_NETWORK_LIB #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #define RELEASE_PLATFORM #endif +#if defined(QT_NETWORK_LIB) && defined(RELEASE_PLATFORM) +#define USE_UPDATE_PROMOTER #endif @@ -160,12 +161,14 @@ void MainWindow::initWindow() { QStringList missingModules; -#ifndef RELEASE_PLATFORM +#ifndef USE_UPDATE_PROMOTER ui->actionCheck_for_Updates->setVisible(false); -#endif -#ifndef QT_NETWORK_LIB +#ifdef RELEASE_PLATFORM + // Only report the network module missing if we would + // have otherwise used it (we don't on non-release platforms). missingModules.append(" 'network'"); #endif +#endif #ifndef QT_CHARTS_LIB ui->pushButton_SummaryChart->setVisible(false); @@ -352,7 +355,7 @@ void MainWindow::on_actionCheck_for_Updates_triggered() { checkForUpdates(true); } -#ifdef RELEASE_PLATFORM +#ifdef USE_UPDATE_PROMOTER void MainWindow::checkForUpdates(bool requestedByUser) { if (!this->networkAccessManager) this->networkAccessManager = new NetworkAccessManager(this); @@ -2976,7 +2979,7 @@ void MainWindow::on_actionPreferences_triggered() { void MainWindow::togglePreferenceSpecificUi() { ui->actionOpen_Project_in_Text_Editor->setEnabled(!porymapConfig.textEditorOpenFolder.isEmpty()); -#ifdef QT_NETWORK_LIB +#ifdef USE_UPDATE_PROMOTER if (this->updatePromoter) this->updatePromoter->updatePreferences(); #endif From b97a4e76b1f6f5999558fc7d0d06013ab3e566ae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Aug 2025 12:48:54 -0400 Subject: [PATCH 67/71] Better default names for range-based metatile images --- src/ui/metatileimageexporter.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp index 25f8f802..51b953df 100644 --- a/src/ui/metatileimageexporter.cpp +++ b/src/ui/metatileimageexporter.cpp @@ -221,8 +221,20 @@ QString MetatileImageExporter::getDefaultFileName() const { } defaultFilename.append("_"); } - defaultFilename.append("Metatiles.png"); - return defaultFilename; + defaultFilename.append("Metatile"); + + uint16_t start = ui->spinBox_MetatileStart->value(); + uint16_t end = ui->spinBox_MetatileEnd->value(); + if (start != end) { + defaultFilename.append("s"); + } + if (!ui->checkBox_PrimaryTileset->isChecked() && !ui->checkBox_SecondaryTileset->isChecked()) { + defaultFilename.append(QString("_%1").arg(Metatile::getMetatileIdString(start))); + if (start != end) { + defaultFilename.append(QString("-%1").arg(Metatile::getMetatileIdString(end))); + } + } + return QString("%1.png").arg(defaultFilename); } void MetatileImageExporter::queuePreviewUpdate() { From 9e820a79fe7de5d25e5d7653180d6b31e04be67d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Aug 2025 13:34:31 -0400 Subject: [PATCH 68/71] Fix tile status not updating while painting on layer view --- include/ui/metatilelayersitem.h | 26 +++++++++-------- include/ui/tileseteditor.h | 5 ++-- src/ui/metatilelayersitem.cpp | 51 ++++++++++++++------------------- src/ui/tileseteditor.cpp | 22 +++++++------- 4 files changed, 50 insertions(+), 54 deletions(-) diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h index 7d1c74ce..18e799f4 100644 --- a/include/ui/metatilelayersitem.h +++ b/include/ui/metatilelayersitem.h @@ -12,12 +12,14 @@ public: MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset, - Qt::Orientation orientation = Qt::Horizontal); - void draw(); + Qt::Orientation orientation = Qt::Vertical); + + void draw() override; void setTilesets(Tileset*, Tileset*); void setMetatile(Metatile*); - void clearLastModifiedCoords(); - void clearLastHoveredCoords(); + + bool hasCursor() const { return this->cursorCellPos != QPoint(-1,-1); } + Tile tileUnderCursor() const; QPoint tileIndexToPos(int index) const { return this->tilePositions.value(index); } int posToTileIndex(const QPoint &pos) const { return this->tilePositions.indexOf(pos); } @@ -31,14 +33,14 @@ private: Tileset *primaryTileset; Tileset *secondaryTileset; Qt::Orientation orientation; - QPoint prevChangedPos; - QPoint prevHoveredPos; + + QPoint cursorCellPos = QPoint(-1,-1); QList tilePositions; QPoint getBoundedPos(const QPointF &); void updateSelection(); - void hover(const QPoint &pos); + bool setCursorCellPos(const QPoint &pos); signals: void tileChanged(const QPoint &pos); void paletteChanged(const QPoint &pos); @@ -46,11 +48,11 @@ signals: void hoveredTileChanged(const Tile &tile); void hoveredTileCleared(); protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; }; #endif // METATILELAYERSITEM_H diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 4a88cb99..50ab1ce1 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -77,8 +77,6 @@ private slots: void onWindowActivated(); void onHoveredMetatileChanged(uint16_t); void onHoveredMetatileCleared(); - void onHoveredTileChanged(const Tile&); - void onHoveredTileChanged(uint16_t); void onHoveredTileCleared(); void onMetatileLayerSelectionChanged(const QPoint&, const QSize&); void onPaletteEditorChangedPaletteColor(); @@ -159,6 +157,9 @@ private: void applyMetatileSwapsToLayouts(); void rebuildMetatilePropertiesFrame(); void addWidgetToMetatileProperties(QWidget *w, int *row, int rowSpan); + void updateLayerTileStatus(); + void showTileStatus(const Tile &tile); + void showTileStatus(uint16_t tileId); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index 8ec0cceb..c540f4c4 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -9,13 +9,13 @@ MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTiles primaryTileset(primaryTileset), secondaryTileset(secondaryTileset) { - clearLastModifiedCoords(); - clearLastHoveredCoords(); setAcceptHoverEvents(true); setOrientation(orientation); } void MetatileLayersItem::setOrientation(Qt::Orientation orientation) { + if (this->orientation == orientation) + return; this->orientation = orientation; int maxWidth = Metatile::tileWidth(); int maxHeight = Metatile::tileHeight(); @@ -94,16 +94,13 @@ void MetatileLayersItem::draw() { void MetatileLayersItem::setMetatile(Metatile *metatile) { this->metatile = metatile; - this->clearLastModifiedCoords(); - this->clearLastHoveredCoords(); + draw(); } void MetatileLayersItem::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; this->draw(); - this->clearLastModifiedCoords(); - this->clearLastHoveredCoords(); } void MetatileLayersItem::updateSelection() { @@ -112,8 +109,8 @@ void MetatileLayersItem::updateSelection() { } void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - const QPoint pos = this->getBoundedPos(event->pos()); - hover(pos); + const QPoint pos = getBoundedPos(event->pos()); + setCursorCellPos(pos); if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mousePressEvent(event); @@ -123,14 +120,12 @@ void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { } else { emit tileChanged(pos); } - this->prevChangedPos = pos; } void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - const QPoint pos = this->getBoundedPos(event->pos()); - if (this->prevChangedPos == pos) + const QPoint pos = getBoundedPos(event->pos()); + if (!setCursorCellPos(pos)) return; - hover(pos); if (event->buttons() & Qt::RightButton) { SelectablePixmapItem::mouseMoveEvent(event); @@ -140,7 +135,6 @@ void MetatileLayersItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } else { emit tileChanged(pos); } - this->prevChangedPos = pos; } void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { @@ -154,32 +148,29 @@ void MetatileLayersItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { } void MetatileLayersItem::hoverMoveEvent(QGraphicsSceneHoverEvent * event) { - hover(getBoundedPos(event->pos())); + setCursorCellPos(getBoundedPos(event->pos())); } -void MetatileLayersItem::hover(const QPoint &pos) { - if (pos == this->prevHoveredPos) - return; - this->prevHoveredPos = pos; +bool MetatileLayersItem::setCursorCellPos(const QPoint &pos) { + if (this->cursorCellPos == pos) + return false; + this->cursorCellPos = pos; - int tileIndex = posToTileIndex(pos); - if (tileIndex < 0 || !this->metatile || tileIndex >= this->metatile->tiles.length()) - return; - - emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex)); + emit this->hoveredTileChanged(tileUnderCursor()); + return true; } void MetatileLayersItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { - this->clearLastHoveredCoords(); + this->cursorCellPos = QPoint(-1,-1); emit this->hoveredTileCleared(); } -void MetatileLayersItem::clearLastModifiedCoords() { - this->prevChangedPos = QPoint(-1, -1); -} - -void MetatileLayersItem::clearLastHoveredCoords() { - this->prevHoveredPos = QPoint(-1, -1); +Tile MetatileLayersItem::tileUnderCursor() const { + int tileIndex = posToTileIndex(this->cursorCellPos); + if (tileIndex < 0 || !this->metatile || tileIndex >= this->metatile->tiles.length()) { + return Tile(); + } + return this->metatile->tiles.at(tileIndex); } QPoint MetatileLayersItem::getBoundedPos(const QPointF &pos) { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 980f74c8..e38ef2a3 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -314,7 +314,7 @@ void TilesetEditor::initMetatileLayersItem() { 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::hoveredTileChanged, [this](const Tile &tile) { showTileStatus(tile); }); connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared); bool showGrid = porymapConfig.showTilesetEditorLayerGrid; @@ -329,7 +329,7 @@ void TilesetEditor::initMetatileLayersItem() { void TilesetEditor::initTileSelector() { this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset); connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) { - onHoveredTileChanged(tileId); + showTileStatus(tileId); }); connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared); connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, this, &TilesetEditor::drawSelectedTiles); @@ -499,7 +499,6 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { } this->metatileLayersItem->setMetatile(metatile); - this->metatileLayersItem->draw(); MetatileLabelPair labels = Tileset::getMetatileLabelPair(metatileId, this->primaryTileset, this->secondaryTileset); this->ui->lineEdit_MetatileLabel->setText(labels.owned); @@ -512,7 +511,13 @@ void TilesetEditor::queueMetatileReload(uint16_t metatileId) { this->metatileReloadQueue.insert(metatileId); } -void TilesetEditor::onHoveredTileChanged(const Tile &tile) { +void TilesetEditor::updateLayerTileStatus() { + if (this->metatileLayersItem->hasCursor()) { + showTileStatus(this->metatileLayersItem->tileUnderCursor()); + } +} + +void TilesetEditor::showTileStatus(const Tile &tile) { this->ui->statusbar->showMessage(QString("Tile: %1, Palette: %2%3%4") .arg(Util::toHexString(tile.tileId, 3)) .arg(QString::number(tile.palette)) @@ -521,7 +526,7 @@ void TilesetEditor::onHoveredTileChanged(const Tile &tile) { ); } -void TilesetEditor::onHoveredTileChanged(uint16_t tileId) { +void TilesetEditor::showTileStatus(uint16_t tileId) { this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tileId, 3))); } @@ -570,6 +575,7 @@ void TilesetEditor::paintSelectedLayerTiles(const QPoint &pos, bool paletteOnly) this->metatileSelector->drawSelectedMetatile(); this->metatileLayersItem->draw(); + updateLayerTileStatus(); this->tileSelector->draw(); this->commitMetatileChange(prevMetatile); } @@ -589,7 +595,6 @@ void TilesetEditor::onMetatileLayerSelectionChanged(const QPoint &selectionOrigi this->tileSelector->highlight(tiles[0].tileId); this->redrawTileSelector(); } - this->metatileLayersItem->clearLastModifiedCoords(); } void TilesetEditor::setPaletteId(int paletteId) { @@ -606,13 +611,11 @@ void TilesetEditor::refreshPaletteId() { if (this->paletteEditor) { this->paletteEditor->setPaletteId(paletteId()); } - this->metatileLayersItem->clearLastModifiedCoords(); } void TilesetEditor::refreshTileFlips() { this->tileSelector->setTileFlips(ui->checkBox_xFlip->isChecked(), ui->checkBox_yFlip->isChecked()); this->drawSelectedTiles(); - this->metatileLayersItem->clearLastModifiedCoords(); } void TilesetEditor::setMetatileLabel(QString label) @@ -957,8 +960,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile &src, QS this->metatileSelector->select(metatileId); this->metatileSelector->drawMetatile(metatileId); this->metatileLayersItem->draw(); - this->metatileLayersItem->clearLastModifiedCoords(); - this->metatileLayersItem->clearLastHoveredCoords(); + updateLayerTileStatus(); return true; } From edabed01054440e2279aa181a9d1956d470343b3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Aug 2025 14:31:39 -0400 Subject: [PATCH 69/71] Fix status not updating while painting on map, remove redundant CollisionPixmapItem handling --- CHANGELOG.md | 1 + include/editor.h | 2 +- include/ui/collisionpixmapitem.h | 16 ------ include/ui/layoutpixmapitem.h | 5 +- src/editor.cpp | 84 +++++++++++--------------------- src/ui/collisionpixmapitem.cpp | 40 --------------- 6 files changed, 34 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2352b6f4..59d82ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix Undo/Redo in the Tileset and Palette Editors and Paste in the Tileset Editor appearing enabled even when they don't do anything. - Fix `Ctrl+Shift+Z` not being set as a default shortcut for Redo in the Palette Editor like it is for other windows. - Fix the Tileset Editor's status bar not updating while selecting tiles in the metatile layer view. +- Fix the main window's status bar not immediately reflecting changes made while painting metatiles / movement permissions. - Fix cleared metatile labels not updating until the project is reloaded. - Fix some changes in the Tileset Editor being discarded if the window is closed too quickly. - Fix the Region Map Editor incorrectly displaying whether a `MAPSEC` has region map data. diff --git a/include/editor.h b/include/editor.h index ffe628ac..21368f61 100644 --- a/include/editor.h +++ b/include/editor.h @@ -258,11 +258,11 @@ private: bool canPaintMetatiles() const; void onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void onMapEndPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); + void setStatusFromMapPos(const QPoint &pos); private slots: void setSmartPathCursorMode(QGraphicsSceneMouseEvent *event); void mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); - void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); void setSelectedConnectionItem(ConnectionPixmapItem *connectionItem); void onHoveredMovementPermissionChanged(uint16_t, uint16_t); void onHoveredMovementPermissionCleared(); diff --git a/include/ui/collisionpixmapitem.h b/include/ui/collisionpixmapitem.h index 90376c34..d4f7e182 100644 --- a/include/ui/collisionpixmapitem.h +++ b/include/ui/collisionpixmapitem.h @@ -30,23 +30,7 @@ public: void draw(bool ignoreCache = false) override; private: - unsigned actionId_ = 0; - QPoint previousPos; void updateSelection(QPoint pos); - -signals: - void mouseEvent(QGraphicsSceneMouseEvent *, CollisionPixmapItem *); - void hoverEntered(const QPoint &pos); - void hoverChanged(const QPoint &pos); - void hoverCleared(); - -protected: - virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; - virtual void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; - virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; - virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; - virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; - virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; }; #endif // COLLISIONPIXMAPITEM_H diff --git a/include/ui/layoutpixmapitem.h b/include/ui/layoutpixmapitem.h index 04f0500a..44970363 100644 --- a/include/ui/layoutpixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -88,6 +88,9 @@ public: void lockNondominantAxis(QGraphicsSceneMouseEvent *event); QPoint adjustCoords(QPoint pos); +protected: + unsigned actionId_ = 0; + private: void paintSmartPath(int x, int y, bool fromScriptCall = false); static bool isValidSmartPathSelection(MetatileSelection selection); @@ -97,8 +100,6 @@ private: static constexpr int smartPathMiddleIndex = (smartPathWidth / 2) + ((smartPathHeight / 2) * smartPathWidth); QPoint lastMetatileSelectionPos = QPoint(-1,-1); - unsigned actionId_ = 0; - signals: void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); diff --git a/src/editor.cpp b/src/editor.cpp index f5657642..6bce4288 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1231,6 +1231,19 @@ void Editor::onMapHoverChanged(const QPoint &pos) { if (!layout || !layout->isWithinBounds(pos)) return; + setStatusFromMapPos(pos); + Scripting::cb_BlockHoverChanged(pos.x(), pos.y()); +} + +void Editor::onMapHoverCleared() { + updateCursorRectVisibility(); + if (getEditingLayout()) { + ui->statusBar->clearMessage(); + } + Scripting::cb_BlockHoverCleared(); +} + +void Editor::setStatusFromMapPos(const QPoint &pos) { int x = pos.x(); int y = pos.y(); if (this->editMode == EditMode::Metatiles) { @@ -1255,16 +1268,6 @@ void Editor::onMapHoverChanged(const QPoint &pos) { .arg(y) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); } - - Scripting::cb_BlockHoverChanged(x, y); -} - -void Editor::onMapHoverCleared() { - updateCursorRectVisibility(); - if (getEditingLayout()) { - ui->statusBar->clearMessage(); - } - Scripting::cb_BlockHoverCleared(); } QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation) { @@ -1438,10 +1441,15 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (this->editMode == EditMode::Metatiles) { + if (this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision) { if (editAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { - item->updateMetatileSelection(event); + if (this->editMode == EditMode::Collision) { + auto collisionItem = dynamic_cast(item); + if (collisionItem) collisionItem->updateMovementPermissionSelection(event); + } else { + item->updateMetatileSelection(event); + } } else if (event->buttons() & Qt::MiddleButton) { if (event->modifiers() & Qt::ControlModifier) { item->magicFill(event); @@ -1457,18 +1465,24 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i adjustStraightPathPos(event, item, &pos); item->paint(event); } + setStatusFromMapPos(pos); } else if (editAction == EditAction::Select) { item->select(event); } else if (editAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { - item->updateMetatileSelection(event); + if (this->editMode == EditMode::Metatiles) { + item->updateMetatileSelection(event); + } else { + item->pick(event); + } } else if (event->modifiers() & Qt::ControlModifier) { item->magicFill(event); } else { item->floodFill(event); } + setStatusFromMapPos(pos); } else if (editAction == EditAction::Pick) { - if (event->buttons() & Qt::RightButton) { + if (this->editMode == EditMode::Metatiles && (event->buttons() & Qt::RightButton)) { item->updateMetatileSelection(event); } else if (event->type() != QEvent::GraphicsSceneMouseRelease) { item->pick(event); @@ -1520,46 +1534,6 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } } -void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item) { - auto editAction = getEditAction(); - if (this->editMode != EditMode::Collision || editAction == EditAction::Move) { - event->ignore(); - return; - } - - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - - if (editAction == EditAction::Paint) { - if (event->buttons() & Qt::RightButton) { - item->updateMovementPermissionSelection(event); - } else if (event->buttons() & Qt::MiddleButton) { - if (event->modifiers() & Qt::ControlModifier) { - item->magicFill(event); - } else { - item->floodFill(event); - } - } else { - adjustStraightPathPos(event, item, &pos); - item->paint(event); - } - } else if (editAction == EditAction::Select) { - item->select(event); - } else if (editAction == EditAction::Fill) { - if (event->buttons() & Qt::RightButton) { - item->pick(event); - } else if (event->modifiers() & Qt::ControlModifier) { - item->magicFill(event); - } else { - item->floodFill(event); - } - } else if (editAction == EditAction::Pick) { - item->pick(event); - } else if (editAction == EditAction::Shift) { - adjustStraightPathPos(event, item, &pos); - item->shift(event); - } -} - // On project close we want to leave the editor view empty. // Otherwise a map is normally only cleared when a new one is being displayed. void Editor::clearMap() { @@ -1697,7 +1671,7 @@ void Editor::displayMapMovementPermissions() { collision_item = new CollisionPixmapItem(this->layout, ui->spinBox_SelectedCollision, ui->spinBox_SelectedElevation, this->metatile_selector_item, this->settings, &this->collisionOpacity); - connect(collision_item, &CollisionPixmapItem::mouseEvent, this, &Editor::mouseEvent_collision); + connect(collision_item, &CollisionPixmapItem::mouseEvent, this, &Editor::mouseEvent_map); connect(collision_item, &CollisionPixmapItem::hoverEntered, this, &Editor::onMapHoverEntered); connect(collision_item, &CollisionPixmapItem::hoverChanged, this, &Editor::onMapHoverChanged); connect(collision_item, &CollisionPixmapItem::hoverCleared, this, &Editor::onMapHoverCleared); diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index 9fcfb7de..ce06fb7c 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -2,46 +2,6 @@ #include "editcommands.h" #include "metatile.h" -void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (pos != this->previousPos) { - this->previousPos = pos; - emit this->hoverChanged(pos); - } -} - -void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { - this->has_mouse = true; - this->previousPos = Metatile::coordFromPixmapCoord(event->pos()); - emit this->hoverEntered(this->previousPos); -} - -void CollisionPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { - this->has_mouse = false; - emit this->hoverCleared(); -} - -void CollisionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - this->paint_tile_initial_x = this->straight_path_initial_x = pos.x(); - this->paint_tile_initial_y = this->straight_path_initial_y = pos.y(); - emit mouseEvent(event, this); -} - -void CollisionPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (pos != this->previousPos) { - this->previousPos = pos; - emit this->hoverChanged(pos); - } - emit mouseEvent(event, this); -} - -void CollisionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - this->lockedAxis = CollisionPixmapItem::Axis::None; - emit mouseEvent(event, this); -} - void CollisionPixmapItem::draw(bool ignoreCache) { if (this->layout) { this->layout->setCollisionItem(this); From 3ae1e404f891997e6776147afa50fa6f47a41cc7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Aug 2025 15:06:37 -0400 Subject: [PATCH 70/71] Fix metatile status not updating while pasting/swapping in Tileset Editor --- CHANGELOG.md | 2 +- include/ui/tileseteditor.h | 3 ++- include/ui/tileseteditormetatileselector.h | 3 +++ src/ui/tileseteditor.cpp | 11 +++++++++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d82ff0..57898b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,7 +45,7 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp - Fix Undo/Redo ignoring the automatic resizing that occurs if a layout/border was an unexpected size. - Fix Undo/Redo in the Tileset and Palette Editors and Paste in the Tileset Editor appearing enabled even when they don't do anything. - Fix `Ctrl+Shift+Z` not being set as a default shortcut for Redo in the Palette Editor like it is for other windows. -- Fix the Tileset Editor's status bar not updating while selecting tiles in the metatile layer view. +- Fix the Tileset Editor's status bar not updating while selecting tiles in the metatile layer view, or when pasting metatiles. - Fix the main window's status bar not immediately reflecting changes made while painting metatiles / movement permissions. - Fix cleared metatile labels not updating until the project is reloaded. - Fix some changes in the Tileset Editor being discarded if the window is closed too quickly. diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 50ab1ce1..fc974d10 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -75,7 +75,6 @@ public slots: private slots: void onWindowActivated(); - void onHoveredMetatileChanged(uint16_t); void onHoveredMetatileCleared(); void onHoveredTileCleared(); void onMetatileLayerSelectionChanged(const QPoint&, const QSize&); @@ -160,6 +159,8 @@ private: void updateLayerTileStatus(); void showTileStatus(const Tile &tile); void showTileStatus(uint16_t tileId); + void updateMetatileStatus(); + void showMetatileStatus(uint16_t metatileId); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index a982d219..3b38d9b2 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -26,6 +26,9 @@ public: void removeFromSwapSelection(uint16_t metatileId); void clearSwapSelection(); + bool hasCursor() const { return this->prevCellPos != QPoint(-1,-1); } + uint16_t metatileIdUnderCursor() const { return this->lastHoveredMetatileId; } + QVector usedMetatiles; bool selectorShowUnused = false; bool selectorShowCounts = false; diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e38ef2a3..38a41bf2 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -213,7 +213,7 @@ 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::hoveredMetatileChanged, this, &TilesetEditor::showMetatileStatus); connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileCleared, this, &TilesetEditor::onHoveredMetatileCleared); connect(this->metatileSelector, &TilesetEditorMetatileSelector::selectedMetatileChanged, this, &TilesetEditor::onSelectedMetatileChanged); connect(this->metatileSelector, &TilesetEditorMetatileSelector::swapRequested, this, &TilesetEditor::commitMetatileSwap); @@ -473,7 +473,13 @@ void TilesetEditor::drawSelectedTiles() { this->ui->graphicsView_selectedTile->setSceneRect(0, 0, size.width(), size.height()); } -void TilesetEditor::onHoveredMetatileChanged(uint16_t metatileId) { +void TilesetEditor::updateMetatileStatus() { + if (this->metatileSelector->hasCursor()) { + showMetatileStatus(this->metatileSelector->metatileIdUnderCursor()); + } +} + +void TilesetEditor::showMetatileStatus(uint16_t metatileId) { QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId)); if (label.size() != 0) { @@ -959,6 +965,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile &src, QS *this->metatile = src; this->metatileSelector->select(metatileId); this->metatileSelector->drawMetatile(metatileId); + updateMetatileStatus(); this->metatileLayersItem->draw(); updateLayerTileStatus(); return true; From e2b1a1ecbb3b8fcfb34aedfe7a3f1c1d53d7791f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 8 Aug 2025 15:24:46 -0400 Subject: [PATCH 71/71] Fix window focus when closing Palette Editor --- src/ui/paletteeditor.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp index a862302e..ebc66cde 100644 --- a/src/ui/paletteeditor.cpp +++ b/src/ui/paletteeditor.cpp @@ -283,4 +283,13 @@ void PaletteEditor::closeEvent(QCloseEvent*) { saveGeometry(), saveState() ); + + // Opening the color search window then closing the Palette Editor sets + // focus to the main editor window instead of the parent (Tileset Editor). + // Make sure the parent is active when we close. + auto p = dynamic_cast(parent()); + if (p && p->isVisible()) { + p->raise(); + p->activateWindow(); + } }