Merge pull request #719 from GriffinRichards/local-id

Support local ID strings, misc event fixes
This commit is contained in:
GriffinR 2025-04-24 16:16:31 -04:00 committed by GitHub
commit f776a31e4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 627 additions and 461 deletions

View File

@ -14,7 +14,7 @@ class Map;
class Layout;
class Blockdata;
class Event;
class DraggablePixmapItem;
class EventPixmapItem;
class Editor;
enum CommandId {

View File

@ -19,7 +19,7 @@ class EventFrame;
class ObjectFrame;
class CloneObjectFrame;
class WarpFrame;
class DraggablePixmapItem;
class EventPixmapItem;
class Event;
class ObjectEvent;
@ -79,9 +79,13 @@ public:
None,
};
// all event groups except warps have IDs that start at 1
// Normally we refer to events using their index in the list of that group's events.
// Object events often get referred to with a special "local ID", which is really just the index + 1.
// We use this local ID number in the index spinner for object events instead of the actual index.
// This distinction is only really important for object and warp events, because these are normally
// the only two groups of events that need to be explicitly referred to.
static int getIndexOffset(Event::Group group) {
return (group == Event::Group::Warp) ? 0 : 1;
return (group == Event::Group::Object) ? 1 : 0;
}
static Event::Group typeToGroup(Event::Type type) {
@ -149,13 +153,13 @@ public:
QJsonObject getCustomAttributes() const { return this->customAttributes; }
void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; }
virtual void loadPixmap(Project *project);
virtual QPixmap loadPixmap(Project *project);
void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; }
QPixmap getPixmap() const { return this->pixmap; }
void setPixmapItem(DraggablePixmapItem *item);
DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; }
void setPixmapItem(EventPixmapItem *item);
EventPixmapItem *getPixmapItem() const { return this->pixmapItem; }
void setUsesDefaultPixmap(bool newUsesDefaultPixmap) { this->usesDefaultPixmap = newUsesDefaultPixmap; }
bool getUsesDefaultPixmap() const { return this->usesDefaultPixmap; }
@ -194,7 +198,7 @@ protected:
QJsonObject customAttributes;
QPixmap pixmap;
DraggablePixmapItem *pixmapItem = nullptr;
EventPixmapItem *pixmapItem = nullptr;
QPointer<EventFrame> eventFrame;
@ -229,7 +233,7 @@ public:
virtual QSet<QString> getExpectedFields() override;
virtual void loadPixmap(Project *project) override;
virtual QPixmap loadPixmap(Project *project) override;
void setGfx(QString newGfx) { this->gfx = newGfx; }
QString getGfx() const { return this->gfx; }
@ -296,17 +300,17 @@ public:
virtual QSet<QString> getExpectedFields() override;
virtual void loadPixmap(Project *project) override;
virtual QPixmap loadPixmap(Project *project) override;
void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; }
QString getTargetMap() const { return this->targetMap; }
void setTargetID(int newTargetID) { this->targetID = newTargetID; }
int getTargetID() const { return this->targetID; }
void setTargetID(QString newTargetID) { this->targetID = newTargetID; }
QString getTargetID() const { return this->targetID; }
private:
QString targetMap;
int targetID = 0;
QString targetID;
};

View File

@ -72,6 +72,8 @@ public:
void resetEvents();
QList<Event *> getEvents(Event::Group group = Event::Group::None) const;
Event* getEvent(Event::Group group, int index) const;
Event* getEvent(Event::Group group, const QString &idName) const;
QStringList getEventIdNames(Event::Group group) const;
int getNumEvents(Event::Group group = Event::Group::None) const;
QStringList getScriptLabels(Event::Group group = Event::Group::None);
QString getScriptsFilePath() const;

View File

@ -30,7 +30,7 @@
#include "mapruler.h"
#include "encountertablemodel.h"
class DraggablePixmapItem;
class EventPixmapItem;
class MetatilesPixmapItem;
class Editor : public QObject
@ -109,7 +109,7 @@ public:
void toggleBorderVisibility(bool visible, bool enableScriptCallback = true);
void updateCustomMapAttributes();
DraggablePixmapItem *addEventPixmapItem(Event *event);
EventPixmapItem *addEventPixmapItem(Event *event);
void removeEventPixmapItem(Event *event);
bool canAddEvents(const QList<Event*> &events);
void selectMapEvent(Event *event, bool toggle = false);
@ -118,13 +118,16 @@ public:
void duplicateSelectedEvents();
void redrawAllEvents();
void redrawEvents(const QList<Event*> &events);
void redrawEventPixmapItem(DraggablePixmapItem *item);
void redrawEventPixmapItem(EventPixmapItem *item);
void updateEventPixmapItemZValue(EventPixmapItem *item);
qreal getEventOpacity(const Event *event) const;
void setPlayerViewRect(const QRectF &rect);
void updateCursorRectPos(int x, int y);
void setCursorRectVisible(bool visible);
void onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition);
void onEventReleased(Event *event, const QPoint &position);
void updateWarpEventWarning(Event *event);
void updateWarpEventWarnings();
@ -175,10 +178,7 @@ public:
static QList<QList<const QImage*>> collisionIcons;
int eventShiftActionId = 0;
void eventsView_onMousePress(QMouseEvent *event);
bool selectingEvent = false;
int eventMoveActionId = 0;
void deleteSelectedEvents();
void shouldReselectEvents();
@ -186,6 +186,22 @@ public:
static void openInTextEditor(const QString &path, int lineNum = 0);
void setCollisionGraphics();
enum ZValue {
MapBorder = -4,
MapConnectionInactive = -3,
MapConnectionActive = -2,
MapConnectionMask = -1,
// Event pixmaps set their z value to be their y position on the map.
// Their y value is int16_t, so we have enough space to allocate the
// full range + 1 for the selected event (which should always be on top).
EventMinimum = 1,
EventMaximum = EventMinimum + 0x10000,
Ruler,
ResizeLayoutPopup
};
public slots:
void openMapScripts() const;
void openScript(const QString &scriptLabel) const;
@ -253,11 +269,11 @@ private slots:
signals:
void eventsChanged();
void openEventMap(Event*);
void openConnectedMap(MapConnection*);
void wildMonTableOpened(EncounterTableModel*);
void wildMonTableClosed();
void wildMonTableEdited();
void warpEventDoubleClicked(QString, int, Event::Group);
void currentMetatilesSelectionChanged();
void mapRulerStatusChanged(const QString &);
void tilesetUpdated(QString);

View File

@ -177,7 +177,7 @@ private slots:
void on_action_Save_Project_triggered();
bool save(bool currentOnly = false);
void openWarpMap(QString map_name, int event_id, Event::Group event_group);
void openEventMap(Event *event);
void duplicate();
void setClipboardData(poryjson::Json::object);
@ -197,8 +197,7 @@ private slots:
void onMapLoaded(Map *map);
void onMapRulerStatusChanged(const QString &);
void applyUserShortcuts();
void markMapEdited();
void markSpecificMapEdited(Map*);
void markMapEdited(Map*);
void markLayoutEdited();
void on_actionNew_Tileset_triggered();

View File

