Reimplement connection mirroring

This commit is contained in:
GriffinR 2024-07-08 16:01:30 -04:00
parent f1cfc3c78e
commit 1e09d08c9c
12 changed files with 405 additions and 251 deletions

View file

@ -128,7 +128,6 @@ private:
void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); void setNewBorderDimensionsBlockdata(int newWidth, int newHeight);
signals: signals:
void mapChanged(Map *map);
void modified(); void modified();
void mapDimensionsChanged(const QSize &size); void mapDimensionsChanged(const QSize &size);
void mapNeedsRedrawing(); void mapNeedsRedrawing();

View file

@ -5,6 +5,8 @@
#include <QString> #include <QString>
#include <QHash> #include <QHash>
class Map;
class MapConnection { class MapConnection {
public: public:
QString direction; QString direction;
@ -12,12 +14,15 @@ public:
QString map_name; QString map_name;
}; };
inline bool operator==(const MapConnection &c1, const MapConnection &c2) { struct MapConnectionMirror {
return c1.map_name == c2.map_name; MapConnection * connection = nullptr;
} Map * map = nullptr;
};
inline uint qHash(const MapConnection &key) { inline bool operator==(const MapConnection &c1, const MapConnection &c2) {
return qHash(key.map_name); return c1.direction == c2.direction &&
c1.offset == c2.offset &&
c1.map_name == c2.map_name;
} }
#endif // MAPCONNECTION_H #endif // MAPCONNECTION_H

View file

@ -75,9 +75,10 @@ public:
void setEditingConnections(); void setEditingConnections();
void setMapEditingButtonsEnabled(bool enabled); void setMapEditingButtonsEnabled(bool enabled);
void setConnectionsVisibility(bool visible); void setConnectionsVisibility(bool visible);
void updateConnectionOffset(int offset);
void addNewConnection(); 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 removeSelectedConnection();
void addNewWildMonGroup(QWidget *window); void addNewWildMonGroup(QWidget *window);
void deleteWildMonGroup(); void deleteWildMonGroup();
@ -168,17 +169,16 @@ private:
void updateBorderVisibility(); void updateBorderVisibility();
QPoint calculateConnectionPosition(const MapConnection *connection, const QPixmap &pixmap); 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 createConnectionItem(MapConnection* connection);
void populateConnectionsList(); void populateConnectionsList();
void addConnectionToList(ConnectionPixmapItem* connection); void addConnectionToList(ConnectionPixmapItem* connection);
void updateDiveEmergeMap(QString mapName, QString direction); void updateDiveEmergeMap(QString mapName, QString direction);
void onConnectionOffsetChanged(int newOffset); bool shouldMirrorConnection(const MapConnection &source);
void removeMirroredConnection(MapConnection*); MapConnectionMirror getMirroredConnection(const MapConnection&);
void updateMirroredConnectionOffset(MapConnection*); void addMirroredConnection(const MapConnection&);
void updateMirroredConnectionDirection(MapConnection*, QString); void removeMirroredConnection(const MapConnection&);
void updateMirroredConnectionMap(MapConnection*, QString);
void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false);
void updateEncounterFields(EncounterFields newFields); void updateEncounterFields(EncounterFields newFields);
QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMovementPermissionText(uint16_t collision, uint16_t elevation);
QString getMetatileDisplayMessage(uint16_t metatileId); QString getMetatileDisplayMessage(uint16_t metatileId);
@ -194,7 +194,9 @@ private slots:
void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event);
void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item);
void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *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 onConnectionItemSelected(ConnectionPixmapItem* connectionItem);
void onHoveredMovementPermissionChanged(uint16_t, uint16_t); void onHoveredMovementPermissionChanged(uint16_t, uint16_t);
void onHoveredMovementPermissionCleared(); void onHoveredMovementPermissionCleared();
@ -215,7 +217,7 @@ signals:
void warpEventDoubleClicked(QString, int, Event::Group); void warpEventDoubleClicked(QString, int, Event::Group);
void currentMetatilesSelectionChanged(); void currentMetatilesSelectionChanged();
void mapRulerStatusChanged(const QString &); void mapRulerStatusChanged(const QString &);
void editedMapData(); void editedMapData(Map*);
void tilesetUpdated(QString); void tilesetUpdated(QString);
}; };

View file

