diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index cdb4dcb8..c2f0647c 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1715,7 +1715,7 @@ 0 0 100 - 16 + 30 @@ -1809,7 +1809,7 @@ 0 0 100 - 16 + 30 @@ -1903,7 +1903,7 @@ 0 0 100 - 16 + 30 @@ -2003,7 +2003,7 @@ 0 0 100 - 16 + 30 @@ -2097,7 +2097,7 @@ 0 0 100 - 16 + 30 @@ -2596,6 +2596,9 @@ true + + true + @@ -2667,6 +2670,9 @@ true + + true + @@ -2757,8 +2763,8 @@ 0 0 - 365 - 658 + 100 + 30 diff --git a/include/core/map.h b/include/core/map.h index a6f6fa1c..83f9a9d1 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -98,7 +98,7 @@ public: QStringList getScriptLabels(Event::Group group = Event::Group::None); void removeEvent(Event *); void addEvent(Event *); - QPixmap renderConnection(MapConnection, MapLayout *); + QPixmap renderConnection(const MapConnection&, MapLayout *); QPixmap renderBorder(bool ignoreCache = false); void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 474be5b1..a7077813 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -12,6 +12,12 @@ public: QString direction; int offset; QString map_name; + + static const QStringList cardinalDirections; + static bool isCardinal(const QString &direction); + static bool isHorizontal(const QString &direction); + static bool isVertical(const QString &direction); + static MapConnection mirror(const MapConnection &source, const QString &mapName); }; struct MapConnectionMirror { diff --git a/include/editor.h b/include/editor.h index f0b54b69..7c878023 100644 --- a/include/editor.h +++ b/include/editor.h @@ -168,14 +168,13 @@ private: QPixmap collisionSheetPixmap; void updateBorderVisibility(); - QPoint calculateConnectionPosition(const MapConnection *connection, const QPixmap &pixmap); + QPoint calculateConnectionPosition(const MapConnection &connection, const QPixmap &pixmap); + QPixmap getConnectionPixmap(const MapConnection &connection); void updateConnectionItem(ConnectionPixmapItem* connectionItem); void updateConnectionItemPos(ConnectionPixmapItem* connectionItem); void createConnectionItem(MapConnection* connection); - void populateConnectionsList(); void addConnectionToList(ConnectionPixmapItem* connection); void updateDiveEmergeMap(QString mapName, QString direction); - bool shouldMirrorConnection(const MapConnection &source); MapConnectionMirror getMirroredConnection(const MapConnection&); void addMirroredConnection(const MapConnection&); void removeMirroredConnection(const MapConnection&); @@ -197,7 +196,7 @@ private slots: 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 setSelectedConnection(ConnectionPixmapItem* connectionItem); void onHoveredMovementPermissionChanged(uint16_t, uint16_t); void onHoveredMovementPermissionCleared(); void onHoveredMetatileSelectionChanged(uint16_t); diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index f0e00ae1..4f18b649 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -28,13 +28,14 @@ public: int initialOffset; int baseMapWidth; int baseMapHeight; - void render(qreal opacity = 1); + void setEditable(bool editable); bool getEditable(); - void updateHighlight(bool selected); + void setSelected(bool selected); + void render(); private: - bool highlighted = false; + bool selected = false; protected: QVariant itemChange(GraphicsItemChange change, const QVariant &value); @@ -42,10 +43,9 @@ protected: void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*); signals: - void connectionItemSelected(ConnectionPixmapItem* connectionItem); void connectionItemDoubleClicked(ConnectionPixmapItem* connectionItem); void connectionMoved(MapConnection *, int newOffset); - void highlightChanged(bool highlighted); + void selectionChanged(bool selected); }; #endif // CONNECTIONPIXMAPITEM_H diff --git a/include/ui/noscrollcombobox.h b/include/ui/noscrollcombobox.h index 59c680a3..43db9deb 100644 --- a/include/ui/noscrollcombobox.h +++ b/include/ui/noscrollcombobox.h @@ -13,6 +13,7 @@ public: void setTextItem(const QString &text); void setNumberItem(int value); void setHexItem(uint32_t value); + void setClearButtonEnabled(bool enabled); private: void setItem(int index, const QString &text); diff --git a/porymap.pro b/porymap.pro index 5c898302..8afc99a5 100644 --- a/porymap.pro +++ b/porymap.pro @@ -24,6 +24,7 @@ SOURCES += src/core/block.cpp \ src/core/heallocation.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ + src/core/mapconnection.cpp \ src/core/maplayout.cpp \ src/core/mapparser.cpp \ src/core/metatile.cpp \ diff --git a/src/core/map.cpp b/src/core/map.cpp index 463b9341..fad4683a 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -217,34 +217,21 @@ QPixmap Map::renderBorder(bool ignoreCache) { return layout->border_pixmap; } -QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout) { - int x, y, w, h; +QPixmap Map::renderConnection(const MapConnection &connection, MapLayout * fromLayout) { + if (!MapConnection::isCardinal(connection.direction)) + return QPixmap(); + + int x = 0, y = 0, w = getWidth(), h = getHeight(); if (connection.direction == "up") { - x = 0; y = getHeight() - BORDER_DISTANCE; - w = getWidth(); h = BORDER_DISTANCE; } else if (connection.direction == "down") { - x = 0; - y = 0; - w = getWidth(); h = BORDER_DISTANCE; } else if (connection.direction == "left") { x = getWidth() - BORDER_DISTANCE; - y = 0; w = BORDER_DISTANCE; - h = getHeight(); } else if (connection.direction == "right") { - x = 0; - y = 0; w = BORDER_DISTANCE; - h = getHeight(); - } else { - // this should not happen - x = 0; - y = 0; - w = getWidth(); - h = getHeight(); } render(true, fromLayout, QRect(x, y, w, h)); diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index ebf48592..82a0b71e 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -1,43 +1,35 @@ #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); -} +const QStringList MapConnection::cardinalDirections = { + "up", "down", "left", "right" +}; -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) { +MapConnection MapConnection::mirror(const MapConnection &source, const QString &mapName) { static const QMap oppositeDirections = { {"up", "down"}, {"down", "up"}, {"right", "left"}, {"left", "right"}, {"dive", "emerge"}, {"emerge", "dive"} }; - return oppositeDirections.value(direction); + + // TODO: Allowing editing unknown directions is a can of worms. + // Specifically a self-connection with an empty direction and an offset of 0 can be identified as its own mirror + MapConnection mirror; + mirror.direction = oppositeDirections.value(source.direction/*, source.direction*/); + mirror.map_name = mapName; + mirror.offset = -source.offset; + + return mirror; } -static MapConnection* MapConnection::getMirror(const MapConnection*, const Map*) { - +bool MapConnection::isHorizontal(const QString &direction) { + return direction == "left" || direction == "right"; } -static MapConnection* MapConnection::newMirror(const MapConnection*) { +bool MapConnection::isVertical(const QString &direction) { + return direction == "up" || direction == "down"; +} -}*/ +bool MapConnection::isCardinal(const QString &direction) { + return cardinalDirections.contains(direction); +} diff --git a/src/editor.cpp b/src/editor.cpp index b88ac4cf..0f23f331 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -89,7 +89,6 @@ void Editor::setEditingMap() { current_view = map_item; if (map_item) { map_item->paintingMode = MapPixmapItem::PaintMode::Metatiles; - displayMapConnections(); map_item->draw(); map_item->setVisible(true); } @@ -109,7 +108,6 @@ void Editor::setEditingMap() { void Editor::setEditingCollision() { current_view = collision_item; if (collision_item) { - displayMapConnections(); collision_item->draw(); collision_item->setVisible(true); } @@ -135,7 +133,6 @@ void Editor::setEditingObjects() { } if (map_item) { map_item->paintingMode = MapPixmapItem::PaintMode::EventObjects; - displayMapConnections(); map_item->draw(); map_item->setVisible(true); } @@ -172,8 +169,6 @@ void Editor::setEditingConnections() { map_item->paintingMode = MapPixmapItem::PaintMode::Disabled; map_item->draw(); map_item->setVisible(true); - populateConnectionsList(); - maskNonVisibleConnectionTiles(); } if (collision_item) { collision_item->setVisible(false); @@ -722,58 +717,15 @@ void Editor::updateEncounterFields(EncounterFields newFields) { project->wildMonFields = newFields; } -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_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) { - if (connection->direction == "dive") { - ui->comboBox_DiveMap->setCurrentText(connection->map_name); - } else if (connection->direction == "emerge") { - ui->comboBox_EmergeMap->setCurrentText(connection->map_name); - } - } - - // Clear any existing connections in list - 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 // Sync the selection highlight between the list UI and the graphical map connection - connect(connectionItem, &ConnectionPixmapItem::highlightChanged, listItem, &ConnectionsListItem::setSelected); + connect(connectionItem, &ConnectionPixmapItem::selectionChanged, listItem, &ConnectionsListItem::setSelected); connect(listItem, &ConnectionsListItem::selected, [this, connectionItem] { - // When the list item is selected, select the pixmap too - if (connectionItem == selected_connection_item) { - // Already selected, no change - return; - } - // Deselect old connection and select new connection - if (selected_connection_item) selected_connection_item->updateHighlight(false); - selected_connection_item = connectionItem; - selected_connection_item->updateHighlight(true); + setSelectedConnection(connectionItem); }); - 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) { @@ -787,9 +739,10 @@ void Editor::addConnectionToList(ConnectionPixmapItem * 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) { + connect(listItem, &ConnectionsListItem::editedDirection, [this, connectionItem, listItem](MapConnection* connection, QString direction) { setConnectionDirection(connection, direction); - updateConnectionItem(connectionItem); // TODO: Simplify? + updateConnectionItem(connectionItem); // TODO: Simplify + listItem->updateUI(); // Changing direction may have changed our offset too }); connect(listItem, &ConnectionsListItem::editedMapName, [this, connectionItem](MapConnection* connection, QString mapName) { setConnectionMap(connection, mapName); @@ -813,13 +766,13 @@ void Editor::addConnectionToList(ConnectionPixmapItem * connectionItem) { void Editor::addNewConnection() { // Find direction with least number of connections. - QMap directionCounts = QMap({{"up", 0}, {"right", 0}, {"down", 0}, {"left", 0}}); + QMap directionCounts; for (MapConnection* connection : map->connections) { directionCounts[connection->direction]++; } QString minDirection = "up"; int minCount = INT_MAX; - for (QString direction : directionCounts.keys()) { + for (QString direction : MapConnection::cardinalDirections) { if (directionCounts[direction] < minCount) { minDirection = direction; minCount = directionCounts[direction]; @@ -827,7 +780,6 @@ void Editor::addNewConnection() { } // 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); @@ -838,11 +790,14 @@ void Editor::addNewConnection() { newConnection->offset = 0; newConnection->map_name = defaultMapName; addConnection(map, newConnection); - onConnectionItemSelected(connection_items.last()); + setSelectedConnection(connection_items.last()); addMirroredConnection(*newConnection); } void Editor::addConnection(Map* map, MapConnection * connection) { + if (!map || !connection) + return; + map->connections.append(connection); if (map == this->map) { // Adding a connection to the current map, we need to display it visually. @@ -857,7 +812,6 @@ void Editor::addConnection(Map* map, MapConnection * connection) { ui->comboBox_EmergeMap->setCurrentText(connection->map_name); } else { createConnectionItem(connection); - addConnectionToList(connection_items.last()); } } emit editedMapData(map); @@ -885,7 +839,7 @@ void Editor::removeConnectionItem(ConnectionPixmapItem* connectionItem, bool rem if (connectionItem == selected_connection_item) { selected_connection_item = nullptr; if (!connection_items.isEmpty()) - onConnectionItemSelected(connection_items.first()); + setSelectedConnection(connection_items.first()); } delete connectionItem; } @@ -902,22 +856,9 @@ void Editor::removeSelectedConnection() { 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)) + if (!map || !ui->checkBox_MirrorConnections->isChecked()) return mirror; // Note: It's possible (and ok) for mirror.map == this->map @@ -925,12 +866,10 @@ MapConnectionMirror Editor::getMirroredConnection(const MapConnection &source) { 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. + // Note: There is no strict source -> mirror pairing, i.e. we are not guaranteed + // to always get the same MapConnection if there are multiple identical copies. + MapConnection target = MapConnection::mirror(source, map->name); for (auto connection : mirror.map->connections) { if (*connection == target) { mirror.connection = connection; @@ -946,10 +885,7 @@ void Editor::addMirroredConnection(const MapConnection &source) { return; mirror.connection = new MapConnection; - mirror.connection->direction = oppositeDirections.value(source.direction); - mirror.connection->map_name = map->name; - mirror.connection->offset = -source.offset; - + *mirror.connection = MapConnection::mirror(source, map->name); addConnection(mirror.map, mirror.connection); } @@ -993,7 +929,7 @@ void Editor::updateDiveEmergeMap(QString mapName, QString direction) { return; } - // TODO: How does the game handle having multiple Dive/Emerge maps. Should we support this? + // Only the first Dive/Emerge map (if present) is considered, as in-game. MapConnection* connection = nullptr; for (MapConnection* conn : map->connections) { if (conn->direction == direction) { @@ -1002,14 +938,18 @@ void Editor::updateDiveEmergeMap(QString mapName, QString direction) { } } - // 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. + // Dive/Emerge maps aren't represented visually (aside from their combo boxes), + // so we have less to do here than a normal connection. if (connection) { - removeMirroredConnection(*connection); - removeConnection(map, connection); - } - if (!mapName.isEmpty() && mapName != DYNAMIC_MAP_NAME) { + // Update existing connection + if (mapName.isEmpty()) { + removeMirroredConnection(*connection); + removeConnection(map, connection); + } else { + setConnectionMap(connection, mapName); + } + } else if (!mapName.isEmpty()) { + // Create new connection connection = new MapConnection; connection->direction = direction; connection->offset = 0; @@ -1019,25 +959,36 @@ void Editor::updateDiveEmergeMap(QString mapName, QString direction) { } } -QPoint Editor::calculateConnectionPosition(const MapConnection *connection, const QPixmap &pixmap) { +QPoint Editor::calculateConnectionPosition(const MapConnection &connection, const QPixmap &pixmap) { int x = 0, y = 0; const int mWidth = 16, mHeight = 16; - if (connection->direction == "up") { - x = connection->offset * mWidth; + if (connection.direction == "up") { + x = connection.offset * mWidth; y = -pixmap.height(); - } else if (connection->direction == "down") { - x = connection->offset * mWidth; + } else if (connection.direction == "down") { + x = connection.offset * mWidth; y = map->getHeight() * mHeight; - } else if (connection->direction == "left") { + } else if (connection.direction == "left") { x = -pixmap.width(); - y = connection->offset * mHeight; - } else if (connection->direction == "right") { + y = connection.offset * mHeight; + } else if (connection.direction == "right") { x = map->getWidth() * mWidth; - y = connection->offset * mHeight; + y = connection.offset * mHeight; } return QPoint(x, y); } +QPixmap Editor::getConnectionPixmap(const MapConnection &connection) { + Map *connectedMap = project->getMap(connection.map_name); + + // connectedMap will be null for MAP_DYNAMIC and any map that fails to load. + // The connection will be editable from the list, but no image will be displayed on the map. + if (!connectedMap) + return QPixmap(); + + return connectedMap->renderConnection(connection, map->layout); +} + void Editor::updateConnectionItem(ConnectionPixmapItem* connectionItem) { if (!connectionItem || !connectionItem->connection) return; @@ -1046,29 +997,22 @@ void Editor::updateConnectionItem(ConnectionPixmapItem* connectionItem) { if (mapName.isEmpty()) return; - // TODO: What happens if a connection is saved with an empty name - if (mapName == DYNAMIC_MAP_NAME || !project->mapNames.contains(mapName)) { + if (!project->mapNames.contains(mapName)) { logError(QString("Invalid map name '%1' specified for connection.").arg(mapName)); return; } - Map *connectedMap = project->getMap(mapName); - if (!connectedMap) - return; - - QPixmap pixmap = connectedMap->renderConnection(*connectionItem->connection, map->layout); connectionItem->initialOffset = connectionItem->connection->offset; - connectionItem->basePixmap = pixmap; - QPoint pos = calculateConnectionPosition(connectionItem->connection, pixmap); + connectionItem->basePixmap = getConnectionPixmap(*connectionItem->connection); + QPoint pos = calculateConnectionPosition(*connectionItem->connection, connectionItem->basePixmap); connectionItem->blockSignals(true); - connectionItem->setPixmap(pixmap); - connectionItem->updateHighlight(connectionItem == selected_connection_item); + connectionItem->setPixmap(connectionItem->basePixmap); connectionItem->initialX = pos.x(); connectionItem->initialY = pos.y(); connectionItem->setX(pos.x()); connectionItem->setY(pos.y()); - connectionItem->setZValue(-1); + connectionItem->render(); connectionItem->blockSignals(false); maskNonVisibleConnectionTiles(); @@ -1080,9 +1024,9 @@ void Editor::updateConnectionItemPos(ConnectionPixmapItem* connectionItem) { MapConnection *connection = connectionItem->connection; connectionItem->blockSignals(true); - if (connection->direction == "up" || connection->direction == "down") { + if (MapConnection::isVertical(connection->direction)) { connectionItem->setX(connectionItem->initialX + (connection->offset - connectionItem->initialOffset) * 16); - } else if (connection->direction == "left" || connection->direction == "right") { + } else if (MapConnection::isHorizontal(connection->direction)) { connectionItem->setY(connectionItem->initialY + (connection->offset - connectionItem->initialOffset) * 16); } connectionItem->blockSignals(false); @@ -1115,7 +1059,6 @@ void Editor::setConnectionOffset(MapConnection *connection, int offset) { } } } - // TODO: If there's no mirror but there should be we're not creating one connection->offset = offset; emit editedMapData(map); @@ -1141,19 +1084,26 @@ void Editor::setConnectionDirection(MapConnection *connection, const QString &di // TODO: Lazy removeMirroredConnection(*connection); + + if (MapConnection::isHorizontal(connection->direction) != MapConnection::isHorizontal(direction) + || MapConnection::isVertical(connection->direction) != MapConnection::isVertical(direction)) { + // If the direction has changed between vertical/horizontal then the old offset may not make sense, so we reset it + setConnectionOffset(connection, 0); + } connection->direction = direction; + addMirroredConnection(*connection); emit editedMapData(map); } -void Editor::onConnectionItemSelected(ConnectionPixmapItem* connectionItem) { - if (!connectionItem) +void Editor::setSelectedConnection(ConnectionPixmapItem* connectionItem) { + if (!connectionItem || connectionItem == selected_connection_item) return; + if (selected_connection_item) selected_connection_item->setSelected(false); selected_connection_item = connectionItem; - for (ConnectionPixmapItem* item : connection_items) - item->updateHighlight(item == selected_connection_item); + selected_connection_item->setSelected(true); } // TODO: Inaccurate if there are multiple connections from the same map @@ -1161,7 +1111,7 @@ void Editor::setSelectedConnectionFromMap(QString mapName) { // Search for the first connection that connects to the given map map. for (ConnectionPixmapItem* item : connection_items) { if (item->connection->map_name == mapName) { - onConnectionItemSelected(item); + setSelectedConnection(item); break; } } @@ -1606,6 +1556,7 @@ bool Editor::displayMap() { displayMapBorder(); displayMapGrid(); displayWildMonTables(); + maskNonVisibleConnectionTiles(); this->map_ruler->setZValue(1000); scene->addItem(this->map_ruler); @@ -1795,41 +1746,56 @@ void Editor::displayMapConnections() { selected_connection_item = nullptr; connection_items.clear(); + const QSignalBlocker blocker1(ui->comboBox_DiveMap); + const QSignalBlocker blocker2(ui->comboBox_EmergeMap); + ui->comboBox_DiveMap->clear(); + ui->comboBox_EmergeMap->clear(); + ui->comboBox_DiveMap->addItems(project->mapNames); + ui->comboBox_EmergeMap->addItems(project->mapNames); + ui->comboBox_DiveMap->setCurrentText(""); + ui->comboBox_EmergeMap->setCurrentText(""); + + // Note: We only support editing 1 Dive and Emerge connection per map. + // In a vanilla game only the first Dive/Emerge connection is considered, so allowing + // users to have multiple is likely to lead to confusion. In case users have changed + // this we won't delete extra diving connections, but we'll only display the first one. for (MapConnection *connection : map->connections) { - if (connection->direction == "dive" || connection->direction == "emerge") { - continue; + if (connection->direction == "dive") { + if (ui->comboBox_DiveMap->currentText().isEmpty()) + ui->comboBox_DiveMap->setCurrentText(connection->map_name); + } else if (connection->direction == "emerge") { + if (ui->comboBox_EmergeMap->currentText().isEmpty()) + ui->comboBox_EmergeMap->setCurrentText(connection->map_name); + } else { + // We allow any unknown direction names here. They'll be editable from the list menu. + createConnectionItem(connection); } - createConnectionItem(connection); } - if (!connection_items.empty()) { - onConnectionItemSelected(connection_items.first()); - } - - maskNonVisibleConnectionTiles(); + if (!connection_items.empty()) + setSelectedConnection(connection_items.first()); } void Editor::createConnectionItem(MapConnection* connection) { - Map *connected_map = project->getMap(connection->map_name); - if (!connected_map) { + if (!connection) return; - } - - QPixmap pixmap = connected_map->renderConnection(*connection, map->layout); - QPoint pos = calculateConnectionPosition(connection, pixmap); - + QPixmap pixmap = getConnectionPixmap(*connection); + QPoint pos = calculateConnectionPosition(*connection, pixmap); ConnectionPixmapItem *item = new ConnectionPixmapItem(pixmap, connection, pos.x(), pos.y(), map->getWidth(), map->getHeight()); item->setX(pos.x()); item->setY(pos.y()); - item->setZValue(-1); + item->render(); scene->addItem(item); - connect(item, &ConnectionPixmapItem::connectionItemSelected, this, &Editor::onConnectionItemSelected); + connect(item, &ConnectionPixmapItem::selectionChanged, [this, item](bool selected) { + if (selected) setSelectedConnection(item); + }); connect(item, &ConnectionPixmapItem::connectionItemDoubleClicked, [this, item] { emit this->connectionItemDoubleClicked(item->connection->map_name, map->name); }); connection_items.append(item); + addConnectionToList(item); } // Hides connected map tiles that cannot be seen from the current map (beyond BORDER_DISTANCE). @@ -1891,14 +1857,12 @@ void Editor::updateMapBorder() { } void Editor::updateMapConnections() { - for (int i = 0; i < connection_items.size(); i++) { - Map *connected_map = project->getMap(connection_items[i]->connection->map_name); - if (!connected_map) + for (auto item : connection_items) { + if (!item->connection) continue; - - QPixmap pixmap = connected_map->renderConnection(*(connection_items[i]->connection), map->layout); - connection_items[i]->basePixmap = pixmap; - connection_items[i]->setPixmap(pixmap); + QPixmap pixmap = getConnectionPixmap(*item->connection); + item->basePixmap = pixmap; + item->setPixmap(pixmap); } maskNonVisibleConnectionTiles(); @@ -1992,7 +1956,7 @@ void Editor::updateBorderVisibility() { item->setVisible(visible); item->setEditable(editingConnections); item->setEnabled(visible); - item->updateHighlight(item == selected_connection_item); + item->render(); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 38af29d2..19377a49 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -736,6 +736,13 @@ void MainWindow::on_action_Reload_Project_triggered() { // setMap, but with a visible error message in case of failure. // Use when the user is specifically requesting a map to open. bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { + if (map_name == DYNAMIC_MAP_NAME) { + QMessageBox msgBox(this); + QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name); + msgBox.critical(nullptr, "Error Opening Map", errorMsg); + return false; + } + if (!setMap(map_name, scrollTreeView)) { QMessageBox msgBox(this); QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3") @@ -750,7 +757,7 @@ bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { bool MainWindow::setMap(QString map_name, bool scrollTreeView) { logInfo(QString("Setting map to '%1'").arg(map_name)); - if (map_name.isEmpty()) { + if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { return false; } @@ -828,10 +835,6 @@ void MainWindow::refreshMapScene() } void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) { - // Can't warp to dynamic maps - if (map_name == DYNAMIC_MAP_NAME) - return; - // Ensure valid destination map name. if (!editor->project->mapNames.contains(map_name)) { logError(QString("Invalid map name '%1'").arg(map_name)); @@ -1846,6 +1849,8 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) clickToolButtonFromEditMode(editor->obj_edit_mode); } else if (index == MainTab::Connections) { editor->setEditingConnections(); + // Stop the Dive/Emerge combo boxes from getting the initial focus + ui->graphicsView_Map->setFocus(); } if (index != MainTab::WildPokemon) { if (editor->project && editor->project->wildEncountersLoaded) @@ -2577,13 +2582,13 @@ void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() { void MainWindow::on_button_OpenDiveMap_clicked() { const QString mapName = ui->comboBox_DiveMap->currentText(); - if (mapName != DYNAMIC_MAP_NAME && editor->project->mapNames.contains(mapName)) + if (editor->project->mapNames.contains(mapName)) userSetMap(mapName, true); } void MainWindow::on_button_OpenEmergeMap_clicked() { const QString mapName = ui->comboBox_EmergeMap->currentText(); - if (mapName != DYNAMIC_MAP_NAME && editor->project->mapNames.contains(mapName)) + if (editor->project->mapNames.contains(mapName)) userSetMap(mapName, true); } diff --git a/src/project.cpp b/src/project.cpp index c6550c6a..06382168 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -117,6 +117,9 @@ void Project::clearTilesetCache() { } Map* Project::loadMap(QString map_name) { + if (map_name == DYNAMIC_MAP_NAME) + return nullptr; + Map *map; if (mapCache.contains(map_name)) { map = mapCache.value(map_name); diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index 060f084f..1b7743f9 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -2,15 +2,28 @@ #include -void ConnectionPixmapItem::render(qreal opacity) { - QPixmap newPixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height()); - if (opacity < 1) { - QPainter painter(&newPixmap); - int alpha = static_cast(255 * (1 - opacity)); - painter.fillRect(0, 0, newPixmap.width(), newPixmap.height(), QColor(0, 0, 0, alpha)); - painter.end(); +void ConnectionPixmapItem::render() { + QPixmap pixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height()); + this->setZValue(-1); + + // When editing is inactive the current selection is ignored, all connections should appear normal. + if (this->getEditable()) { + if (this->selected) { + // Draw highlight + QPainter painter(&pixmap); + painter.setPen(QColor(255, 0, 255)); + painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); + painter.end(); + } else { + // Darken the image + this->setZValue(-2); + QPainter painter(&pixmap); + int alpha = static_cast(255 * 0.25); + painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QColor(0, 0, 0, alpha)); + painter.end(); + } } - this->setPixmap(newPixmap); + this->setPixmap(pixmap); } QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) @@ -20,7 +33,7 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari qreal x, y; int newOffset = this->initialOffset; - if (this->connection->direction == "up" || this->connection->direction == "down") { + if (MapConnection::isVertical(this->connection->direction)) { x = round(newPos.x() / 16) * 16; newOffset += (x - initialX) / 16; x = newOffset * 16; @@ -29,7 +42,7 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari x = this->initialX; } - if (this->connection->direction == "right" || this->connection->direction == "left") { + if (MapConnection::isHorizontal(this->connection->direction)) { y = round(newPos.y() / 16) * 16; newOffset += (y - this->initialY) / 16; y = newOffset * 16; @@ -56,41 +69,18 @@ bool ConnectionPixmapItem::getEditable() { return (this->flags() & ItemIsMovable) != 0; } -// TODO: Consider whether it still makes sense to highlight the "current" connection (given you can edit them at any point now) -void ConnectionPixmapItem::updateHighlight(bool selected) { - const int normalZ = -1; - const qreal normalOpacity = 1.0; - - // When editing is inactive the current selection is ignored, all connections should appear normal. - if (!this->getEditable()) { - this->setZValue(normalZ); - this->render(normalOpacity); +void ConnectionPixmapItem::setSelected(bool selected) { + if (this->selected == selected) return; - } - - this->setZValue(selected ? normalZ : -2); - this->render(selected ? normalOpacity : 0.75); - - if (selected) { - // Draw highlight - QPixmap pixmap = this->pixmap(); - QPainter painter(&pixmap); - painter.setPen(QColor(255, 0, 255)); - painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); - painter.end(); - this->setPixmap(pixmap); - } - - // Let the list UI know if the selection highlight changes so it can update accordingly - if (this->highlighted != selected) - emit highlightChanged(selected); - this->highlighted = selected; + this->selected = selected; + this->render(); + emit selectionChanged(selected); } void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { if (!this->getEditable()) return; - emit connectionItemSelected(this); + this->setSelected(true); } void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index f40ae57f..cf6e3020 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -1,8 +1,6 @@ #include "connectionslistitem.h" #include "ui_connectionslistitem.h" -static const QStringList directions = {"up", "down", "left", "right"}; - ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) : QFrame(parent), ui(new Ui::ConnectionsListItem) @@ -15,7 +13,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->comboBox_Direction->setEditable(false); ui->comboBox_Direction->setMinimumContentsLength(0); - ui->comboBox_Direction->addItems(directions); + ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); ui->comboBox_Map->setMinimumContentsLength(6); ui->comboBox_Map->addItems(mapNames); diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index ee778df5..fcb662c7 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -1,6 +1,7 @@ #include "noscrollcombobox.h" #include +#include NoScrollComboBox::NoScrollComboBox(QWidget *parent) : QComboBox(parent) @@ -61,3 +62,8 @@ void NoScrollComboBox::setHexItem(uint32_t value) { this->setItem(this->findData(value), "0x" + QString::number(value, 16).toUpper()); } + +void NoScrollComboBox::setClearButtonEnabled(bool enabled) { + if (this->lineEdit()) + this->lineEdit()->setClearButtonEnabled(enabled); +}