@ -1,70 +0,0 @@
#ifndef DRAGGABLEPIXMAPITEM_H
#define DRAGGABLEPIXMAPITEM_H
#include <QString>
#include <QGraphicsItemGroup>
#include <QGraphicsPixmapItem>
#include <QGraphicsItemAnimation>
#include <QtWidgets>
#include "events.h"
class Editor;
class DraggablePixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
DraggablePixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {}
DraggablePixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) {
this->event = event;
event->setPixmapItem(this);
this->editor = editor;
updatePosition();
}
Event *event = nullptr;
void updatePosition();
void move(int dx, int dy);
void moveTo(const QPoint &pos);
void emitPositionChanged();
void updatePixmap();
private:
Editor *editor = nullptr;
QPoint lastPos;
bool active = false;
bool releaseSelectionQueued = false;
signals:
void positionChanged(Event *event);
void xChanged(int);
void yChanged(int);
void elevationChanged(int);
void spriteChanged(QPixmap pixmap);
void onPropertyChanged(QString key, QString value);
public slots:
void set_x(int x) {
event->setX(x);
updatePosition();
}
void set_y(int y) {
event->setY(y);
updatePosition();
}
void set_elevation(int z) {
event->setElevation(z);
updatePosition();
}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent*);
void mouseMoveEvent(QGraphicsSceneMouseEvent*);
void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*);
};
#endif // DRAGGABLEPIXMAPITEM_H

View File

@ -57,7 +57,9 @@ protected:
bool initialized = false;
bool connected = false;
void populateDropdown(NoScrollComboBox * combo, const QStringList &items);
void populateScriptDropdown(NoScrollComboBox * combo, Project * project);
void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group);
private:
Event *event;
@ -78,6 +80,7 @@ public:
virtual void populate(Project *project) override;
public:
QLineEdit *line_edit_local_id;
NoScrollComboBox *combo_sprite;
NoScrollComboBox *combo_movement;
NoScrollSpinBox *spinner_radius_x;
@ -108,12 +111,15 @@ public:
virtual void populate(Project *project) override;
public:
QLineEdit *line_edit_local_id;
NoScrollComboBox *combo_sprite;
NoScrollSpinBox *spinner_target_id;
NoScrollComboBox *combo_target_id;
NoScrollComboBox *combo_target_map;
private:
CloneObjectEvent *clone;
void tryInvalidateIdDropdown(Map *map);
};
@ -131,12 +137,15 @@ public:
virtual void populate(Project *project) override;
public:
QLineEdit *line_edit_id;
NoScrollComboBox *combo_dest_map;
NoScrollComboBox *combo_dest_warp;
QPushButton *warning;
private:
WarpEvent *warp;
void tryInvalidateIdDropdown(Map *map);
};
@ -275,6 +284,8 @@ public:
private:
HealLocationEvent *healLocation;
void tryInvalidateIdDropdown(Map *map);
};
#endif // EVENTRAMES_H

View File

@ -0,0 +1,58 @@
#ifndef EVENTPIXMAPITEM_H
#define EVENTPIXMAPITEM_H
#include <QString>
#include <QGraphicsItemGroup>
#include <QGraphicsPixmapItem>
#include <QGraphicsItemAnimation>
#include <QtWidgets>
#include "events.h"
class Project;
class EventPixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
explicit EventPixmapItem(Event *event);
void render(Project *project);
bool isSelected() const { return m_selected; }
void setSelected(bool selected) { m_selected = selected; }
Event * getEvent() const { return m_event; }
void move(int dx, int dy);
void moveTo(int x, int y);
void moveTo(const QPoint &pos);
private:
QPixmap m_basePixmap;
Event *const m_event = nullptr;
QPoint m_lastPos;
bool m_active = false;
bool m_selected = false;
bool m_releaseSelectionQueued = false;
void updatePixelPosition();
signals:
void xChanged(int x);
void yChanged(int y);
void posChanged(int x, int y);
void rendered(const QPixmap &pixmap);
void selected(Event *event, bool toggle);
void dragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition);
void released(Event *event, const QPoint &position);
void doubleClicked(Event *event);
protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(m_event); }
};
#endif // EVENTPIXMAPITEM_H

View File

@ -45,25 +45,4 @@ protected:
virtual void keyPressEvent(QKeyEvent *event) override;
};
class Editor;
// TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView.
class GraphicsView : public QGraphicsView
{
public:
GraphicsView() : QGraphicsView() {}
GraphicsView(QWidget *parent) : QGraphicsView(parent) {}
public:
// GraphicsView_Object object;
Editor *editor;
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void moveEvent(QMoveEvent *event) override;
};
//Q_DECLARE_METATYPE(GraphicsView)
#endif // GRAPHICSVIEW_H

View File