@ -178,7 +178,6 @@ private slots:
void paste(); void paste();
void onConnectionItemDoubleClicked(QString, QString); void onConnectionItemDoubleClicked(QString, QString);
void onMapChanged(Map *map);
void onMapNeedsRedrawing(); void onMapNeedsRedrawing();
void onTilesetsSaved(QString, QString); void onTilesetsSaved(QString, QString);
void onWildMonDataChanged(); void onWildMonDataChanged();
@ -366,6 +365,7 @@ private:
void clickToolButtonFromEditMode(QString editMode); void clickToolButtonFromEditMode(QString editMode);
void markMapEdited(); void markMapEdited();
void markMapEdited(Map*);
void showWindowTitle(); void showWindowTitle();
void initWindow(); void initWindow();

View file

@ -44,7 +44,7 @@ protected:
signals: signals:
void connectionItemSelected(ConnectionPixmapItem* connectionItem); void connectionItemSelected(ConnectionPixmapItem* connectionItem);
void connectionItemDoubleClicked(ConnectionPixmapItem* connectionItem); void connectionItemDoubleClicked(ConnectionPixmapItem* connectionItem);
void connectionMoved(MapConnection*); void connectionMoved(MapConnection *, int newOffset);
void highlightChanged(bool highlighted); void highlightChanged(bool highlighted);
}; };

View file

@ -19,15 +19,16 @@ class ConnectionsListItem : public QFrame
Q_OBJECT Q_OBJECT
public: public:
explicit ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames); explicit ConnectionsListItem(QWidget *parent, MapConnection *connection, const QStringList &mapNames);
~ConnectionsListItem(); ~ConnectionsListItem();
void updateUI(); void updateUI();
void setSelected(bool selected); void setSelected(bool selected);
MapConnection * connection;
private: private:
Ui::ConnectionsListItem *ui; Ui::ConnectionsListItem *ui;
MapConnection * const connection;
bool isSelected = false; bool isSelected = false;
protected: protected:
@ -35,10 +36,12 @@ protected:
void mouseDoubleClickEvent(QMouseEvent *); void mouseDoubleClickEvent(QMouseEvent *);
signals: signals:
void edited(); void editedOffset(MapConnection *connection, int newOffset);
void deleteRequested(); void editedDirection(MapConnection *connection, const QString &direction);
void editedMapName(MapConnection *connection, const QString &mapName);
void removed();
void selected(); void selected();
void doubleClicked(); void doubleClicked(const QString &mapName);
private slots: private slots:
void on_comboBox_Direction_currentTextChanged(const QString &direction); void on_comboBox_Direction_currentTextChanged(const QString &direction);

View file

