From 1e09d08c9cdeaebad47914afc32477f946fe2a38 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 8 Jul 2024 16:01:30 -0400 Subject: [PATCH] Reimplement connection mirroring --- include/core/map.h | 1 - include/core/mapconnection.h | 15 +- include/editor.h | 24 +- include/mainwindow.h | 2 +- include/ui/connectionpixmapitem.h | 2 +- include/ui/connectionslistitem.h | 13 +- src/core/map.cpp | 4 +- src/core/mapconnection.cpp | 43 +++ src/editor.cpp | 490 ++++++++++++++++++------------ src/mainwindow.cpp | 34 +-- src/ui/connectionpixmapitem.cpp | 4 +- src/ui/connectionslistitem.cpp | 24 +- 12 files changed, 405 insertions(+), 251 deletions(-) create mode 100644 src/core/mapconnection.cpp diff --git a/include/core/map.h b/include/core/map.h index c214fbb7..a6f6fa1c 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -128,7 +128,6 @@ private: void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); signals: - void mapChanged(Map *map); void modified(); void mapDimensionsChanged(const QSize &size); void mapNeedsRedrawing(); diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 11aa0e59..474be5b1 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -5,6 +5,8 @@ #include #include +class Map; + class MapConnection { public: QString direction; @@ -12,12 +14,15 @@ public: QString map_name; }; -inline bool operator==(const MapConnection &c1, const MapConnection &c2) { - return c1.map_name == c2.map_name; -} +struct MapConnectionMirror { + MapConnection * connection = nullptr; + Map * map = nullptr; +}; -inline uint qHash(const MapConnection &key) { - return qHash(key.map_name); +inline bool operator==(const MapConnection &c1, const MapConnection &c2) { + return c1.direction == c2.direction && + c1.offset == c2.offset && + c1.map_name == c2.map_name; } #endif // MAPCONNECTION_H diff --git a/include/editor.h b/include/editor.h index f9bbe051..f0b54b69 100644 --- a/include/editor.h +++ b/include/editor.h @@ -75,9 +75,10 @@ public: void setEditingConnections(); void setMapEditingButtonsEnabled(bool enabled); void setConnectionsVisibility(bool visible); - void updateConnectionOffset(int offset); void addNewConnection(); - void removeConnection(ConnectionPixmapItem* connectionItem); + void addConnection(Map* map, MapConnection* connection); + void removeConnection(Map* map, MapConnection* connection); + void removeConnectionItem(ConnectionPixmapItem* connectionItem, bool removeMirror = true); void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); @@ -168,17 +169,16 @@ private: void updateBorderVisibility(); QPoint calculateConnectionPosition(const MapConnection *connection, const QPixmap &pixmap); - void redrawConnection(ConnectionPixmapItem* connectionItem); + void updateConnectionItem(ConnectionPixmapItem* connectionItem); + void updateConnectionItemPos(ConnectionPixmapItem* connectionItem); void createConnectionItem(MapConnection* connection); void populateConnectionsList(); void addConnectionToList(ConnectionPixmapItem* connection); void updateDiveEmergeMap(QString mapName, QString direction); - void onConnectionOffsetChanged(int newOffset); - void removeMirroredConnection(MapConnection*); - void updateMirroredConnectionOffset(MapConnection*); - void updateMirroredConnectionDirection(MapConnection*, QString); - void updateMirroredConnectionMap(MapConnection*, QString); - void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false); + bool shouldMirrorConnection(const MapConnection &source); + MapConnectionMirror getMirroredConnection(const MapConnection&); + void addMirroredConnection(const MapConnection&); + void removeMirroredConnection(const MapConnection&); void updateEncounterFields(EncounterFields newFields); QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); @@ -194,7 +194,9 @@ private slots: void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); - void onConnectionMoved(MapConnection*); + void setConnectionOffset(MapConnection *connection, int offset); + void setConnectionMap(MapConnection *connection, const QString &mapName); + void setConnectionDirection(MapConnection *connection, const QString &direction); void onConnectionItemSelected(ConnectionPixmapItem* connectionItem); void onHoveredMovementPermissionChanged(uint16_t, uint16_t); void onHoveredMovementPermissionCleared(); @@ -215,7 +217,7 @@ signals: void warpEventDoubleClicked(QString, int, Event::Group); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); - void editedMapData(); + void editedMapData(Map*); void tilesetUpdated(QString); }; diff --git a/include/mainwindow.h b/include/mainwindow.h index 16eee651..14a5e9cd 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -178,7 +178,6 @@ private slots: void paste(); void onConnectionItemDoubleClicked(QString, QString); - void onMapChanged(Map *map); void onMapNeedsRedrawing(); void onTilesetsSaved(QString, QString); void onWildMonDataChanged(); @@ -366,6 +365,7 @@ private: void clickToolButtonFromEditMode(QString editMode); void markMapEdited(); + void markMapEdited(Map*); void showWindowTitle(); void initWindow(); diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 41995003..f0e00ae1 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -44,7 +44,7 @@ protected: signals: void connectionItemSelected(ConnectionPixmapItem* connectionItem); void connectionItemDoubleClicked(ConnectionPixmapItem* connectionItem); - void connectionMoved(MapConnection*); + void connectionMoved(MapConnection *, int newOffset); void highlightChanged(bool highlighted); }; diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index ce8a0163..a2ce2c16 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -19,15 +19,16 @@ class ConnectionsListItem : public QFrame Q_OBJECT public: - explicit ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames); + explicit ConnectionsListItem(QWidget *parent, MapConnection *connection, const QStringList &mapNames); ~ConnectionsListItem(); void updateUI(); void setSelected(bool selected); + MapConnection * connection; + private: Ui::ConnectionsListItem *ui; - MapConnection * const connection; bool isSelected = false; protected: @@ -35,10 +36,12 @@ protected: void mouseDoubleClickEvent(QMouseEvent *); signals: - void edited(); - void deleteRequested(); + void editedOffset(MapConnection *connection, int newOffset); + void editedDirection(MapConnection *connection, const QString &direction); + void editedMapName(MapConnection *connection, const QString &mapName); + void removed(); void selected(); - void doubleClicked(); + void doubleClicked(const QString &mapName); private slots: void on_comboBox_Direction_currentTextChanged(const QString &direction); diff --git a/src/core/map.cpp b/src/core/map.cpp index a0ecf404..463b9341 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -304,8 +304,8 @@ void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); } - emit mapChanged(this); emit mapDimensionsChanged(QSize(getWidth(), getHeight())); + modify(); } void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { @@ -322,7 +322,7 @@ void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); } - emit mapChanged(this); + modify(); } void Map::openScript(QString label) { diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp new file mode 100644 index 00000000..ebf48592 --- /dev/null +++ b/src/core/mapconnection.cpp @@ -0,0 +1,43 @@ +#include "mapconnection.h" +#include "map.h" + +void MapConnection::setDirection(const QString & direction) { + if (direction == m_direction) + return; + auto before = m_direction; + m_direction = direction; + emit directionChanged(before, m_direction); +} + +void MapConnection::setOffset(int offset) { + if (offset == m_offset) + return; + auto before = m_offset; + m_offset = offset; + emit offsetChanged(before, m_offset); +} + +void MapConnection::setMapName(const QString &mapName) { + if (mapName == m_mapName) + return; + auto before = m_mapName; + m_mapName = mapName; + emit mapNameChanged(before, m_mapName); +} +/* +static QString MapConnection::oppositeDirection(const QString &direction) { + static const QMap oppositeDirections = { + {"up", "down"}, {"down", "up"}, + {"right", "left"}, {"left", "right"}, + {"dive", "emerge"}, {"emerge", "dive"} + }; + return oppositeDirections.value(direction); +} + +static MapConnection* MapConnection::getMirror(const MapConnection*, const Map*) { + +} + +static MapConnection* MapConnection::newMirror(const MapConnection*) { + +}*/ diff --git a/src/editor.cpp b/src/editor.cpp index 98dca046..b88ac4cf 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -726,14 +726,14 @@ void Editor::populateConnectionsList() { const QSignalBlocker blocker1(ui->comboBox_DiveMap); const QSignalBlocker blocker2(ui->comboBox_EmergeMap); + // TODO: We should probably just be doing this once, and updating the items when new maps are created. ui->comboBox_DiveMap->clear(); - ui->comboBox_DiveMap->addItems(project->mapNames); - ui->comboBox_DiveMap->setCurrentText(""); - ui->comboBox_DiveMap->lineEdit()->setClearButtonEnabled(true); - ui->comboBox_EmergeMap->clear(); + ui->comboBox_DiveMap->addItems(project->mapNames); ui->comboBox_EmergeMap->addItems(project->mapNames); + ui->comboBox_DiveMap->setCurrentText(""); ui->comboBox_EmergeMap->setCurrentText(""); + ui->comboBox_DiveMap->lineEdit()->setClearButtonEnabled(true); ui->comboBox_EmergeMap->lineEdit()->setClearButtonEnabled(true); for (MapConnection* connection : map->connections) { @@ -745,24 +745,22 @@ void Editor::populateConnectionsList() { } // Clear any existing connections in list - for (auto w : ui->scrollAreaContents_ConnectionsList->findChildren()) - w->deleteLater(); + for (auto listItem : ui->scrollAreaContents_ConnectionsList->findChildren()) + listItem->deleteLater(); for (auto item :connection_items) addConnectionToList(item); } +// TODO: When connecting a map to itself the new mirror is shaded incorrectly +// TODO: Set default focus to the selected connection, right now it defaults to the dive combo box + void Editor::addConnectionToList(ConnectionPixmapItem * connectionItem) { ConnectionsListItem *listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, connectionItem->connection, project->mapNames); ui->layout_ConnectionsList->insertWidget(ui->layout_ConnectionsList->count() - 1, listItem); // Insert above the vertical spacer - // Connect the pixmap item to the list item - connect(connectionItem, &ConnectionPixmapItem::connectionMoved, listItem, &ConnectionsListItem::updateUI); + // Sync the selection highlight between the list UI and the graphical map connection connect(connectionItem, &ConnectionPixmapItem::highlightChanged, listItem, &ConnectionsListItem::setSelected); - connect(connectionItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater); - if (connectionItem == selected_connection_item) - listItem->setSelected(true); - connect(listItem, &ConnectionsListItem::selected, [this, connectionItem] { // When the list item is selected, select the pixmap too if (connectionItem == selected_connection_item) { @@ -774,32 +772,113 @@ void Editor::addConnectionToList(ConnectionPixmapItem * connectionItem) { selected_connection_item = connectionItem; selected_connection_item->updateHighlight(true); }); - connect(listItem, &ConnectionsListItem::edited, [this, connectionItem] { - // When the list item is edited update the pixmap - // TODO: This is probably slower than necessary (we don't need a full redraw if we're just moving it) - // TODO: Handle mirroring - redrawConnection(connectionItem); - emit editedMapData(); + if (connectionItem == selected_connection_item) + listItem->setSelected(true); + + // Sync edits to 'offset' between the list UI and the graphical map connection + connect(connectionItem, &ConnectionPixmapItem::connectionMoved, [this, listItem](MapConnection* connection, int offset) { + setConnectionOffset(connection, offset); + listItem->updateUI(); }); - connect(listItem, &ConnectionsListItem::deleteRequested, [this, connectionItem] { + connect (listItem, &ConnectionsListItem::editedOffset, [this, connectionItem](MapConnection* connection, int offset) { + setConnectionOffset(connection, offset); + updateConnectionItemPos(connectionItem); + }); + + // Sync edits to 'direction' or 'map' between the list UI and the graphical map connection. + // These are 1-way because the direction and map cannot be edited graphically, only via the list UI. + connect(listItem, &ConnectionsListItem::editedDirection, [this, connectionItem](MapConnection* connection, QString direction) { + setConnectionDirection(connection, direction); + updateConnectionItem(connectionItem); // TODO: Simplify? + }); + connect(listItem, &ConnectionsListItem::editedMapName, [this, connectionItem](MapConnection* connection, QString mapName) { + setConnectionMap(connection, mapName); + updateConnectionItem(connectionItem); + }); + + // Sync deleting the map connection + connect(connectionItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater); + connect(listItem, &ConnectionsListItem::removed, [this, connectionItem] { // 'Remove' button has been clicked. Delete the pixmap. - // The list item will be deleted via the earlier connection to the pixmap's 'destroyed' signal. - removeConnection(connectionItem); + // The list item will be deleted via the above connection to the pixmap's 'destroyed' signal. + removeConnectionItem(connectionItem); }); - connect(listItem, &ConnectionsListItem::doubleClicked, [this, connectionItem] { - // Double clicking the list item opens the connected map - emit connectionItemDoubleClicked(connectionItem->connection->map_name, map->name); + + // Double clicking the list item opens the connected map + connect(listItem, &ConnectionsListItem::doubleClicked, [this](QString connectedMapName) { + emit connectionItemDoubleClicked(connectedMapName, map->name); }); } -void Editor::removeConnection(ConnectionPixmapItem* connectionItem) { + +void Editor::addNewConnection() { + // Find direction with least number of connections. + QMap directionCounts = QMap({{"up", 0}, {"right", 0}, {"down", 0}, {"left", 0}}); + for (MapConnection* connection : map->connections) { + directionCounts[connection->direction]++; + } + QString minDirection = "up"; + int minCount = INT_MAX; + for (QString direction : directionCounts.keys()) { + if (directionCounts[direction] < minCount) { + minDirection = direction; + minCount = directionCounts[direction]; + } + } + + // Prefer not to connect the map to itself (we have to if it's the only map). + // TODO: Is this more or less sensible than a connection with no map name + QString defaultMapName = project->mapNames.first(); + if (defaultMapName == map->name && project->mapNames.length() > 1) { + defaultMapName = project->mapNames.at(1); + } + + MapConnection* newConnection = new MapConnection; + newConnection->direction = minDirection; + newConnection->offset = 0; + newConnection->map_name = defaultMapName; + addConnection(map, newConnection); + onConnectionItemSelected(connection_items.last()); + addMirroredConnection(*newConnection); +} + +void Editor::addConnection(Map* map, MapConnection * connection) { + map->connections.append(connection); + if (map == this->map) { + // Adding a connection to the current map, we need to display it visually. + // Note that for the Dive/Emerge combo boxes this is normally redundant + // as the user can only create these by having already set the text, + // *except* in the case where they're connecting a map to itself. + if (connection->direction == "dive") { + const QSignalBlocker blocker(ui->comboBox_DiveMap); + ui->comboBox_DiveMap->setCurrentText(connection->map_name); + } else if (connection->direction == "emerge") { + const QSignalBlocker blocker(ui->comboBox_EmergeMap); + ui->comboBox_EmergeMap->setCurrentText(connection->map_name); + } else { + createConnectionItem(connection); + addConnectionToList(connection_items.last()); + } + } + emit editedMapData(map); +} + +void Editor::removeConnectionItem(ConnectionPixmapItem* connectionItem, bool removeMirror) { if (!connectionItem) return; - map->connections.removeOne(connectionItem->connection); - connection_items.removeOne(connectionItem); - //removeMirroredConnection(connectionItem->connection); // TODO + if (connectionItem->connection) { + if (removeMirror) { + // If a map is connected to itself and we delete the connection then this function + // will be called twice, once for the target of the delete and once for its mirror. + // We only want to try deleting the mirror once, on the first call (because the mirror + // of the mirror is the original target again, and we already deleted it). + removeMirroredConnection(*connectionItem->connection); + } + removeConnection(map, connectionItem->connection); + } + connection_items.removeOne(connectionItem); if (connectionItem->scene()) connectionItem->scene()->removeItem(connectionItem); @@ -808,14 +887,136 @@ void Editor::removeConnection(ConnectionPixmapItem* connectionItem) { if (!connection_items.isEmpty()) onConnectionItemSelected(connection_items.first()); } - - delete connectionItem->connection; delete connectionItem; - emit editedMapData(); +} + +void Editor::removeConnection(Map* map, MapConnection* connection) { + if (!map) + return; + map->connections.removeOne(connection); + delete connection; + emit editedMapData(map); } void Editor::removeSelectedConnection() { - removeConnection(selected_connection_item); + removeConnectionItem(selected_connection_item); +} + +static const QMap oppositeDirections = { + {"up", "down"}, {"down", "up"}, + {"right", "left"}, {"left", "right"}, + {"dive", "emerge"}, {"emerge", "dive"} +}; + +bool Editor::shouldMirrorConnection(const MapConnection &source) { + // The current map's connections are not mirrored if the user has disabled this setting, + // unless it's a connection to itself (which is mirrored by definition). + // TODO: Confirm in-game representation of the above fact is accurate. + return ui->checkBox_MirrorConnections->isChecked() || (map && map->name == source.map_name); +} + +MapConnectionMirror Editor::getMirroredConnection(const MapConnection &source) { + MapConnectionMirror mirror; + if (!map || !shouldMirrorConnection(source)) + return mirror; + + // Note: It's possible (and ok) for mirror.map == this->map + mirror.map = project->getMap(source.map_name); + if (!mirror.map) + return mirror; + + MapConnection target; + target.direction = oppositeDirections.value(source.direction); + target.map_name = map->name; + target.offset = -source.offset; + + // Find the matching connection in the connected map. + for (auto connection : mirror.map->connections) { + if (*connection == target) { + mirror.connection = connection; + break; + } + } + return mirror; +} + +void Editor::addMirroredConnection(const MapConnection &source) { + MapConnectionMirror mirror = getMirroredConnection(source); + if (!mirror.map) + return; + + mirror.connection = new MapConnection; + mirror.connection->direction = oppositeDirections.value(source.direction); + mirror.connection->map_name = map->name; + mirror.connection->offset = -source.offset; + + addConnection(mirror.map, mirror.connection); +} + +void Editor::removeMirroredConnection(const MapConnection &source) { + MapConnectionMirror mirror = getMirroredConnection(source); + if (!mirror.map || !mirror.connection) + return; + + if (map == mirror.map) { + // The connection to delete is displayed on the currently-opened map, we need to delete it visually as well. + if (source.direction == "dive") { + const QSignalBlocker blocker(ui->comboBox_DiveMap); + ui->comboBox_DiveMap->setCurrentText(""); + } else if (source.direction == "emerge") { + const QSignalBlocker blocker(ui->comboBox_EmergeMap); + ui->comboBox_EmergeMap->setCurrentText(""); + } else { + // Find and delete the matching connection graphics + for (auto item :connection_items) { + if (item->connection == mirror.connection) { + removeConnectionItem(item, false); + return; + } + } + } + } + removeConnection(mirror.map, mirror.connection); +} + +void Editor::updateDiveMap(QString mapName) { + updateDiveEmergeMap(mapName, "dive"); +} + +void Editor::updateEmergeMap(QString mapName) { + updateDiveEmergeMap(mapName, "emerge"); +} + +void Editor::updateDiveEmergeMap(QString mapName, QString direction) { + if (!mapName.isEmpty() && !project->mapNamesToMapConstants.contains(mapName)) { + logError(QString("Invalid %1 connection map name: '%2'").arg(direction).arg(mapName)); + return; + } + + // TODO: How does the game handle having multiple Dive/Emerge maps. Should we support this? + MapConnection* connection = nullptr; + for (MapConnection* conn : map->connections) { + if (conn->direction == direction) { + connection = conn; + break; + } + } + + // We (lazily) always update the connection by deleting the old one and creating a new one. + // Unlike the displayed connections which can be updated rapidly by click+drag, this isn't + // really an issue for Dive/Emerge maps. + if (connection) { + removeMirroredConnection(*connection); + removeConnection(map, connection); + } + if (!mapName.isEmpty() && mapName != DYNAMIC_MAP_NAME) { + connection = new MapConnection; + connection->direction = direction; + connection->offset = 0; + connection->map_name = mapName; + addConnection(map, connection); + addMirroredConnection(*connection); + } } QPoint Editor::calculateConnectionPosition(const MapConnection *connection, const QPixmap &pixmap) { @@ -837,7 +1038,7 @@ QPoint Editor::calculateConnectionPosition(const MapConnection *connection, cons return QPoint(x, y); } -void Editor::redrawConnection(ConnectionPixmapItem* connectionItem) { +void Editor::updateConnectionItem(ConnectionPixmapItem* connectionItem) { if (!connectionItem || !connectionItem->connection) return; @@ -870,36 +1071,80 @@ void Editor::redrawConnection(ConnectionPixmapItem* connectionItem) { connectionItem->setZValue(-1); connectionItem->blockSignals(false); - // TODO: - //updateMirroredConnectionMap(selected_connection_item->connection, originalMapName); - maskNonVisibleConnectionTiles(); } -// TODO: Generalize, maybe use in addConnectionToList connection instead of full render -void Editor::updateConnectionOffset(int offset) { - /*if (!selected_connection_item) +void Editor::updateConnectionItemPos(ConnectionPixmapItem* connectionItem) { + if (!connectionItem || !connectionItem->connection) return; - selected_connection_item->blockSignals(true); - selected_connection_item->connection->offset = offset; - if (selected_connection_item->connection->direction == "up" || selected_connection_item->connection->direction == "down") { - selected_connection_item->setX(selected_connection_item->initialX + (offset - selected_connection_item->initialOffset) * 16); - } else if (selected_connection_item->connection->direction == "left" || selected_connection_item->connection->direction == "right") { - selected_connection_item->setY(selected_connection_item->initialY + (offset - selected_connection_item->initialOffset) * 16); + MapConnection *connection = connectionItem->connection; + connectionItem->blockSignals(true); + if (connection->direction == "up" || connection->direction == "down") { + connectionItem->setX(connectionItem->initialX + (connection->offset - connectionItem->initialOffset) * 16); + } else if (connection->direction == "left" || connection->direction == "right") { + connectionItem->setY(connectionItem->initialY + (connection->offset - connectionItem->initialOffset) * 16); } - selected_connection_item->blockSignals(false); - updateMirroredConnectionOffset(selected_connection_item->connection); - maskNonVisibleConnectionTiles();*/ + connectionItem->blockSignals(false); + maskNonVisibleConnectionTiles(); } -void Editor::onConnectionMoved(MapConnection* connection) { - // TODO: - //updateMirroredConnectionOffset(connection); +void Editor::setConnectionOffset(MapConnection *connection, int offset) { + if (!connection || !map || connection->offset == offset) + return; + + MapConnectionMirror mirror = getMirroredConnection(*connection); + if (mirror.connection && mirror.map) { + mirror.connection->offset = -offset; + if (mirror.map != map) { + emit editedMapData(mirror.map); + } else { + // The mirror is displayed on the current map, update its graphics + for (auto item :connection_items) { + if (item->connection == mirror.connection) { + updateConnectionItemPos(item); + break; + } + } + // TODO: We should be signaling to the list item from the pixmap item, rather than searching for it. + for (auto listItem : ui->scrollAreaContents_ConnectionsList->findChildren()) { + if (listItem->connection == mirror.connection){ + listItem->updateUI(); + break; + } + } + } + } + // TODO: If there's no mirror but there should be we're not creating one + + connection->offset = offset; + emit editedMapData(map); // TODO: This is likely the source of the visual masking bug while dragging (this happens after the move) maskNonVisibleConnectionTiles(); - emit editedMapData(); +} + +void Editor::setConnectionMap(MapConnection *connection, const QString &mapName) { + if (!connection || !map || connection->map_name == mapName) + return; + + removeMirroredConnection(*connection); + connection->map_name = mapName; + addMirroredConnection(*connection); + + emit editedMapData(map); +} + +void Editor::setConnectionDirection(MapConnection *connection, const QString &direction) { + if (!connection || !map || connection->direction == direction) + return; + + // TODO: Lazy + removeMirroredConnection(*connection); + connection->direction = direction; + addMirroredConnection(*connection); + + emit editedMapData(map); } void Editor::onConnectionItemSelected(ConnectionPixmapItem* connectionItem) { @@ -1579,7 +1824,6 @@ void Editor::createConnectionItem(MapConnection* connection) { item->setZValue(-1); scene->addItem(item); - connect(item, &ConnectionPixmapItem::connectionMoved, this, &Editor::onConnectionMoved); connect(item, &ConnectionPixmapItem::connectionItemSelected, this, &Editor::onConnectionItemSelected); connect(item, &ConnectionPixmapItem::connectionItemDoubleClicked, [this, item] { emit this->connectionItemDoubleClicked(item->connection->map_name, map->name); @@ -1703,144 +1947,6 @@ void Editor::displayMapGrid() { connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::onToggleGridClicked); } -void Editor::addNewConnection() { - // Find direction with least number of connections. - QMap directionCounts = QMap({{"up", 0}, {"right", 0}, {"down", 0}, {"left", 0}}); - for (MapConnection* connection : map->connections) { - directionCounts[connection->direction]++; - } - QString minDirection = "up"; - int minCount = INT_MAX; - for (QString direction : directionCounts.keys()) { - if (directionCounts[direction] < minCount) { - minDirection = direction; - minCount = directionCounts[direction]; - } - } - - // Don't connect the map to itself. - QString defaultMapName = project->mapNames.first(); - if (defaultMapName == map->name) { - defaultMapName = project->mapNames.value(1); - } - - MapConnection* newConnection = new MapConnection; - newConnection->direction = minDirection; - newConnection->offset = 0; - newConnection->map_name = defaultMapName; - map->connections.append(newConnection); - createConnectionItem(newConnection); - addConnectionToList(connection_items.last()); - onConnectionItemSelected(connection_items.last()); - - updateMirroredConnection(newConnection, newConnection->direction, newConnection->map_name); -} - -void Editor::updateMirroredConnectionOffset(MapConnection* connection) { - updateMirroredConnection(connection, connection->direction, connection->map_name); -} -void Editor::updateMirroredConnectionDirection(MapConnection* connection, QString originalDirection) { - updateMirroredConnection(connection, originalDirection, connection->map_name); -} -void Editor::updateMirroredConnectionMap(MapConnection* connection, QString originalMapName) { - updateMirroredConnection(connection, connection->direction, originalMapName); -} -void Editor::removeMirroredConnection(MapConnection* connection) { - updateMirroredConnection(connection, connection->direction, connection->map_name, true); -} -void Editor::updateMirroredConnection(MapConnection* connection, QString originalDirection, QString originalMapName, bool isDelete) { - if (!ui->checkBox_MirrorConnections->isChecked()) - return; - Map* otherMap = project->getMap(originalMapName); - if (!otherMap) - return; - - static QMap oppositeDirections = QMap({ - {"up", "down"}, {"right", "left"}, - {"down", "up"}, {"left", "right"}, - {"dive", "emerge"},{"emerge", "dive"}}); - QString oppositeDirection = oppositeDirections.value(originalDirection); - - // Find the matching connection in the connected map. - MapConnection* mirrorConnection = nullptr; - for (MapConnection* conn : otherMap->connections) { - if (conn->direction == oppositeDirection && conn->map_name == map->name) { - mirrorConnection = conn; - } - } - - if (isDelete) { - if (mirrorConnection) { - otherMap->connections.removeOne(mirrorConnection); - delete mirrorConnection; - } - return; - } - - if (connection->direction != originalDirection || connection->map_name != originalMapName) { - if (mirrorConnection) { - otherMap->connections.removeOne(mirrorConnection); - delete mirrorConnection; - mirrorConnection = nullptr; - otherMap = project->getMap(connection->map_name); - } - } - - // Create a new mirrored connection, if a matching one doesn't already exist. - if (!mirrorConnection) { - mirrorConnection = new MapConnection; - mirrorConnection->direction = oppositeDirections.value(connection->direction); - mirrorConnection->map_name = map->name; - otherMap->connections.append(mirrorConnection); - } - - mirrorConnection->offset = -connection->offset; -} - -void Editor::updateDiveMap(QString mapName) { - updateDiveEmergeMap(mapName, "dive"); -} - -void Editor::updateEmergeMap(QString mapName) { - updateDiveEmergeMap(mapName, "emerge"); -} - -void Editor::updateDiveEmergeMap(QString mapName, QString direction) { - if (!mapName.isEmpty() && !project->mapNamesToMapConstants.contains(mapName)) { - logError(QString("Invalid %1 connection map name: '%2'").arg(direction).arg(mapName)); - return; - } - - MapConnection* connection = nullptr; - for (MapConnection* conn : map->connections) { - if (conn->direction == direction) { - connection = conn; - break; - } - } - - if (mapName.isEmpty() || mapName == DYNAMIC_MAP_NAME) { - // Remove dive/emerge connection - if (connection) { - map->connections.removeOne(connection); - removeMirroredConnection(connection); - } - } else { - if (!connection) { - connection = new MapConnection; - connection->direction = direction; - connection->offset = 0; - connection->map_name = mapName; - map->connections.append(connection); - updateMirroredConnection(connection, connection->direction, connection->map_name); - } else { - QString originalMapName = connection->map_name; - connection->map_name = mapName; - updateMirroredConnectionMap(connection, originalMapName); - } - } -} - void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) { if (map->layout->tileset_primary_label != tilesetLabel || forceLoad) @@ -1893,7 +1999,7 @@ void Editor::updateBorderVisibility() { void Editor::updateCustomMapHeaderValues(QTableWidget *table) { map->customHeaders = CustomAttributesTable::getAttributes(table); - emit editedMapData(); + emit editedMapData(map); } Tileset* Editor::getCurrentMapPrimaryTileset() diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7de1c54b..38af29d2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -310,7 +310,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::wildMonDataChanged, this, &MainWindow::onWildMonDataChanged); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); - connect(this->editor, &Editor::editedMapData, this, &MainWindow::markMapEdited); + connect(this->editor, &Editor::editedMapData, [this](Map* map) { this->markMapEdited(map); }); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts); connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor); @@ -413,10 +413,19 @@ void MainWindow::showWindowTitle() { } void MainWindow::markMapEdited() { - if (editor && editor->map) { - editor->map->hasUnsavedDataChanges = true; + if (editor) markMapEdited(editor->map); +} + +void MainWindow::markMapEdited(Map* map) { + if (!map) + return; + map->hasUnsavedDataChanges = true; + + // TODO: Only update the necessary list icon + updateMapList(); + + if (editor && editor->map == map) showWindowTitle(); - } } // Update the UI using information we've read from the user's project files. @@ -769,7 +778,6 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { showWindowTitle(); - connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged); connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); @@ -2440,10 +2448,6 @@ void MainWindow::onConnectionItemDoubleClicked(QString mapName, QString fromMapN editor->setSelectedConnectionFromMap(fromMapName); } -void MainWindow::onMapChanged(Map *) { - updateMapList(); -} - void MainWindow::onMapNeedsRedrawing() { redrawMapScene(); } @@ -2555,9 +2559,8 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { void MainWindow::on_pushButton_AddConnection_clicked() { - // TODO: Bring up a prompt for information. Mark the current map *AND* the connected map as edited + // TODO: Bring up a prompt for information? editor->addNewConnection(); - markMapEdited(); } void MainWindow::on_pushButton_NewWildMonGroup_clicked() { @@ -2584,20 +2587,15 @@ void MainWindow::on_button_OpenEmergeMap_clicked() { userSetMap(mapName, true); } -// TODO: Mirror change to/from other maps void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { // Include empty names as an update (user is deleting the connection) - if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) { + if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) editor->updateDiveMap(mapName); - markMapEdited(); - } } void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName) { - if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) { + if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) editor->updateEmergeMap(mapName); - markMapEdited(); - } } void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index 301fcfb6..060f084f 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -38,8 +38,8 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari y = this->initialY; } - this->connection->offset = newOffset; - emit connectionMoved(this->connection); + if (this->connection->offset != newOffset) + emit connectionMoved(this->connection, newOffset); return QPointF(x, y); } else { diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index b9f456a3..f40ae57f 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -5,8 +5,7 @@ static const QStringList directions = {"up", "down", "left", "right"}; ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) : QFrame(parent), - ui(new Ui::ConnectionsListItem), - connection(connection) + ui(new Ui::ConnectionsListItem) { ui->setupUi(this); @@ -24,6 +23,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->spinBox_Offset->setMinimum(INT_MIN); ui->spinBox_Offset->setMaximum(INT_MAX); + this->connection = connection; this->updateUI(); } @@ -58,33 +58,31 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { } void ConnectionsListItem::mouseDoubleClickEvent(QMouseEvent *) { - emit doubleClicked(); + emit doubleClicked(this->connection->map_name); } void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(const QString &direction) { - this->connection->direction = direction; this->setSelected(true); - emit this->edited(); + if (this->connection->direction != direction) + emit this->editedDirection(this->connection, direction); } void ConnectionsListItem::on_comboBox_Map_currentTextChanged(const QString &mapName) { - if (ui->comboBox_Map->findText(mapName) >= 0) { - this->connection->map_name = mapName; - this->setSelected(true); - emit this->edited(); - } + this->setSelected(true); + if (ui->comboBox_Map->findText(mapName) >= 0 && this->connection->map_name != mapName) + emit this->editedMapName(this->connection, mapName); } void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) { - this->connection->offset = offset; this->setSelected(true); - emit this->edited(); + if (this->connection->offset != offset) + emit editedOffset(this->connection, offset); } void ConnectionsListItem::on_button_Delete_clicked() { - emit this->deleteRequested(); + emit this->removed(); }