@ -5,13 +5,17 @@
#include "graphicsview.h"
#include "overlay.h"
class MapView : public GraphicsView
class Editor;
class MapView : public QGraphicsView
{
Q_OBJECT
public:
MapView() : GraphicsView() {}
MapView(QWidget *parent) : GraphicsView(parent) {}
MapView() : QGraphicsView() {}
MapView(QWidget *parent) : QGraphicsView(parent) {}
Editor *editor;
Overlay * getOverlay(int layer);
void clearOverlayMap();
@ -73,6 +77,7 @@ public:
protected:
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void keyPressEvent(QKeyEvent*) override;
virtual void moveEvent(QMoveEvent *event) override;
private:
QMap<int, Overlay*> overlayMap;

View File

@ -73,7 +73,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/ui/customscriptseditor.cpp \
src/ui/customscriptslistitem.cpp \
src/ui/divingmappixmapitem.cpp \
src/ui/draggablepixmapitem.cpp \
src/ui/eventpixmapitem.cpp \
src/ui/bordermetatilespixmapitem.cpp \
src/ui/collisionpixmapitem.cpp \
src/ui/connectionpixmapitem.cpp \
@ -184,7 +184,7 @@ HEADERS += include/core/advancemapparser.h \
include/ui/customscriptseditor.h \
include/ui/customscriptslistitem.h \
include/ui/divingmappixmapitem.h \
include/ui/draggablepixmapitem.h \
include/ui/eventpixmapitem.h \
include/ui/bordermetatilespixmapitem.h \
include/ui/collisionpixmapitem.h \
include/ui/connectionpixmapitem.h \

View File

@ -1,5 +1,5 @@
#include "editcommands.h"
#include "draggablepixmapitem.h"
#include "eventpixmapitem.h"
#include "bordermetatilespixmapitem.h"
#include "editor.h"

View File

@ -20,8 +20,7 @@ Event* Event::create(Event::Type type) {
}
Event::~Event() {
if (this->eventFrame)
this->eventFrame->deleteLater();
delete this->eventFrame;
}
EventFrame *Event::getEventFrame() {
@ -34,7 +33,7 @@ void Event::destroyEventFrame() {
this->eventFrame = nullptr;
}
void Event::setPixmapItem(DraggablePixmapItem *item) {
void Event::setPixmapItem(EventPixmapItem *item) {
this->pixmapItem = item;
if (this->eventFrame) {
this->eventFrame->invalidateConnections();
@ -105,7 +104,7 @@ QString Event::typeToString(Event::Type type) {
{Event::Type::CloneObject, "Clone Object"},
{Event::Type::Warp, "Warp"},
{Event::Type::Trigger, "Trigger"},
{Event::Type::WeatherTrigger, "Weather"},
{Event::Type::WeatherTrigger, "Weather Trigger"},
{Event::Type::Sign, "Sign"},
{Event::Type::HiddenItem, "Hidden Item"},
{Event::Type::SecretBase, "Secret Base"},
@ -114,9 +113,10 @@ QString Event::typeToString(Event::Type type) {
return typeToStringMap.value(type);
}
void Event::loadPixmap(Project *project) {
QPixmap Event::loadPixmap(Project *project) {
this->pixmap = project->getEventPixmap(this->getEventGroup());
this->usesDefaultPixmap = true;
return this->pixmap;
}
@ -152,12 +152,13 @@ EventFrame *ObjectEvent::createEventFrame() {
OrderedJson::object ObjectEvent::buildEventJson(Project *) {
OrderedJson::object objectJson;
if (projectConfig.eventCloneObjectEnabled) {
objectJson["type"] = Event::typeToJsonKey(Event::Type::Object);
}
QString idName = this->getIdName();
if (!idName.isEmpty())
objectJson["local_id"] = idName;
if (projectConfig.eventCloneObjectEnabled) {
objectJson["type"] = Event::typeToJsonKey(Event::Type::Object);
}
objectJson["graphics_id"] = this->getGfx();
objectJson["x"] = this->getX();
objectJson["y"] = this->getY();
@ -224,13 +225,13 @@ QSet<QString> ObjectEvent::getExpectedFields() {
return expectedFields;
}
void ObjectEvent::loadPixmap(Project *project) {
QPixmap ObjectEvent::loadPixmap(Project *project) {
this->pixmap = project->getEventPixmap(this->gfx, this->movement);
if (!this->pixmap.isNull()) {
this->usesDefaultPixmap = false;
} else {
Event::loadPixmap(project);
return this->pixmap;
}
return Event::loadPixmap(project);
}
@ -261,10 +262,11 @@ EventFrame *CloneObjectEvent::createEventFrame() {
OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) {
OrderedJson::object cloneJson;
cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject);
QString idName = this->getIdName();
if (!idName.isEmpty())
cloneJson["local_id"] = idName;
cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject);
cloneJson["graphics_id"] = this->getGfx();
cloneJson["x"] = this->getX();
cloneJson["y"] = this->getY();
@ -281,7 +283,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) {
this->setY(readInt(&json, "y"));
this->setIdName(readString(&json, "local_id"));
this->setGfx(readString(&json, "graphics_id"));
this->setTargetID(readInt(&json, "target_local_id"));
this->setTargetID(readString(&json, "target_local_id"));
// Log a warning if "target_map" isn't a known map ID, but don't overwrite user data.
const QString mapConstant = readString(&json, "target_map");
@ -295,7 +297,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) {
void CloneObjectEvent::setDefaultValues(Project *project) {
this->setGfx(project->gfxDefines.key(0, "0"));
this->setTargetID(1);
this->setTargetID(QString::number(Event::getIndexOffset(Event::Group::Object)));
if (this->getMap()) this->setTargetMap(this->getMap()->name());
}
@ -312,11 +314,10 @@ QSet<QString> CloneObjectEvent::getExpectedFields() {
return expectedFields;
}
void CloneObjectEvent::loadPixmap(Project *project) {
QPixmap CloneObjectEvent::loadPixmap(Project *project) {
// Try to get the targeted object to clone
int eventIndex = this->targetID - 1;
Map *clonedMap = project->loadMap(this->targetMap);
Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr;
Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr;
if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) {
// Get graphics data from cloned object
@ -328,7 +329,7 @@ void CloneObjectEvent::loadPixmap(Project *project) {
this->gfx = project->gfxDefines.key(0, "0");
this->movement = project->movementTypes.value(0, "0");
}
ObjectEvent::loadPixmap(project);
return ObjectEvent::loadPixmap(project);
}

View File

@ -195,6 +195,49 @@ Event* Map::getEvent(Event::Group group, int index) const {
return m_events[group].value(index, nullptr);
}
Event* Map::getEvent(Event::Group group, const QString &idName) const {
if (idName.isEmpty())
return nullptr;
bool idIsNumber;
int id = idName.toInt(&idIsNumber, 0);
if (idIsNumber)
return getEvent(group, id - Event::getIndexOffset(group));
auto events = getEvents(group);
for (const auto &event : events) {
if (event->getIdName() == idName) {
return event;
}
}
return nullptr;
}
// Returns a list of ID names for the given event group (or all events, if no group is given).
// For events with no explicit ID name, their index string is given instead.
QStringList Map::getEventIdNames(Event::Group group) const {
QList<Event::Group> groups;
if (group == Event::Group::None) {
groups = Event::groups();
} else {
groups.append(group);
}
QStringList idNames;
for (const auto &group : groups) {
const auto events = m_events[group];
int indexOffset = Event::getIndexOffset(group);
for (int i = 0; i < events.length(); i++) {
QString idName = events.at(i)->getIdName();
if (idName.isEmpty()) {
idName = QString::number(i + indexOffset);
}
idNames.append(idName);
}
}
return idNames;
}
int Map::getNumEvents(Event::Group group) const {
if (group == Event::Group::None) {
// Total number of events

View File

@ -1,5 +1,5 @@
#include "editor.h"
#include "draggablepixmapitem.h"
#include "eventpixmapitem.h"
#include "imageproviders.h"
#include "log.h"
#include "connectionslistitem.h"
@ -1345,7 +1345,6 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) {
}
void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) {
// TODO: add event tab event painting tool buttons stuff here
if (!item->getEditsEnabled()) {
return;
}
@ -1419,8 +1418,11 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i
if (event && event->getPixmapItem())
event->getPixmapItem()->moveTo(pos);
}
} else if (eventEditAction == EditAction::Select) {
// do nothing here, at least for now
} else if (eventEditAction == EditAction::Select && event->type() == QEvent::GraphicsSceneMousePress) {
if (!(event->modifiers() & Qt::ControlModifier) && this->selectedEvents.length() > 1) {
// User is clearing group selection by clicking on the background
selectMapEvent(this->selectedEvents.first());
}
} else if (eventEditAction == EditAction::Shift) {
static QPoint selection_origin;
@ -1538,7 +1540,7 @@ bool Editor::displayLayout() {
scene->installEventFilter(filter);
connect(filter, &MapSceneEventFilter::wheelZoom, this, &Editor::onWheelZoom);
scene->installEventFilter(this->map_ruler);
this->map_ruler->setZValue(1000);
this->map_ruler->setZValue(ZValue::Ruler);
scene->addItem(this->map_ruler);
}
@ -1723,9 +1725,6 @@ void Editor::clearMapEvents() {
if (events_group->scene()) {
events_group->scene()->removeItem(events_group);
}
// events_group does not own its children, the childrens' parent
// is set to the group's parent (and our group has no parent).
qDeleteAll(events_group->childItems());
delete events_group;
events_group = nullptr;
}
@ -1745,9 +1744,15 @@ void Editor::displayMapEvents() {
events_group->setHandlesChildEvents(false);
}
DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) {
EventPixmapItem *Editor::addEventPixmapItem(Event *event) {
this->project->loadEventPixmap(event);
auto item = new DraggablePixmapItem(event, this);
auto item = new EventPixmapItem(event);
connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap);
connect(item, &EventPixmapItem::dragged, this, &Editor::onEventDragged);
connect(item, &EventPixmapItem::released, this, &Editor::onEventReleased);
connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent);
connect(item, &EventPixmapItem::posChanged, [this, event] { updateWarpEventWarning(event); });
connect(item, &EventPixmapItem::yChanged, [this, item] { updateEventPixmapItemZValue(item); });
redrawEventPixmapItem(item);
this->events_group->addToGroup(item);
return item;
@ -1821,6 +1826,7 @@ void Editor::maskNonVisibleConnectionTiles() {
QBrush brush(ui->graphicsView_Map->palette().color(QPalette::Active, QPalette::Base));
connection_mask = scene->addPath(mask, pen, brush);
connection_mask->setZValue(ZValue::MapConnectionMask);
}
void Editor::clearMapBorder() {
@ -1843,7 +1849,7 @@ void Editor::displayMapBorder() {
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
item->setX(x * 16);
item->setY(y * 16);
item->setZValue(-3);
item->setZValue(ZValue::MapBorder);
scene->addItem(item);
borderItems.append(item);
}
@ -2013,32 +2019,59 @@ qreal Editor::getEventOpacity(const Event *event) const {
return event->getUsesDefaultPixmap() ? 0.7 : 1.0;
}
void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) {
if (item && item->event && !item->event->getPixmap().isNull()) {
item->setOpacity(getEventOpacity(item->event));
project->loadEventPixmap(item->event, true);
item->setPixmap(item->event->getPixmap());
item->setShapeMode(porymapConfig.eventSelectionShapeMode);
void Editor::redrawEventPixmapItem(EventPixmapItem *item) {
if (!item) return;
Event *event = item->getEvent();
if (!event) return;
if (this->editMode == EditMode::Events) {
if (this->selectedEvents.contains(item->event)) {
// Draw the selection rectangle
QImage image = item->pixmap().toImage();
QPainter painter(&image);
painter.setPen(QColor(255, 0, 255));
painter.drawRect(0, 0, image.width() - 1, image.height() - 1);
painter.end();
item->setPixmap(QPixmap::fromImage(image));
}
item->setAcceptedMouseButtons(Qt::AllButtons);
} else {
// Can't interact with event pixmaps outside of event editing mode.
// We could do setEnabled(false), but rather than ignoring the mouse events this
// would reject them, which would prevent painting on the map behind the events.
item->setAcceptedMouseButtons(Qt::NoButton);
}
item->updatePosition();
if (this->editMode == EditMode::Events) {
item->setAcceptedMouseButtons(Qt::AllButtons);
item->setSelected(this->selectedEvents.contains(event));
} else {
// Can't interact with event pixmaps outside of event editing mode.
// We could do setEnabled(false), but rather than ignoring the mouse events this
// would reject them, which would prevent painting on the map behind the events.
item->setAcceptedMouseButtons(Qt::NoButton);
item->setSelected(false);
}
updateEventPixmapItemZValue(item);
item->setOpacity(getEventOpacity(event));
item->setShapeMode(porymapConfig.eventSelectionShapeMode);
item->render(project);
}
void Editor::updateEventPixmapItemZValue(EventPixmapItem *item) {
if (!item) return;
Event *event = item->getEvent();
if (!event) return;
if (item->isSelected()) {
item->setZValue(ZValue::EventMaximum);
} else {
item->setZValue(event->getY() + ((ZValue::EventMaximum - ZValue::EventMinimum) / 2));
}
}
void Editor::onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition) {
if (!this->map || !this->map_item)
return;
this->map_item->hoveredMapMetatileChanged(newPosition);
// Drag all the other selected events (if any) with it
QList<Event*> draggedEvents;
if (this->selectedEvents.contains(event)) {
draggedEvents = this->selectedEvents;
} else {
draggedEvents.append(event);
}
QPoint moveDistance = newPosition - oldPosition;
this->map->commit(new EventMove(draggedEvents, moveDistance.x(), moveDistance.y(), this->eventMoveActionId));
}
void Editor::onEventReleased(Event *, const QPoint &) {
this->eventMoveActionId++;
}
// Warp events display a warning if they're not positioned on a metatile with a warp behavior.
@ -2323,32 +2356,6 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working
return process.startDetached(pid);
}
// It doesn't seem to be possible to prevent the mousePress event
// from triggering both event's DraggablePixmapItem and the background mousePress.
// Since the DraggablePixmapItem's event fires first, we can set a temp
// variable "selectingEvent" so that we can detect whether or not the user
// is clicking on the background instead of an event.
void Editor::eventsView_onMousePress(QMouseEvent *event) {
// make sure we are in event editing mode
if (map_item && this->editMode != EditMode::Events) {
return;
}
if (this->eventEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) {
this->eventEditAction = EditAction::Select;
this->settings->mapCursor = QCursor();
this->cursorMapTileRect->setSingleTileMode();
this->ui->toolButton_Paint->setChecked(false);
this->ui->toolButton_Select->setChecked(true);
}
bool multiSelect = event->modifiers() & Qt::ControlModifier;
if (!selectingEvent && !multiSelect && this->selectedEvents.length() > 1) {
// User is clearing group selection by clicking on the background
this->selectMapEvent(this->selectedEvents.first());
}
selectingEvent = false;
}
void Editor::setCollisionTabSpinBoxes(uint16_t collision, uint16_t elevation) {
const QSignalBlocker blocker1(ui->spinBox_SelectedCollision);
const QSignalBlocker blocker2(ui->spinBox_SelectedElevation);

View File

@ -10,7 +10,7 @@
#include "customattributesframe.h"
#include "scripting.h"
#include "adjustingstackedwidget.h"
#include "draggablepixmapitem.h"
#include "eventpixmapitem.h"
#include "editcommands.h"
#include "flowlayout.h"
#include "shortcut.h"
@ -345,9 +345,9 @@ void MainWindow::initEditor() {
this->editor = new Editor(ui);
connect(this->editor, &Editor::eventsChanged, this, &MainWindow::updateEvents);
connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap);
connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap);
connect(this->editor, &Editor::openEventMap, this, &MainWindow::openEventMap);
connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged);
connect(this->editor, &Editor::wildMonTableEdited, this, &MainWindow::markMapEdited);
connect(this->editor, &Editor::wildMonTableEdited, [this] { markMapEdited(this->editor->map); });
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated);
connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent);
@ -528,11 +528,7 @@ void MainWindow::updateWindowTitle() {
}
}
void MainWindow::markMapEdited() {
if (editor) markSpecificMapEdited(editor->map);
}
void MainWindow::markSpecificMapEdited(Map* map) {
void MainWindow::markMapEdited(Map* map) {
if (!map)
return;
map->setHasUnsavedDataChanges(true);
@ -956,8 +952,6 @@ bool MainWindow::setMap(QString map_name) {
updateMapList();
resetMapListFilters();
connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection);
// If the map's MAPSEC / layout changes, update the map's position in the map list.
// These are doing more work than necessary, rather than rebuilding the entire list they should find and relocate the appropriate row.
connect(editor->map, &Map::layoutChanged, this, &MainWindow::rebuildMapList_Layouts, Qt::UniqueConnection);
@ -1070,20 +1064,57 @@ void MainWindow::refreshCollisionSelector() {
on_horizontalSlider_CollisionZoom_valueChanged(ui->horizontalSlider_CollisionZoom->value());
}
void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) {
// Open the destination map.
if (!userSetMap(map_name))
// Some events (like warps) have data that refers to an event on a different map.
// This function opens that map, and selects the event it's referring to.
void MainWindow::openEventMap(Event *sourceEvent) {
if (!sourceEvent || !this->editor->map) return;
QString targetMapName;
QString targetEventIdName;
Event::Group targetEventGroup;
Event::Type eventType = sourceEvent->getEventType();
if (eventType == Event::Type::Warp) {
// Warp events open to their destination warp event.
WarpEvent *warp = dynamic_cast<WarpEvent *>(sourceEvent);
targetMapName = warp->getDestinationMap();
targetEventIdName = warp->getDestinationWarpID();
targetEventGroup = Event::Group::Warp;
} else if (eventType == Event::Type::CloneObject) {
// Clone object events open to their target object event.
CloneObjectEvent *clone = dynamic_cast<CloneObjectEvent *>(sourceEvent);
targetMapName = clone->getTargetMap();
targetEventIdName = clone->getTargetID();
targetEventGroup = Event::Group::Object;
} else if (eventType == Event::Type::SecretBase) {
// Secret Bases open to their secret base entrance
const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix);
SecretBaseEvent *base = dynamic_cast<SecretBaseEvent *>(sourceEvent);
// Extract the map name from the secret base ID.
QString baseId = base->getBaseID();
targetMapName = this->editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_")));
// Just select the first warp. Normally the only warp event on every secret base map is the entrance/exit, so this is usually correct.
// The warp IDs for secret bases are specified in the project's C code, not in the map data, so we don't have an easy way to read the actual IDs.
targetEventIdName = "0";
targetEventGroup = Event::Group::Warp;
} else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) {
// Heal location events open to their respawn NPC
HealLocationEvent *heal = dynamic_cast<HealLocationEvent *>(sourceEvent);
targetMapName = heal->getRespawnMapName();
targetEventIdName = heal->getRespawnNPC();
targetEventGroup = Event::Group::Object;
} else {
// Other event types have no target map to open.
return;
}
if (!userSetMap(targetMapName))
return;
// Select the target event.
int index = event_id - Event::getIndexOffset(event_group);
Event* event = this->editor->map->getEvent(event_group, index);
if (event) {
this->editor->selectMapEvent(event);
} else {
// Can still warp to this map, but can't select the specified event
logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name));
}
// Map opened successfully, now try to select the targeted event on that map.
Event *targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName);
this->editor->selectMapEvent(targetEvent);
}
void MainWindow::displayMapProperties() {
@ -1123,7 +1154,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te
}
this->editor->map->setLayout(layout);
setMap(this->editor->map->name());
markMapEdited();
markMapEdited(this->editor->map);
}
void MainWindow::onLayoutSelectorEditingFinished() {
@ -2488,7 +2519,7 @@ void MainWindow::onOpenConnectedMap(MapConnection *connection) {
}
void MainWindow::onMapLoaded(Map *map) {
connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(map); });
connect(map, &Map::modified, [this, map] { markMapEdited(map); });
}
void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) {

View File

@ -159,6 +159,11 @@ void Project::clearTilesetCache() {
}
Map* Project::loadMap(const QString &mapName) {
if (mapName == getDynamicMapName()) {
// Silently ignored, caller is expected to handle this if they want this to be an error.
return nullptr;
}
Map* map = this->maps.value(mapName);
if (!map) {
logError(QString("Unknown map name '%1'.").arg(mapName));

View File

@ -1,6 +1,7 @@
#include "connectionpixmapitem.h"
#include "editcommands.h"
#include "map.h"
#include "editor.h"
#include <math.h>
@ -30,7 +31,7 @@ void ConnectionPixmapItem::render(bool ignoreCache) {
this->basePixmap = this->connection->render();
QPixmap pixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height());
this->setZValue(-1);
this->setZValue(Editor::ZValue::MapConnectionActive);
// When editing is inactive the current selection is ignored, all connections should appear normal.
if (this->getEditable()) {
@ -42,7 +43,7 @@ void ConnectionPixmapItem::render(bool ignoreCache) {
painter.end();
} else {
// Darken the image
this->setZValue(-2);
this->setZValue(Editor::ZValue::MapConnectionInactive);
QPainter painter(&pixmap);
int alpha = static_cast<int>(255 * 0.25);
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QColor(0, 0, 0, alpha));

View File

@ -1,134 +0,0 @@
#include "draggablepixmapitem.h"
#include "editor.h"
#include "editcommands.h"
#include "mapruler.h"
#include "metatile.h"
static unsigned currentActionId = 0;
void DraggablePixmapItem::updatePosition() {
int x = this->event->getPixelX();
int y = this->event->getPixelY();
setX(x);
setY(y);
if (this->editor->selectedEvents.contains(this->event)) {
setZValue(event->getY() + 1);
} else {
setZValue(event->getY());
}
editor->updateWarpEventWarning(event);
}
void DraggablePixmapItem::emitPositionChanged() {
emit xChanged(event->getX());
emit yChanged(event->getY());
emit elevationChanged(event->getElevation());
}
void DraggablePixmapItem::updatePixmap() {
editor->project->loadEventPixmap(event, true);
this->updatePosition();
editor->redrawEventPixmapItem(this);
emit spriteChanged(event->getPixmap());
}
void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) {
if (this->active)
return;
this->active = true;
this->lastPos = Metatile::coordFromPixmapCoord(mouse->scenePos());
bool selectionToggle = mouse->modifiers() & Qt::ControlModifier;
if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) {
// User is either toggling this selection on/off as part of a group selection,
// or they're newly selecting just this item.
this->editor->selectMapEvent(this->event, selectionToggle);
} else {
// This item is already selected and the user isn't toggling the selection, so there are 4 possibilities:
// 1. This is the only selected event, and the selection is pointless.
// 2. This is the only selected event, and they want to drag the item around.
// 3. There's a group selection, and they want to start a new selection with just this item.
// 4. There's a group selection, and they want to drag the group around.
// 'selectMapEvent' will immediately clear the rest of the selection, which supports #1-3 but prevents #4.
// To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred.
this->releaseSelectionQueued = true;
}
this->editor->selectingEvent = true;
}
void DraggablePixmapItem::move(int dx, int dy) {
event->setX(event->getX() + dx);
event->setY(event->getY() + dy);
updatePosition();
emitPositionChanged();
}
void DraggablePixmapItem::moveTo(const QPoint &pos) {
event->setX(pos.x());
event->setY(pos.y());
updatePosition();
emitPositionChanged();
}
void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) {
if (!this->active)
return;
QPoint pos = Metatile::coordFromPixmapCoord(mouse->scenePos());
if (pos == this->lastPos)
return;
QPoint moveDistance = pos - this->lastPos;
this->lastPos = pos;
emit this->editor->map_item->hoveredMapMetatileChanged(pos);
QList <Event *> selectedEvents;
if (this->editor->selectedEvents.contains(this->event)) {
selectedEvents = this->editor->selectedEvents;
} else {
selectedEvents.append(this->event);
}
editor->map->commit(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId));
this->releaseSelectionQueued = false;
}
void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) {
if (!this->active)
return;
this->active = false;
currentActionId++;
if (this->releaseSelectionQueued) {
this->releaseSelectionQueued = false;
if (Metatile::coordFromPixmapCoord(mouse->scenePos()) == this->lastPos)
this->editor->selectMapEvent(this->event);
}
}
// Events with properties that specify a map will open that map when double-clicked.
void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) {
Event::Type eventType = this->event->getEventType();
if (eventType == Event::Type::Warp) {
WarpEvent *warp = dynamic_cast<WarpEvent *>(this->event);
QString destMap = warp->getDestinationMap();
int warpId = ParseUtil::gameStringToInt(warp->getDestinationWarpID());
emit editor->warpEventDoubleClicked(destMap, warpId, Event::Group::Warp);
}
else if (eventType == Event::Type::CloneObject) {
CloneObjectEvent *clone = dynamic_cast<CloneObjectEvent *>(this->event);
emit editor->warpEventDoubleClicked(clone->getTargetMap(), clone->getTargetID(), Event::Group::Object);
}
else if (eventType == Event::Type::SecretBase) {
const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix);
SecretBaseEvent *base = dynamic_cast<SecretBaseEvent *>(this->event);
QString baseId = base->getBaseID();
QString destMap = editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_")));
emit editor->warpEventDoubleClicked(destMap, 0, Event::Group::Warp);
}
else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) {
HealLocationEvent *heal = dynamic_cast<HealLocationEvent *>(this->event);
const QString localIdName = heal->getRespawnNPC();
int localId = 0; // TODO: Get value from localIdName
emit editor->warpEventDoubleClicked(heal->getRespawnMapName(), localId, Event::Group::Object);
}
}