@ -304,8 +304,8 @@ void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool
Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight);
} }
emit mapChanged(this);
emit mapDimensionsChanged(QSize(getWidth(), getHeight())); emit mapDimensionsChanged(QSize(getWidth(), getHeight()));
modify();
} }
void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { 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); Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight);
} }
emit mapChanged(this); modify();
} }
void Map::openScript(QString label) { void Map::openScript(QString label) {

View file

@ -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<QString, QString> 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*) {
}*/

View file

@ -726,14 +726,14 @@ void Editor::populateConnectionsList() {
const QSignalBlocker blocker1(ui->comboBox_DiveMap); const QSignalBlocker blocker1(ui->comboBox_DiveMap);
const QSignalBlocker blocker2(ui->comboBox_EmergeMap); 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->clear();
ui->comboBox_DiveMap->addItems(project->mapNames);
ui->comboBox_DiveMap->setCurrentText("");
ui->comboBox_DiveMap->lineEdit()->setClearButtonEnabled(true);
ui->comboBox_EmergeMap->clear(); ui->comboBox_EmergeMap->clear();
ui->comboBox_DiveMap->addItems(project->mapNames);
ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->addItems(project->mapNames);
ui->comboBox_DiveMap->setCurrentText("");
ui->comboBox_EmergeMap->setCurrentText(""); ui->comboBox_EmergeMap->setCurrentText("");
ui->comboBox_DiveMap->lineEdit()->setClearButtonEnabled(true);
ui->comboBox_EmergeMap->lineEdit()->setClearButtonEnabled(true); ui->comboBox_EmergeMap->lineEdit()->setClearButtonEnabled(true);
for (MapConnection* connection : map->connections) { for (MapConnection* connection : map->connections) {
@ -745,24 +745,22 @@ void Editor::populateConnectionsList() {
} }
// Clear any existing connections in list // Clear any existing connections in list
for (auto w : ui->scrollAreaContents_ConnectionsList->findChildren<ConnectionsListItem*>()) for (auto listItem : ui->scrollAreaContents_ConnectionsList->findChildren<ConnectionsListItem*>())
w->deleteLater(); listItem->deleteLater();
for (auto item :connection_items) for (auto item :connection_items)
addConnectionToList(item); 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) { void Editor::addConnectionToList(ConnectionPixmapItem * connectionItem) {
ConnectionsListItem *listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, connectionItem->connection, project->mapNames); 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 ui->layout_ConnectionsList->insertWidget(ui->layout_ConnectionsList->count() - 1, listItem); // Insert above the vertical spacer
// Connect the pixmap item to the list item // Sync the selection highlight between the list UI and the graphical map connection
connect(connectionItem, &ConnectionPixmapItem::connectionMoved, listItem, &ConnectionsListItem::updateUI);
connect(connectionItem, &ConnectionPixmapItem::highlightChanged, listItem, &ConnectionsListItem::setSelected); 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] { connect(listItem, &ConnectionsListItem::selected, [this, connectionItem] {
// When the list item is selected, select the pixmap too // When the list item is selected, select the pixmap too
if (connectionItem == selected_connection_item) { if (connectionItem == selected_connection_item) {
@ -774,32 +772,113 @@ void Editor::addConnectionToList(ConnectionPixmapItem * connectionItem) {
selected_connection_item = connectionItem; selected_connection_item = connectionItem;
selected_connection_item->updateHighlight(true); selected_connection_item->updateHighlight(true);
}); });
connect(listItem, &ConnectionsListItem::edited, [this, connectionItem] { if (connectionItem == selected_connection_item)
// When the list item is edited update the pixmap listItem->setSelected(true);
// TODO: This is probably slower than necessary (we don't need a full redraw if we're just moving it)
// TODO: Handle mirroring // Sync edits to 'offset' between the list UI and the graphical map connection
redrawConnection(connectionItem); connect(connectionItem, &ConnectionPixmapItem::connectionMoved, [this, listItem](MapConnection* connection, int offset) {
emit editedMapData(); 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. // 'Remove' button has been clicked. Delete the pixmap.
// The list item will be deleted via the earlier connection to the pixmap's 'destroyed' signal. // The list item will be deleted via the above connection to the pixmap's 'destroyed' signal.
removeConnection(connectionItem); removeConnectionItem(connectionItem);
}); });
connect(listItem, &ConnectionsListItem::doubleClicked, [this, connectionItem] {
// Double clicking the list item opens the connected map // Double clicking the list item opens the connected map
emit connectionItemDoubleClicked(connectionItem->connection->map_name, map->name); 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<QString, int> directionCounts = QMap<QString, int>({{"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) if (!connectionItem)
return; return;
map->connections.removeOne(connectionItem->connection); if (connectionItem->connection) {
connection_items.removeOne(connectionItem); if (removeMirror) {
//removeMirroredConnection(connectionItem->connection); // TODO // 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()) if (connectionItem->scene())
connectionItem->scene()->removeItem(connectionItem); connectionItem->scene()->removeItem(connectionItem);
@ -808,14 +887,136 @@ void Editor::removeConnection(ConnectionPixmapItem* connectionItem) {
if (!connection_items.isEmpty()) if (!connection_items.isEmpty())
onConnectionItemSelected(connection_items.first()); onConnectionItemSelected(connection_items.first());
} }
delete connectionItem->connection;
delete connectionItem; 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() { void Editor::removeSelectedConnection() {
removeConnection(selected_connection_item); removeConnectionItem(selected_connection_item);
}
static const QMap<QString, QString> 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) { QPoint Editor::calculateConnectionPosition(const MapConnection *connection, const QPixmap &pixmap) {
@ -837,7 +1038,7 @@ QPoint Editor::calculateConnectionPosition(const MapConnection *connection, cons
return QPoint(x, y); return QPoint(x, y);
} }
void Editor::redrawConnection(ConnectionPixmapItem* connectionItem) { void Editor::updateConnectionItem(ConnectionPixmapItem* connectionItem) {
if (!connectionItem || !connectionItem->connection) if (!connectionItem || !connectionItem->connection)
return; return;
@ -870,36 +1071,80 @@ void Editor::redrawConnection(ConnectionPixmapItem* connectionItem) {
connectionItem->setZValue(-1); connectionItem->setZValue(-1);
connectionItem->blockSignals(false); connectionItem->blockSignals(false);
// TODO:
//updateMirroredConnectionMap(selected_connection_item->connection, originalMapName);
maskNonVisibleConnectionTiles(); maskNonVisibleConnectionTiles();
} }
// TODO: Generalize, maybe use in addConnectionToList connection instead of full render void Editor::updateConnectionItemPos(ConnectionPixmapItem* connectionItem) {
void Editor::updateConnectionOffset(int offset) { if (!connectionItem || !connectionItem->connection)
/*if (!selected_connection_item)
return; return;
selected_connection_item->blockSignals(true); MapConnection *connection = connectionItem->connection;
selected_connection_item->connection->offset = offset; connectionItem->blockSignals(true);
if (selected_connection_item->connection->direction == "up" || selected_connection_item->connection->direction == "down") { if (connection->direction == "up" || connection->direction == "down") {
selected_connection_item->setX(selected_connection_item->initialX + (offset - selected_connection_item->initialOffset) * 16); connectionItem->setX(connectionItem->initialX + (connection->offset - connectionItem->initialOffset) * 16);
} else if (selected_connection_item->connection->direction == "left" || selected_connection_item->connection->direction == "right") { } else if (connection->direction == "left" || connection->direction == "right") {
selected_connection_item->setY(selected_connection_item->initialY + (offset - selected_connection_item->initialOffset) * 16); connectionItem->setY(connectionItem->initialY + (connection->offset - connectionItem->initialOffset) * 16);
} }
selected_connection_item->blockSignals(false); connectionItem->blockSignals(false);
updateMirroredConnectionOffset(selected_connection_item->connection); maskNonVisibleConnectionTiles();
maskNonVisibleConnectionTiles();*/
} }
void Editor::onConnectionMoved(MapConnection* connection) { void Editor::setConnectionOffset(MapConnection *connection, int offset) {
// TODO: if (!connection || !map || connection->offset == offset)
//updateMirroredConnectionOffset(connection); 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<ConnectionsListItem*>()) {
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) // TODO: This is likely the source of the visual masking bug while dragging (this happens after the move)
maskNonVisibleConnectionTiles(); 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) { void Editor::onConnectionItemSelected(ConnectionPixmapItem* connectionItem) {
@ -1579,7 +1824,6 @@ void Editor::createConnectionItem(MapConnection* connection) {
item->setZValue(-1); item->setZValue(-1);
scene->addItem(item); scene->addItem(item);
connect(item, &ConnectionPixmapItem::connectionMoved, this, &Editor::onConnectionMoved);
connect(item, &ConnectionPixmapItem::connectionItemSelected, this, &Editor::onConnectionItemSelected); connect(item, &ConnectionPixmapItem::connectionItemSelected, this, &Editor::onConnectionItemSelected);
connect(item, &ConnectionPixmapItem::connectionItemDoubleClicked, [this, item] { connect(item, &ConnectionPixmapItem::connectionItemDoubleClicked, [this, item] {
emit this->connectionItemDoubleClicked(item->connection->map_name, map->name); 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); connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::onToggleGridClicked);
} }
void Editor::addNewConnection() {
// Find direction with least number of connections.
QMap<QString, int> directionCounts = QMap<QString, int>({{"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<QString, QString> oppositeDirections = QMap<QString, QString>({
{"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) void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad)
{ {
if (map->layout->tileset_primary_label != tilesetLabel || forceLoad) if (map->layout->tileset_primary_label != tilesetLabel || forceLoad)
@ -1893,7 +1999,7 @@ void Editor::updateBorderVisibility() {
void Editor::updateCustomMapHeaderValues(QTableWidget *table) void Editor::updateCustomMapHeaderValues(QTableWidget *table)
{ {
map->customHeaders = CustomAttributesTable::getAttributes(table); map->customHeaders = CustomAttributesTable::getAttributes(table);
emit editedMapData(); emit editedMapData(map);
} }
Tileset* Editor::getCurrentMapPrimaryTileset() Tileset* Editor::getCurrentMapPrimaryTileset()

View file

@ -310,7 +310,7 @@ void MainWindow::initEditor() {
connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged);
connect(this->editor, &Editor::wildMonDataChanged, this, &MainWindow::onWildMonDataChanged); connect(this->editor, &Editor::wildMonDataChanged, this, &MainWindow::onWildMonDataChanged);
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); 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(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated);
connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts); connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts);
connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor); connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor);
@ -413,10 +413,19 @@ void MainWindow::showWindowTitle() {
} }
void MainWindow::markMapEdited() { void MainWindow::markMapEdited() {
if (editor && editor->map) { if (editor) markMapEdited(editor->map);
editor->map->hasUnsavedDataChanges = true; }
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(); showWindowTitle();
}
} }
// Update the UI using information we've read from the user's project files. // 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(); showWindowTitle();
connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged);
connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing);
connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); });
@ -2440,10 +2448,6 @@ void MainWindow::onConnectionItemDoubleClicked(QString mapName, QString fromMapN
editor->setSelectedConnectionFromMap(fromMapName); editor->setSelectedConnectionFromMap(fromMapName);
} }
void MainWindow::onMapChanged(Map *) {
updateMapList();
}
void MainWindow::onMapNeedsRedrawing() { void MainWindow::onMapNeedsRedrawing() {
redrawMapScene(); redrawMapScene();
} }
@ -2555,9 +2559,8 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) {
void MainWindow::on_pushButton_AddConnection_clicked() 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(); editor->addNewConnection();
markMapEdited();
} }
void MainWindow::on_pushButton_NewWildMonGroup_clicked() { void MainWindow::on_pushButton_NewWildMonGroup_clicked() {
@ -2584,20 +2587,15 @@ void MainWindow::on_button_OpenEmergeMap_clicked() {
userSetMap(mapName, true); userSetMap(mapName, true);
} }
// TODO: Mirror change to/from other maps
void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) {
// Include empty names as an update (user is deleting the connection) // 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); editor->updateDiveMap(mapName);
markMapEdited();
}
} }
void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName) { 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); editor->updateEmergeMap(mapName);
markMapEdited();
}
} }
void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel)