View File

@ -1,7 +1,7 @@
#include "eventframes.h"
#include "customattributesframe.h"
#include "editcommands.h"
#include "draggablepixmapitem.h"
#include "eventpixmapitem.h"
#include <limits>
using std::numeric_limits;
@ -74,6 +74,7 @@ void EventFrame::setup() {
this->label_id = new QLabel("event_type");
l_vbox_1->addWidget(this->label_id);
l_vbox_1->addLayout(l_layout_xyz);
this->label_id->setText(Event::typeToString(this->event->getEventType()));
// icon / pixmap label
this->label_icon = new QLabel(this);
@ -113,7 +114,7 @@ void EventFrame::connectSignals(MainWindow *) {
}
});
connect(this->event->getPixmapItem(), &DraggablePixmapItem::xChanged, this->spinner_x, &NoScrollSpinBox::setValue);
connect(this->event->getPixmapItem(), &EventPixmapItem::xChanged, this->spinner_x, &NoScrollSpinBox::setValue);
this->spinner_y->disconnect();
connect(this->spinner_y, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
@ -122,7 +123,7 @@ void EventFrame::connectSignals(MainWindow *) {
this->event->getMap()->commit(new EventMove(QList<Event *>() << this->event, 0, delta, this->spinner_y->getActionId()));
}
});
connect(this->event->getPixmapItem(), &DraggablePixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue);
connect(this->event->getPixmapItem(), &EventPixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue);
this->spinner_z->disconnect();
connect(this->spinner_z, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
@ -172,19 +173,31 @@ void EventFrame::setActive(bool active) {
this->blockSignals(!active);
}
void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &items) {
// Set the items in the combo box. This may be called after the frame is initialized
// if the frame needs to be repopulated, so ensure the text in the combo is preserved
// and that we don't accidentally fire 'currentTextChanged'.
const QSignalBlocker b(combo);
const QString savedText = combo->currentText();
combo->clear();
combo->addItems(items);
combo->setCurrentText(savedText);
}
void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) {
// The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file.
if (!this->event->getMap())
return;
QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup());
combo->addItems(scripts);
populateDropdown(combo, scripts);
// Depending on the settings, the autocomplete may also contain all global scripts.
if (porymapConfig.loadAllEventScripts) {
project->insertGlobalScriptLabels(scripts);
}
// Note: Because 'combo' is the parent, the old QCompleter will be deleted when a new one is set.
auto completer = new QCompleter(scripts, combo);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
@ -197,14 +210,32 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj
combo->setCompleter(completer);
// If the project changes the script labels, update the EventFrame.
// TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes).
// This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes.
connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) {
if (!project->mapNames.contains(mapName))
return;
Map *map = project->loadMap(mapName);
if (map) populateDropdown(combo, map->getEventIdNames(group));
}
void ObjectFrame::setup() {
EventFrame::setup();
this->label_id->setText("Object");
// local id
QFormLayout *l_form_local_id = new QFormLayout();
this->line_edit_local_id = new QLineEdit(this);
static const QString line_edit_local_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this object in scripts. "
"If no name is given you can refer to this object using its 'object id' number.");
this->line_edit_local_id->setToolTip(line_edit_local_id_toolTip);
this->line_edit_local_id->setPlaceholderText("LOCALID_MY_NPC");
l_form_local_id->addRow("Local ID", this->line_edit_local_id);
this->layout_contents->addLayout(l_form_local_id);
// sprite combo
QFormLayout *l_form_sprite = new QFormLayout();
@ -294,21 +325,29 @@ void ObjectFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
// local id
this->line_edit_local_id->disconnect();
connect(this->line_edit_local_id, &QLineEdit::textChanged, [this](const QString &text) {
this->object->setIdName(text);
this->object->modify();
});
// sprite update
this->combo_sprite->disconnect();
connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) {
connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) {
this->object->setGfx(text);
this->object->getPixmapItem()->updatePixmap();
this->object->getPixmapItem()->render(project);
this->object->modify();
});
connect(this->object->getPixmapItem(), &DraggablePixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap);
connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap);
// movement
this->combo_movement->disconnect();
connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) {
connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) {
this->object->setMovement(text);
this->object->getPixmapItem()->updatePixmap();
this->object->getPixmapItem()->render(project);
this->object->modify();
});
@ -366,6 +405,9 @@ void ObjectFrame::initialize() {
const QSignalBlocker blocker(this);
EventFrame::initialize();
// local id
this->line_edit_local_id->setText(this->object->getIdName());
// sprite
this->combo_sprite->setTextItem(this->object->getGfx());
@ -397,12 +439,11 @@ void ObjectFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
this->combo_sprite->addItems(project->gfxDefines.keys());
this->combo_movement->addItems(project->movementTypes);
this->combo_flag->addItems(project->flagNames);
this->combo_trainer_type->addItems(project->trainerTypes);
this->populateScriptDropdown(this->combo_script, project);
populateDropdown(this->combo_sprite, project->gfxDefines.keys());
populateDropdown(this->combo_movement, project->movementTypes);
populateDropdown(this->combo_flag, project->flagNames);
populateDropdown(this->combo_trainer_type, project->trainerTypes);
populateScriptDropdown(this->combo_script, project);
}
@ -410,13 +451,25 @@ void ObjectFrame::populate(Project *project) {
void CloneObjectFrame::setup() {
EventFrame::setup();
this->label_id->setText("Clone Object");
this->spinner_z->setEnabled(false);
// local id
QFormLayout *l_form_local_id = new QFormLayout();
this->line_edit_local_id = new QLineEdit(this);
static const QString line_edit_local_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this object in scripts. "
"If no name is given you can refer to this object using its 'object id' number.");
this->line_edit_local_id->setToolTip(line_edit_local_id_toolTip);
this->line_edit_local_id->setPlaceholderText("LOCALID_MY_CLONE_NPC");
l_form_local_id->addRow("Local ID", this->line_edit_local_id);
this->layout_contents->addLayout(l_form_local_id);
// sprite combo (edits disabled)
QFormLayout *l_form_sprite = new QFormLayout();
this->combo_sprite = new NoScrollComboBox(this);
static const QString combo_sprite_toolTip = Util::toHtmlParagraph("The sprite graphics to use for this object. This is updated automatically "
"to match the target object, and so can't be edited. By default the games "
"will get the graphics directly from the target object, so this field is ignored.");
this->combo_sprite->setToolTip(combo_sprite_toolTip);
l_form_sprite->addRow("Sprite", this->combo_sprite);
this->combo_sprite->setEnabled(false);
this->layout_contents->addLayout(l_form_sprite);
@ -429,12 +482,12 @@ void CloneObjectFrame::setup() {
l_form_dest_map->addRow("Target Map", this->combo_target_map);
this->layout_contents->addLayout(l_form_dest_map);
// clone local id spinbox
// clone local id combo
QFormLayout *l_form_dest_id = new QFormLayout();
this->spinner_target_id = new NoScrollSpinBox(this);
static const QString spinner_target_id_toolTip = Util::toHtmlParagraph("event_object ID of the object being cloned.");
this->spinner_target_id->setToolTip(spinner_target_id_toolTip);
l_form_dest_id->addRow("Target Local ID", this->spinner_target_id);
this->combo_target_id = new NoScrollComboBox(this);
static const QString combo_target_id_toolTip = Util::toHtmlParagraph("The Local ID name or number of the object being cloned.");
this->combo_target_id->setToolTip(combo_target_id_toolTip);
l_form_dest_id->addRow("Target Local ID", this->combo_target_id);
this->layout_contents->addLayout(l_form_dest_id);
// custom attributes
@ -445,27 +498,48 @@ void CloneObjectFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
// local id
this->line_edit_local_id->disconnect();
connect(this->line_edit_local_id, &QLineEdit::textChanged, [this](const QString &text) {
this->clone->setIdName(text);
this->clone->modify();
});
// update icon displayed in frame with target
connect(this->clone->getPixmapItem(), &DraggablePixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap);
connect(this->clone->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap);
// target map
this->combo_target_map->disconnect();
connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &text) {
this->clone->setTargetMap(text);
this->clone->getPixmapItem()->updatePixmap();
connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
this->clone->setTargetMap(mapName);
this->clone->getPixmapItem()->render(project);
this->combo_sprite->setCurrentText(this->clone->getGfx());
this->clone->modify();
populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object);
});
connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
// target id
this->combo_target_id->disconnect();
connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) {
this->clone->setTargetID(text);
this->clone->getPixmapItem()->render(project);
this->combo_sprite->setCurrentText(this->clone->getGfx());
this->clone->modify();
});
// target id
this->spinner_target_id->disconnect();
connect(this->spinner_target_id, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
this->clone->setTargetID(value);
this->clone->getPixmapItem()->updatePixmap();
this->combo_sprite->setCurrentText(this->clone->getGfx());
this->clone->modify();
});
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) {
// If the clone's target map is opened then the names in this frame's ID dropdown may be changed.
// Make sure we update the frame next time it's opened.
if (map && this->clone && map->name() == this->clone->getTargetMap()) {
invalidateValues();
}
}
void CloneObjectFrame::initialize() {
@ -474,13 +548,14 @@ void CloneObjectFrame::initialize() {
const QSignalBlocker blocker(this);
EventFrame::initialize();
// local id
this->line_edit_local_id->setText(this->clone->getIdName());
// sprite
this->combo_sprite->setCurrentText(this->clone->getGfx());
// target id
this->spinner_target_id->setMinimum(1);
this->spinner_target_id->setMaximum(126);
this->spinner_target_id->setValue(this->clone->getTargetID());
this->combo_target_id->setCurrentText(this->clone->getTargetID());
// target map
this->combo_target_map->setTextItem(this->clone->getTargetMap());
@ -492,13 +567,22 @@ void CloneObjectFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
this->combo_target_map->addItems(project->mapNames);
populateDropdown(this->combo_target_map, project->mapNames);
populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object);
}
void WarpFrame::setup() {
EventFrame::setup();
this->label_id->setText("Warp");
// ID
QFormLayout *l_form_id = new QFormLayout();
this->line_edit_id = new QLineEdit(this);
static const QString line_edit_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this warp from other warps. "
"If no name is given you can refer to this warp using its 'warp id' number.");
this->line_edit_id->setToolTip(line_edit_id_toolTip);
this->line_edit_id->setPlaceholderText("WARP_ID_MY_WARP");
l_form_id->addRow("ID", this->line_edit_id);
this->layout_contents->addLayout(l_form_id);
// desination map combo
QFormLayout *l_form_dest_map = new QFormLayout();
@ -534,13 +618,23 @@ void WarpFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
// id
this->line_edit_id->disconnect();
connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) {
this->warp->setIdName(text);
this->warp->modify();
});
// dest map
this->combo_dest_map->disconnect();
connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &text) {
this->warp->setDestinationMap(text);
connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
this->warp->setDestinationMap(mapName);
this->warp->modify();
populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp);
});
connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
// dest id
this->combo_dest_warp->disconnect();
@ -552,6 +646,17 @@ void WarpFrame::connectSignals(MainWindow *window) {
// warning
this->warning->disconnect();
connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked);
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void WarpFrame::tryInvalidateIdDropdown(Map *map) {
// If the warps's target map is opened then the names in this frame's ID dropdown may be changed.
// Make sure we update the frame next time it's opened.
if (map && this->warp && map->name() == this->warp->getDestinationMap()) {
invalidateValues();
}
}
void WarpFrame::initialize() {
@ -560,6 +665,9 @@ void WarpFrame::initialize() {
const QSignalBlocker blocker(this);
EventFrame::initialize();
// id
this->line_edit_id->setText(this->warp->getIdName());
// dest map
this->combo_dest_map->setTextItem(this->warp->getDestinationMap());
@ -573,7 +681,8 @@ void WarpFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
this->combo_dest_map->addItems(project->mapNames);
populateDropdown(this->combo_dest_map, project->mapNames);
populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp);
}
@ -581,8 +690,6 @@ void WarpFrame::populate(Project *project) {
void TriggerFrame::setup() {
EventFrame::setup();
this->label_id->setText("Trigger");
// script combo
QFormLayout *l_form_script = new QFormLayout();
this->combo_script = new NoScrollComboBox(this);
@ -661,10 +768,8 @@ void TriggerFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
// var combo
this->combo_var->addItems(project->varNames);
this->populateScriptDropdown(this->combo_script, project);
populateDropdown(this->combo_var, project->varNames);
populateScriptDropdown(this->combo_script, project);
}
@ -672,8 +777,6 @@ void TriggerFrame::populate(Project *project) {
void WeatherTriggerFrame::setup() {
EventFrame::setup();
this->label_id->setText("Weather Trigger");
// weather combo
QFormLayout *l_form_weather = new QFormLayout();
this->combo_weather = new NoScrollComboBox(this);
@ -715,8 +818,7 @@ void WeatherTriggerFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
// weather
this->combo_weather->addItems(project->coordEventWeatherNames);
populateDropdown(this->combo_weather, project->coordEventWeatherNames);
}
@ -724,8 +826,6 @@ void WeatherTriggerFrame::populate(Project *project) {
void SignFrame::setup() {
EventFrame::setup();
this->label_id->setText("Sign");
// facing dir combo
QFormLayout *l_form_facing_dir = new QFormLayout();
this->combo_facing_dir = new NoScrollComboBox(this);
@ -785,10 +885,8 @@ void SignFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
// facing dir
this->combo_facing_dir->addItems(project->bgEventFacingDirections);
this->populateScriptDropdown(this->combo_script, project);
populateDropdown(this->combo_facing_dir, project->bgEventFacingDirections);
populateScriptDropdown(this->combo_script, project);
}
@ -796,8 +894,6 @@ void SignFrame::populate(Project *project) {
void HiddenItemFrame::setup() {
EventFrame::setup();
this->label_id->setText("Hidden Item");
// item combo
QFormLayout *l_form_item = new QFormLayout();
this->combo_item = new NoScrollComboBox(this);
@ -905,8 +1001,8 @@ void HiddenItemFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
this->combo_item->addItems(project->itemNames);
this->combo_flag->addItems(project->flagNames);
populateDropdown(this->combo_item, project->itemNames);
populateDropdown(this->combo_flag, project->flagNames);
}
@ -914,8 +1010,6 @@ void HiddenItemFrame::populate(Project *project) {
void SecretBaseFrame::setup() {
EventFrame::setup();
this->label_id->setText("Secret Base");
this->spinner_z->setEnabled(false);
// item combo
@ -959,7 +1053,7 @@ void SecretBaseFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
this->combo_base_id->addItems(project->secretBaseIds);
populateDropdown(this->combo_base_id, project->secretBaseIds);
}
@ -967,8 +1061,6 @@ void SecretBaseFrame::populate(Project *project) {
void HealLocationFrame::setup() {
EventFrame::setup();
this->label_id->setText("Heal Location");
this->hideable_label_z->setVisible(false);
this->spinner_z->setVisible(false);
@ -996,7 +1088,8 @@ void HealLocationFrame::setup() {
QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc);
l_form_respawn_npc->setContentsMargins(0, 0, 0, 0);
this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc);
static const QString combo_respawn_npc_toolTip = Util::toHtmlParagraph("event_object ID of the NPC the player interacts with upon respawning after whiteout.");
static const QString combo_respawn_npc_toolTip = Util::toHtmlParagraph("The Local ID name or number of the NPC the player "
"interacts with upon respawning after whiteout.");
this->combo_respawn_npc->setToolTip(combo_respawn_npc_toolTip);
l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc);
this->layout_contents->addWidget(hideable_respawn_npc);
@ -1009,6 +1102,7 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
this->line_edit_id->disconnect();
connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) {
@ -1017,16 +1111,29 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
});
this->combo_respawn_map->disconnect();
connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) {
this->healLocation->setRespawnMapName(text);
connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
this->healLocation->setRespawnMapName(mapName);
this->healLocation->modify();
populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object);
});
connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
this->combo_respawn_npc->disconnect();
connect(this->combo_respawn_npc, &QComboBox::currentTextChanged, [this](const QString &text) {
this->healLocation->setRespawnNPC(text);
this->healLocation->modify();
});
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void HealLocationFrame::tryInvalidateIdDropdown(Map *map) {
// If the heal locations's target map is opened then the names in this frame's ID dropdown may be changed.
// Make sure we update the frame next time it's opened.
if (map && this->healLocation && map->name() == this->healLocation->getRespawnMapName()) {
invalidateValues();
}
}
void HealLocationFrame::initialize() {
@ -1050,7 +1157,8 @@ void HealLocationFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
this->combo_respawn_map->addItems(project->mapNames);
// TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map
// Same for warp IDs.
if (projectConfig.healLocationRespawnDataEnabled) {
populateDropdown(this->combo_respawn_map, project->mapNames);
populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object);
}
}