View file

@ -38,8 +38,8 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari
y = this->initialY; y = this->initialY;
} }
this->connection->offset = newOffset; if (this->connection->offset != newOffset)
emit connectionMoved(this->connection); emit connectionMoved(this->connection, newOffset);
return QPointF(x, y); return QPointF(x, y);
} }
else { else {

View file

@ -5,8 +5,7 @@ static const QStringList directions = {"up", "down", "left", "right"};
ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) : ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) :
QFrame(parent), QFrame(parent),
ui(new Ui::ConnectionsListItem), ui(new Ui::ConnectionsListItem)
connection(connection)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -24,6 +23,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec
ui->spinBox_Offset->setMinimum(INT_MIN); ui->spinBox_Offset->setMinimum(INT_MIN);
ui->spinBox_Offset->setMaximum(INT_MAX); ui->spinBox_Offset->setMaximum(INT_MAX);
this->connection = connection;
this->updateUI(); this->updateUI();
} }
@ -58,33 +58,31 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) {
} }
void ConnectionsListItem::mouseDoubleClickEvent(QMouseEvent *) { void ConnectionsListItem::mouseDoubleClickEvent(QMouseEvent *) {
emit doubleClicked(); emit doubleClicked(this->connection->map_name);
} }
void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(const QString &direction) void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(const QString &direction)
{ {
this->connection->direction = direction;
this->setSelected(true); 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) void ConnectionsListItem::on_comboBox_Map_currentTextChanged(const QString &mapName)
{ {
if (ui->comboBox_Map->findText(mapName) >= 0) { this->setSelected(true);
this->connection->map_name = mapName; if (ui->comboBox_Map->findText(mapName) >= 0 && this->connection->map_name != mapName)
this->setSelected(true); emit this->editedMapName(this->connection, mapName);
emit this->edited();
}
} }
void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset)
{ {
this->connection->offset = offset;
this->setSelected(true); this->setSelected(true);
emit this->edited(); if (this->connection->offset != offset)
emit editedOffset(this->connection, offset);
} }
void ConnectionsListItem::on_button_Delete_clicked() void ConnectionsListItem::on_button_Delete_clicked()
{ {
emit this->deleteRequested(); emit this->removed();
} }