115
src/ui/eventpixmapitem.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "eventpixmapitem.h"
#include "project.h"
#include "editcommands.h"
#include "mapruler.h"
#include "metatile.h"
EventPixmapItem::EventPixmapItem(Event *event)
: QGraphicsPixmapItem(event->getPixmap()),
m_basePixmap(pixmap()),
m_event(event)
{
m_event->setPixmapItem(this);
updatePixelPosition();
}
void EventPixmapItem::render(Project *project) {
if (!m_event)
return;
m_basePixmap = m_event->loadPixmap(project);
// If the base pixmap changes, the event's pixel position may change.
updatePixelPosition();
QPixmap pixmap = m_basePixmap;
if (m_selected) {
// Draw the selection rectangle
QPainter painter(&pixmap);
painter.setPen(Qt::magenta);
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
}
setPixmap(pixmap);
emit rendered(m_basePixmap);
}
void EventPixmapItem::move(int dx, int dy) {
moveTo(m_event->getX() + dx,
m_event->getY() + dy);
}
void EventPixmapItem::moveTo(const QPoint &pos) {
moveTo(pos.x(), pos.y());
}
void EventPixmapItem::moveTo(int x, int y) {
bool changed = false;
if (m_event->getX() != x) {
m_event->setX(x);
emit xChanged(x);
changed = true;
}
if (m_event->getY() != y) {
m_event->setY(y);
emit yChanged(y);
changed = true;
}
if (changed) {
updatePixelPosition();
emit posChanged(x, y);
}
}
void EventPixmapItem::updatePixelPosition() {
setPos(m_event->getPixelX(), m_event->getPixelY());
}
void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {
if (m_active)
return;
m_active = true;
m_lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos());
bool selectionToggle = mouseEvent->modifiers() & Qt::ControlModifier;
if (selectionToggle || !m_selected) {
// User is either toggling this selection on/off as part of a group selection,
// or they're newly selecting just this item.
m_selected = (selectionToggle) ? !m_selected : true;
emit selected(m_event, selectionToggle);
} else {
// This item is already selected and the user isn't toggling the selection, so there are 4 possibilities:
// 1. This is the only selected event, and the selection is pointless.
// 2. This is the only selected event, and they want to drag the item around.
// 3. There's a group selection, and they want to start a new selection with just this item.
// 4. There's a group selection, and they want to drag the group around.
// 'selectMapEvent' will immediately clear the rest of the selection, which supports #1-3 but prevents #4.
// To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred.
m_releaseSelectionQueued = true;
}
mouseEvent->accept();
}
void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) {
if (!m_active || !m_selected)
return;
QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos());
if (pos == m_lastPos)
return;
m_releaseSelectionQueued = false;
emit dragged(m_event, m_lastPos, pos);
m_lastPos = pos;
}
void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) {
if (!m_active)
return;
m_active = false;
if (m_releaseSelectionQueued) {
m_releaseSelectionQueued = false;
if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == m_lastPos)
emit selected(m_event, false);
}
emit released(m_event, m_lastPos);
}

View File

@ -2,22 +2,7 @@
#include "mapview.h"
#include "editor.h"
void GraphicsView::mousePressEvent(QMouseEvent *event) {
QGraphicsView::mousePressEvent(event);
if (editor) {
editor->eventsView_onMousePress(event);
}
}
void GraphicsView::mouseMoveEvent(QMouseEvent *event) {
QGraphicsView::mouseMoveEvent(event);
}
void GraphicsView::mouseReleaseEvent(QMouseEvent *event) {
QGraphicsView::mouseReleaseEvent(event);
}
void GraphicsView::moveEvent(QMoveEvent *event) {
void MapView::moveEvent(QMoveEvent *event) {
QGraphicsView::moveEvent(event);
QLabel *label_MapRulerStatus = findChild<QLabel *>("label_MapRulerStatus", Qt::FindDirectChildrenOnly);
if (label_MapRulerStatus && label_MapRulerStatus->isVisible())

View File

@ -714,19 +714,20 @@ void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
}
void LayoutPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
QPoint pos = Metatile::coordFromPixmapCoord(event->pos());
this->paint_tile_initial_x = this->straight_path_initial_x = pos.x();
this->paint_tile_initial_y = this->straight_path_initial_y = pos.y();
this->metatilePos = Metatile::coordFromPixmapCoord(event->pos());
this->paint_tile_initial_x = this->straight_path_initial_x = this->metatilePos.x();
this->paint_tile_initial_y = this->straight_path_initial_y = this->metatilePos.y();
emit startPaint(event, this);
emit mouseEvent(event, this);
}
void LayoutPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QPoint pos = Metatile::coordFromPixmapCoord(event->pos());
if (pos != this->metatilePos) {
this->metatilePos = pos;
emit this->hoveredMapMetatileChanged(pos);
}
if (pos == this->metatilePos)
return;
this->metatilePos = pos;
emit hoveredMapMetatileChanged(pos);
emit mouseEvent(event, this);
}

View File

@ -110,8 +110,7 @@ void MapImageExporter::setModeSpecificUi() {
}
if (m_mode == ImageExporterMode::Timelapse) {
// TODO: At the moment edit history for events (and the DraggablePixmapItem class)
// explicitly depend on the editor and assume their map is currently open.
// TODO: At the moment edit history for events explicitly depend on the editor and assume their map is currently open.
// Other edit commands rely on this more subtly, like triggering API callbacks or
// spending time rendering their layout (which can make creating timelapses very slow).
// Until this is resolved, the selected map/layout must remain the same as in the editor.

View File

@ -40,7 +40,6 @@ ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int heig
: QObject(parent),
MovableRect(enabled, QRect(0, 0, width * 16, height * 16), color)
{
setZValue(0xFFFFFFFF); // ensure on top of view
setAcceptHoverEvents(true);
setFlags(this->flags() | QGraphicsItem::ItemIsMovable);
}

View File

@ -139,6 +139,7 @@ void ResizeLayoutPopup::setupLayoutView() {
static bool layoutSizeRectVisible = true;
this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255));
this->outline->setZValue(Editor::ZValue::ResizeLayoutPopup); // Ensure on top of view
this->outline->setLimit(cover->rect().toAlignedRect());
connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){
// Note: this extra limit check needs access to the project values, so it is done here and not ResizableRect::mouseMoveEvent