diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui new file mode 100644 index 00000000..a8b18de4 --- /dev/null +++ b/forms/paletteeditor.ui @@ -0,0 +1,1536 @@ + + + PaletteEditor + + + + 0 + 0 + 817 + 739 + + + + MainWindow + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Palette + + + + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Color 4 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 5 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 7 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 6 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 10 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 8 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 9 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 11 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 12 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 14 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 13 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 15 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 0 + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + 31 + + + Qt::Horizontal + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + Color 1 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + 31 + + + Qt::Horizontal + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 2 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + Color 3 + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Red + + + + + + + 31 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + Green + + + + + + + Blue + + + + + + + 31 + + + Qt::Horizontal + + + + + + + 31 + + + Qt::Horizontal + + + + + + + + + + + + + + + 0 + 0 + 817 + 21 + + + + + + + + diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 81727bc1..f8b6947f 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -1,232 +1,445 @@ TilesetEditor - + 0 0 - 1022 - 386 + 800 + 600 - Dialog + MainWindow - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - 1 - 0 - - - - true - - - - - 0 - 0 - 329 - 339 - + + + + + + true - - - - - Qt::Horizontal - - - - 26 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 26 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 120 - - - - - + + + + 0 + 0 + 386 + 539 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - - - - - - - 2 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 1 - 0 - - - - true - - - - - 0 - 0 - 316 - 319 - + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame - - - - - Qt::Horizontal + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Tile Properties - - - 21 - 20 - + + false - + + + + + Palette + + + + + + + + + + X Flip + + + + + + + Y Flip + + + + + + + + 0 + 0 + + + + Selection + + + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + 16 + 16 + + + + + 32 + 32 + + + + QFrame::StyledPanel + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + - - - - - - - Qt::Horizontal + + + + Metatile Properties - - - 21 - 20 - + + false - - - - - - Qt::Vertical + + false - - - 20 - 100 - - - + + + + + Bottom/Top + + + + + + + + 64 + 32 + + + + + 64 + 32 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + Metatile Behavior + + + + + + + + + + Layer Type + + + + + + + + - - - - - - - 1 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - + + + + + true + + + + + 0 + 0 + 386 + 359 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + 0 + 0 + 800 + 21 + + + + + File + + + + + + + Tools + + + + + + + + + + + + + Save Tileset + + + Ctrl+S + + + + + Import Primary Tiles + + + + + Import Secondary Tiles + + + + + Change Number of Metatiles + + + + + Change Palettes + + - - - buttonBox - accepted() - TilesetEditor - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - TilesetEditor - reject() - - - 316 - 260 - - - 286 - 274 - - - - + diff --git a/include/core/metatile.h b/include/core/metatile.h index b5cb19d5..4f280608 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -10,7 +10,10 @@ public: Metatile(); public: QList *tiles = nullptr; + uint8_t behavior; + uint8_t layerType; + Metatile *copy(); static int getBlockIndex(int); }; diff --git a/include/core/tileset.h b/include/core/tileset.h index d18ab5e0..ce735e35 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -17,16 +17,24 @@ public: QString tiles_label; QString palettes_label; QString metatiles_label; + QString metatiles_path; QString callback_label; QString metatile_attrs_label; + QString metatile_attrs_path; + QString tilesImagePath; + QImage tilesImage; + QList palettePaths; QList *tiles = nullptr; QList *metatiles = nullptr; QList> *palettes = nullptr; + Tileset* copy(); + static Tileset* getBlockTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); static QList> getBlockPalettes(Tileset*, Tileset*); + static QList getPalette(int, Tileset*, Tileset*); }; #endif // TILESET_H diff --git a/include/editor.h b/include/editor.h index a9433e95..ac95c761 100644 --- a/include/editor.h +++ b/include/editor.h @@ -68,9 +68,10 @@ public: void updateDiveMap(QString mapName); void updateEmergeMap(QString mapName); void setSelectedConnectionFromMap(QString mapName); - void updatePrimaryTileset(QString tilesetLabel); - void updateSecondaryTileset(QString tilesetLabel); + void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false); + void updateSecondaryTileset(QString tilesetLabel, bool forceLoad = false); void toggleBorderVisibility(bool visible); + Tileset *getCurrentMapPrimaryTileset(); DraggablePixmapItem *addMapEvent(Event *event); void selectMapEvent(DraggablePixmapItem *object); diff --git a/include/mainwindow.h b/include/mainwindow.h index 732f96b1..1eb1ed43 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -12,6 +12,7 @@ #include "project.h" #include "map.h" #include "editor.h" +#include "tileseteditor.h" namespace Ui { class MainWindow; @@ -42,6 +43,7 @@ private slots: void onLoadMapRequested(QString, QString); void onMapChanged(Map *map); void onMapNeedsRedrawing(); + void onTilesetsSaved(QString, QString); void on_action_Save_triggered(); void on_tabWidget_2_currentChanged(int index); @@ -92,6 +94,7 @@ private slots: void onAddNewMapToGroupClick(QAction* triggeredAction); void onTilesetChanged(QString); void currentMetatilesSelectionChanged(); + void onTilesetEditorClosed(); void on_action_Export_Map_Image_triggered(); @@ -123,8 +126,11 @@ private slots: void resetMapViewScale(); + void on_actionTileset_Editor_triggered(); + private: Ui::MainWindow *ui; + TilesetEditor *tilesetEditor = nullptr; QStandardItemModel *mapListModel; QList *mapGroupsModel; QMap mapListIndexes; @@ -153,6 +159,7 @@ private: void initEditor(); void loadUserSettings(); void openRecentProject(); + void updateTilesetEditor(); }; enum MapListUserRoles { diff --git a/include/project.h b/include/project.h index 4efe58a6..fa4ee2ed 100644 --- a/include/project.h +++ b/include/project.h @@ -40,14 +40,16 @@ public: QStringList *secretBaseIds = nullptr; QStringList *bgEventFacingDirections = nullptr; QStringList mapsWithConnections; + QMap metatileBehaviorMap; + QMap metatileBehaviorMapInverse; QMap *map_cache; Map* loadMap(QString); Map* getMap(QString); QMap *tileset_cache = nullptr; - Tileset* loadTileset(QString); - Tileset* getTileset(QString); + Tileset* loadTileset(QString, Tileset *tileset = nullptr); + Tileset* getTileset(QString, bool forceLoad = false); Blockdata* readBlockdata(QString); void loadBlockdata(Map*); @@ -72,6 +74,8 @@ public: void readMapsWithConnections(); void loadMapTilesets(Map*); void loadTilesetAssets(Tileset*); + void loadTilesetTiles(Tileset*, QImage); + void loadTilesetMetatiles(Tileset*); void saveBlockdata(Map*); void saveMapBorder(Map*); @@ -83,6 +87,7 @@ public: void saveMapGroupsTable(); void saveMapConstantsHeader(); void saveHealLocationStruct(Map*); + void saveTilesets(Tileset*, Tileset*); QList* parseAsm(QString text); QStringList getSongNames(); @@ -100,9 +105,11 @@ public: void readCoordEventWeatherNames(); void readSecretBaseIds(); void readBgEventFacingDirections(); + void readMetatileBehaviors(); void loadEventPixmaps(QList objects); QMap getEventObjGfxConstants(); + QString fixPalettePath(QString path); QString fixGraphicPath(QString path); void readMapEvents(Map *map); @@ -127,6 +134,10 @@ private: QString getMapLayoutFilepath(QString); void saveMapHeader(Map*); void saveMapConnections(Map*); + void saveTilesetMetatileAttributes(Tileset*); + void saveTilesetMetatiles(Tileset*); + void saveTilesetTilesImage(Tileset*); + void saveTilesetPalettes(Tileset*, bool); void updateMapsWithConnections(Map*); void saveMapsWithConnections(); void saveMapLayoutsTable(); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index 4bff143a..96896e78 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -8,7 +8,8 @@ QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); -QImage getMetatileImage(int, Tileset*, Tileset*); -QImage getTileImage(int, Tileset*, Tileset*); +QImage getMetatileImage(uint16_t, Tileset*, Tileset*); +QImage getTileImage(uint16_t, Tileset*, Tileset*); +QImage getColoredTileImage(uint16_t, Tileset*, Tileset*, int); #endif // IMAGEPROVIDERS_H diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h new file mode 100644 index 00000000..b6599e58 --- /dev/null +++ b/include/ui/metatilelayersitem.h @@ -0,0 +1,29 @@ +#ifndef METATILELAYERSITEM_H +#define METATILELAYERSITEM_H + +#include "tileset.h" +#include +#include + +class MetatileLayersItem : public QObject, public QGraphicsPixmapItem { + Q_OBJECT +public: + MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset) { + this->metatile = metatile; + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + } + void draw(); + void setTilesets(Tileset*, Tileset*); + void setMetatile(Metatile*); +private: + Metatile* metatile; + Tileset *primaryTileset; + Tileset *secondaryTileset; +signals: + void tileChanged(int, int); +protected: + void mousePressEvent(QGraphicsSceneMouseEvent*); +}; + +#endif // METATILELAYERSITEM_H diff --git a/include/ui/paletteeditor.h b/include/ui/paletteeditor.h new file mode 100644 index 00000000..d8866f24 --- /dev/null +++ b/include/ui/paletteeditor.h @@ -0,0 +1,43 @@ +#ifndef PALETTEEDITOR_H +#define PALETTEEDITOR_H + +#include +#include +#include +#include "project.h" + +namespace Ui { +class PaletteEditor; +} + +class PaletteEditor : public QMainWindow { + Q_OBJECT +public: + explicit PaletteEditor(Project*, Tileset*, Tileset*, QWidget *parent = nullptr); + ~PaletteEditor(); + void setPaletteId(int); + +private: + Ui::PaletteEditor *ui; + Project *project = nullptr; + QList> sliders; + QList frames; + Tileset *primaryTileset; + Tileset *secondaryTileset; + void disableSliderSignals(); + void enableSliderSignals(); + void initColorSliders(); + void refreshColorSliders(); + void refreshColors(); + void refreshColor(int); + void setColor(int); + +signals: + void closed(); + void changedPaletteColor(); + void changedPalette(int); +private slots: + void on_spinBox_PaletteId_valueChanged(int arg1); +}; + +#endif // PALETTEEDITOR_H diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 7befc465..f2d1f54e 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -1,22 +1,90 @@ #ifndef TILESETEDITOR_H #define TILESETEDITOR_H -#include +#include +#include "project.h" +#include "paletteeditor.h" +#include "tileseteditormetatileselector.h" +#include "tileseteditortileselector.h" +#include "metatilelayersitem.h" namespace Ui { class TilesetEditor; } -class TilesetEditor : public QDialog +class TilesetEditor : public QMainWindow { Q_OBJECT public: - explicit TilesetEditor(QWidget *parent = nullptr); + explicit TilesetEditor(Project*, QString, QString, QWidget *parent = nullptr); ~TilesetEditor(); + void setTilesets(QString, QString); + void init(Project*, QString, QString); + +private slots: + void onHoveredMetatileChanged(uint16_t); + void onHoveredMetatileCleared(); + void onSelectedMetatileChanged(uint16_t); + void onHoveredTileChanged(uint16_t); + void onHoveredTileCleared(); + void onSelectedTilesChanged(); + void onMetatileLayerTileChanged(int, int); + void onPaletteEditorClosed(); + void onPaletteEditorChangedPaletteColor(); + void onPaletteEditorChangedPalette(int); + + void on_spinBox_paletteSelector_valueChanged(int arg1); + + void on_checkBox_xFlip_stateChanged(int arg1); + + void on_checkBox_yFlip_stateChanged(int arg1); + + void on_comboBox_metatileBehaviors_currentIndexChanged(const QString &arg1); + + void on_comboBox_layerType_currentIndexChanged(int index); + + void on_actionSave_Tileset_triggered(); + + void on_actionImport_Primary_Tiles_triggered(); + + void on_actionImport_Secondary_Tiles_triggered(); + + void on_actionChange_Metatiles_Count_triggered(); + + void on_actionChange_Palettes_triggered(); private: + void closeEvent(QCloseEvent*); + void initMetatileSelector(); + void initTileSelector(); + void initSelectedTileItem(); + void initMetatileLayersItem(); + void drawSelectedTiles(); + void importTilesetTiles(Tileset*, bool); + void refresh(); Ui::TilesetEditor *ui; + TilesetEditorMetatileSelector *metatileSelector = nullptr; + TilesetEditorTileSelector *tileSelector = nullptr; + MetatileLayersItem *metatileLayersItem = nullptr; + PaletteEditor *paletteEditor = nullptr; + Project *project = nullptr; + Metatile *metatile = nullptr; + int paletteId; + bool tileXFlip; + bool tileYFlip; + bool hasUnsavedChanges; + Tileset *primaryTileset = nullptr; + Tileset *secondaryTileset = nullptr; + QGraphicsScene *metatilesScene = nullptr; + QGraphicsScene *tilesScene = nullptr; + QGraphicsScene *selectedTileScene = nullptr; + QGraphicsPixmapItem *selectedTilePixmapItem = nullptr; + QGraphicsScene *metatileLayersScene = nullptr; + +signals: + void tilesetsSaved(QString, QString); + void closed(); }; #endif // TILESETEDITOR_H diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h new file mode 100644 index 00000000..3da82721 --- /dev/null +++ b/include/ui/tileseteditormetatileselector.h @@ -0,0 +1,44 @@ +#ifndef TILESETEDITORMETATILESELECTOR_H +#define TILESETEDITORMETATILESELECTOR_H + +#include "selectablepixmapitem.h" +#include "tileset.h" + +class TilesetEditorMetatileSelector: public SelectablePixmapItem { + Q_OBJECT +public: + TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset): SelectablePixmapItem(32, 32, 1, 1) { + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + this->numMetatilesWide = 8; + setAcceptHoverEvents(true); + } + void draw(); + void select(uint16_t metatileId); + void setTilesets(Tileset*, Tileset*); + uint16_t getSelectedMetatile(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + void hoverMoveEvent(QGraphicsSceneHoverEvent*); + void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + +private: + Tileset *primaryTileset = nullptr; + Tileset *secondaryTileset = nullptr; + uint16_t selectedMetatile; + int numMetatilesWide; + void updateSelectedMetatile(); + uint16_t getMetatileId(int x, int y); + QPoint getMetatileIdCoords(uint16_t); + uint16_t getValidMetatileId(uint16_t); + +signals: + void hoveredMetatileChanged(uint16_t); + void hoveredMetatileCleared(); + void selectedMetatileChanged(uint16_t); +}; + +#endif // TILESETEDITORMETATILESELECTOR_H diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h new file mode 100644 index 00000000..fe8d6bc2 --- /dev/null +++ b/include/ui/tileseteditortileselector.h @@ -0,0 +1,52 @@ +#ifndef TILESETEDITORTILESELECTOR_H +#define TILESETEDITORTILESELECTOR_H + +#include "selectablepixmapitem.h" +#include "tileset.h" + +class TilesetEditorTileSelector: public SelectablePixmapItem { + Q_OBJECT +public: + TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset): SelectablePixmapItem(16, 16, 2, 2) { + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + this->numTilesWide = 16; + this->paletteId = 0; + this->xFlip = false; + this->yFlip = false; + setAcceptHoverEvents(true); + } + void draw(); + void select(uint16_t metatileId); + void setTilesets(Tileset*, Tileset*); + void setPaletteId(int); + void setTileFlips(bool, bool); + QList getSelectedTiles(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + void hoverMoveEvent(QGraphicsSceneHoverEvent*); + void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + +private: + Tileset *primaryTileset; + Tileset *secondaryTileset; + QList selectedTiles; + int numTilesWide; + int paletteId; + bool xFlip; + bool yFlip; + void updateSelectedTiles(); + uint16_t getTileId(int x, int y); + QPoint getTileCoords(uint16_t); + QList getCurPaletteTable(); + +signals: + void hoveredTileChanged(uint16_t); + void hoveredTileCleared(); + void selectedTilesChanged(); +}; + +#endif // TILESETEDITORTILESELECTOR_H diff --git a/porymap.pro b/porymap.pro index dc94f6eb..e96e2f40 100644 --- a/porymap.pro +++ b/porymap.pro @@ -35,13 +35,17 @@ SOURCES += src/core/block.cpp \ src/ui/imageproviders.cpp \ src/ui/mappixmapitem.cpp \ src/ui/mapsceneeventfilter.cpp \ + src/ui/metatilelayersitem.cpp \ src/ui/metatileselector.cpp \ src/ui/movementpermissionsselector.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ + src/ui/paletteeditor.cpp \ src/ui/selectablepixmapitem.cpp \ src/ui/tileseteditor.cpp \ + src/ui/tileseteditormetatileselector.cpp \ + src/ui/tileseteditortileselector.cpp \ src/editor.cpp \ src/main.cpp \ src/mainwindow.cpp \ @@ -71,13 +75,17 @@ HEADERS += include/core/block.h \ include/ui/imageproviders.h \ include/ui/mappixmapitem.h \ include/ui/mapsceneeventfilter.h \ + include/ui/metatilelayersitem.h \ include/ui/metatileselector.h \ include/ui/movementpermissionsselector.h \ include/ui/neweventtoolbutton.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ + include/ui/paletteeditor.h \ include/ui/selectablepixmapitem.h \ include/ui/tileseteditor.h \ + include/ui/tileseteditormetatileselector.h \ + include/ui/tileseteditortileselector.h \ include/editor.h \ include/mainwindow.h \ include/project.h \ @@ -85,7 +93,8 @@ HEADERS += include/core/block.h \ FORMS += forms/mainwindow.ui \ forms/eventpropertiesframe.ui \ - forms/tileseteditor.ui + forms/tileseteditor.ui \ + forms/paletteeditor.ui RESOURCES += \ resources/images.qrc diff --git a/src/core/map.cpp b/src/core/map.cpp index 3954df06..185306e5 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -226,7 +226,7 @@ QPixmap Map::renderBorder() { } QPixmap Map::renderConnection(MapConnection connection) { - render(); + render(true); int x, y, w, h; if (connection.direction == "up") { x = 0; diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 6dff9dc6..a44b4fa3 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -7,6 +7,17 @@ Metatile::Metatile() tiles = new QList; } +Metatile* Metatile::copy() { + Metatile *copy = new Metatile; + copy->behavior = this->behavior; + copy->layerType = this->layerType; + copy->tiles = new QList; + for (Tile tile : *this->tiles) { + copy->tiles->append(tile); + } + return copy; +} + int Metatile::getBlockIndex(int index) { if (index < Project::getNumMetatilesPrimary()) { return index; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 772e4d7b..d3acbf2b 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -11,6 +11,43 @@ Tileset::Tileset() } +Tileset* Tileset::copy() { + Tileset *copy = new Tileset; + copy->name = this->name; + copy->is_compressed = this->is_compressed; + copy->is_secondary = this->is_secondary; + copy->padding = this->padding; + copy->tiles_label = this->tiles_label; + copy->palettes_label = this->palettes_label; + copy->metatiles_label = this->metatiles_label; + copy->metatiles_path = this->metatiles_path; + copy->callback_label = this->callback_label; + copy->metatile_attrs_label = this->metatile_attrs_label; + copy->metatile_attrs_path = this->metatile_attrs_path; + copy->tilesImage = this->tilesImage.copy(); + copy->tilesImagePath = this->tilesImagePath; + for (int i = 0; i < this->palettePaths.length(); i++) { + copy->palettePaths.append(this->palettePaths.at(i)); + } + copy->tiles = new QList; + for (QImage tile : *this->tiles) { + copy->tiles->append(tile.copy()); + } + copy->metatiles = new QList; + for (Metatile *metatile : *this->metatiles) { + copy->metatiles->append(metatile->copy()); + } + copy->palettes = new QList>; + for (QList palette : *this->palettes) { + QList copyPalette; + for (QRgb color : palette) { + copyPalette.append(color); + } + copy->palettes->append(copyPalette); + } + return copy; +} + Tileset* Tileset::getBlockTileset(int metatile_index, Tileset *primaryTileset, Tileset *secondaryTileset) { if (metatile_index < Project::getNumMetatilesPrimary()) { return primaryTileset; @@ -39,3 +76,14 @@ QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *s } return palettes; } + +QList Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset) { + QList paletteTable; + Tileset *tileset = paletteId < Project::getNumPalettesPrimary() + ? primaryTileset + : secondaryTileset; + for (int i = 0; i < tileset->palettes->at(paletteId).length(); i++) { + paletteTable.append(tileset->palettes->at(paletteId).at(i)); + } + return paletteTable; +} diff --git a/src/editor.cpp b/src/editor.cpp index 23fce69d..08f0c766 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -961,22 +961,22 @@ void Editor::updateDiveEmergeMap(QString mapName, QString direction) { ui->label_NumConnections->setText(QString::number(map->connections.length())); } -void Editor::updatePrimaryTileset(QString tilesetLabel) +void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_primary_label != tilesetLabel) + if (map->layout->tileset_primary_label != tilesetLabel || forceLoad) { map->layout->tileset_primary_label = tilesetLabel; - map->layout->tileset_primary = project->getTileset(tilesetLabel); + map->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); emit tilesetChanged(map->name); } } -void Editor::updateSecondaryTileset(QString tilesetLabel) +void Editor::updateSecondaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_secondary_label != tilesetLabel) + if (map->layout->tileset_secondary_label != tilesetLabel || forceLoad) { map->layout->tileset_secondary_label = tilesetLabel; - map->layout->tileset_secondary = project->getTileset(tilesetLabel); + map->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); emit tilesetChanged(map->name); } } @@ -987,6 +987,12 @@ void Editor::toggleBorderVisibility(bool visible) this->setConnectionsVisibility(visible); } +Tileset* Editor::getCurrentMapPrimaryTileset() +{ + QString tilesetLabel = map->layout->tileset_primary_label; + return project->getTileset(tilesetLabel); +} + void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { active = true; last_x = static_cast(mouse->pos().x() + this->pos().x()) / 16; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e5e1115e..8dbc04e3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -50,7 +50,7 @@ MainWindow::~MainWindow() } void MainWindow::initExtraShortcuts() { - new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this, SLOT(redo())); + new QShortcut(QKeySequence("Ctrl+Shift+Z"), this, SLOT(redo())); new QShortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale())); ui->actionZoom_In->setShortcuts({QKeySequence("Ctrl++"), QKeySequence("Ctrl+=")}); } @@ -200,6 +200,7 @@ void MainWindow::setMap(QString map_name, bool scrollTreeView) { setRecentMap(map_name); updateMapList(); + updateTilesetEditor(); } void MainWindow::redrawMapScene() @@ -410,6 +411,7 @@ void MainWindow::loadDataStructures() { project->readSecretBaseIds(); project->readBgEventFacingDirections(); project->readMapsWithConnections(); + project->readMetatileBehaviors(); project->readTilesetProperties(); } @@ -525,6 +527,12 @@ void MainWindow::onTilesetChanged(QString mapName) setMap(mapName); } +void MainWindow::updateTilesetEditor() { + if (this->tilesetEditor) { + this->tilesetEditor->setTilesets(editor->ui->comboBox_PrimaryTileset->currentText(), editor->ui->comboBox_SecondaryTileset->currentText()); + } +} + void MainWindow::currentMetatilesSelectionChanged() { ui->graphicsView_currentMetatileSelection->setFixedSize(editor->scene_current_metatile_selection_item->pixmap().width() + 2, editor->scene_current_metatile_selection_item->pixmap().height() + 2); @@ -1099,6 +1107,11 @@ void MainWindow::onMapNeedsRedrawing() { redrawMapScene(); } +void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { + this->editor->updatePrimaryTileset(primaryTilesetLabel, true); + this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); +} + void MainWindow::on_action_Export_Map_Image_triggered() { QString defaultFilepath = QString("%1/%2.png").arg(editor->project->root).arg(editor->map->name); @@ -1223,3 +1236,27 @@ void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected) bool visible = selected != 0; editor->toggleBorderVisibility(visible); } + +void MainWindow::on_actionTileset_Editor_triggered() +{ + if (!this->tilesetEditor) { + this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label, this); + connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString))); + connect(this->tilesetEditor, SIGNAL(closed()), this, SLOT(onTilesetEditorClosed())); + } + + if (!this->tilesetEditor->isVisible()) { + this->tilesetEditor->show(); + } else if (this->tilesetEditor->isMinimized()) { + this->tilesetEditor->showNormal(); + } else { + this->tilesetEditor->activateWindow(); + } +} + +void MainWindow::onTilesetEditorClosed() { + if (this->tilesetEditor) { + delete this->tilesetEditor; + this->tilesetEditor = nullptr; + } +} diff --git a/src/project.cpp b/src/project.cpp index 63899604..f1168123 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -576,6 +576,77 @@ void Project::saveHealLocationStruct(Map *map) { saveTextFile(root + "/include/constants/heal_locations.h", constants_text); } +void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { + saveTilesetMetatileAttributes(primaryTileset); + saveTilesetMetatileAttributes(secondaryTileset); + saveTilesetMetatiles(primaryTileset); + saveTilesetMetatiles(secondaryTileset); + saveTilesetTilesImage(primaryTileset); + saveTilesetTilesImage(secondaryTileset); + saveTilesetPalettes(primaryTileset, true); + saveTilesetPalettes(secondaryTileset, false); +} + +void Project::saveTilesetMetatileAttributes(Tileset *tileset) { + QFile attrs_file(tileset->metatile_attrs_path); + if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QByteArray data; + for (Metatile *metatile : *tileset->metatiles) { + data.append(static_cast(metatile->behavior)); + data.append(static_cast((metatile->layerType << 4) & 0xF0)); + } + attrs_file.write(data); + } else { + qDebug() << QString("Could not save tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path); + } +} + +void Project::saveTilesetMetatiles(Tileset *tileset) { + QFile metatiles_file(tileset->metatiles_path); + if (metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QByteArray data; + for (Metatile *metatile : *tileset->metatiles) { + for (int i = 0; i < 8; i++) { + Tile tile = metatile->tiles->at(i); + uint16_t value = static_cast((tile.tile & 0x3ff) + | ((tile.xflip & 1) << 10) + | ((tile.yflip & 1) << 11) + | ((tile.palette & 0xf) << 12)); + data.append(static_cast(value & 0xff)); + data.append(static_cast((value >> 8) & 0xff)); + } + } + metatiles_file.write(data); + } else { + tileset->metatiles = new QList; + qDebug() << QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path); + } +} + +void Project::saveTilesetTilesImage(Tileset *tileset) { + tileset->tilesImage.save(tileset->tilesImagePath); +} + +void Project::saveTilesetPalettes(Tileset *tileset, bool primary) { + int startPaletteId = primary ? 0 : Project::getNumPalettesPrimary(); + int endPaletteId = primary ? Project::getNumPalettesPrimary() : Project::getNumPalettesTotal(); + for (int i = startPaletteId; i < endPaletteId; i++) { + QString filepath = tileset->palettePaths.at(i); + QString content = "JASC-PAL\r\n"; + content += "0100\r\n"; + content += "16\r\n"; + for (int j = 0; j < 16; j++) { + QRgb color = tileset->palettes->at(i).at(j); + content += QString("%1 %2 %3\r\n") + .arg(qRed(color)) + .arg(qGreen(color)) + .arg(qBlue(color)); + } + + saveTextFile(filepath, content); + } +} + void Project::loadMapTilesets(Map* map) { if (map->layout->has_unsaved_changes) { return; @@ -585,12 +656,14 @@ void Project::loadMapTilesets(Map* map) { map->layout->tileset_secondary = getTileset(map->layout->tileset_secondary_label); } -Tileset* Project::loadTileset(QString label) { +Tileset* Project::loadTileset(QString label, Tileset *tileset) { ParseUtil *parser = new ParseUtil; QString headers_text = readTextFile(root + "/data/tilesets/headers.inc"); QStringList *values = getLabelValues(parser->parseAsm(headers_text), label); - Tileset *tileset = new Tileset; + if (tileset == nullptr) { + tileset = new Tileset; + } tileset->name = label; tileset->is_compressed = values->value(0); tileset->is_secondary = values->value(1); @@ -750,7 +823,8 @@ void Project::loadTilesetAssets(Tileset* tileset) { if (tileset->name.isNull()) { return; } - QString dir_path = root + "/data/tilesets/" + category + "/" + tileset->name.replace("gTileset_", "").toLower(); + QString tilesetName = tileset->name; + QString dir_path = root + "/data/tilesets/" + category + "/" + tilesetName.replace("gTileset_", "").toLower(); QString graphics_text = readTextFile(root + "/data/tilesets/graphics.inc"); QList *graphics = parser->parseAsm(graphics_text); @@ -767,53 +841,94 @@ void Project::loadTilesetAssets(Tileset* tileset) { } } - QStringList *palette_paths = new QStringList; if (!palettes_values->isEmpty()) { for (int i = 0; i < palettes_values->length(); i++) { QString value = palettes_values->value(i); - palette_paths->append(root + "/" + value.section('"', 1, 1)); + tileset->palettePaths.append(this->fixPalettePath(root + "/" + value.section('"', 1, 1))); } } else { QString palettes_dir_path = dir_path + "/palettes"; for (int i = 0; i < 16; i++) { - palette_paths->append(palettes_dir_path + "/" + QString("%1").arg(i, 2, 10, QLatin1Char('0')) + ".gbapal"); + tileset->palettePaths.append(palettes_dir_path + "/" + QString("%1").arg(i, 2, 10, QLatin1Char('0')) + ".pal"); } } - QString metatiles_path; - QString metatile_attrs_path; QString metatiles_text = readTextFile(root + "/data/tilesets/metatiles.inc"); QList *metatiles_macros = parser->parseAsm(metatiles_text); QStringList *metatiles_values = getLabelValues(metatiles_macros, tileset->metatiles_label); if (!metatiles_values->isEmpty()) { - metatiles_path = root + "/" + metatiles_values->value(0).section('"', 1, 1); + tileset->metatiles_path = root + "/" + metatiles_values->value(0).section('"', 1, 1); } else { - metatiles_path = dir_path + "/metatiles.bin"; + tileset->metatiles_path = dir_path + "/metatiles.bin"; } QStringList *metatile_attrs_values = getLabelValues(metatiles_macros, tileset->metatile_attrs_label); if (!metatile_attrs_values->isEmpty()) { - metatile_attrs_path = root + "/" + metatile_attrs_values->value(0).section('"', 1, 1); + tileset->metatile_attrs_path = root + "/" + metatile_attrs_values->value(0).section('"', 1, 1); } else { - metatile_attrs_path = dir_path + "/metatile_attributes.bin"; + tileset->metatile_attrs_path = dir_path + "/metatile_attributes.bin"; } - // tiles tiles_path = fixGraphicPath(tiles_path); - QImage *image = new QImage(tiles_path); - //image->setColor(0, qRgb(0xff, 0, 0)); // debug + tileset->tilesImagePath = tiles_path; + QImage image = QImage(tileset->tilesImagePath); + this->loadTilesetTiles(tileset, image); + this->loadTilesetMetatiles(tileset); + // palettes + QList> *palettes = new QList>; + for (int i = 0; i < tileset->palettePaths.length(); i++) { + QList palette; + QString path = tileset->palettePaths.value(i); + QString text = readTextFile(path); + if (!text.isNull()) { + QStringList lines = text.split(QRegExp("[\r\n]"),QString::SkipEmptyParts); + if (lines.length() == 19 && lines[0] == "JASC-PAL" && lines[1] == "0100" && lines[2] == "16") { + for (int j = 0; j < 16; j++) { + QStringList rgb = lines[j + 3].split(QRegExp(" "), QString::SkipEmptyParts); + if (rgb.length() != 3) { + qDebug() << QString("Invalid tileset palette RGB value: '%1'").arg(lines[j + 3]); + palette.append(qRgb((j - 3) * 16, (j - 3) * 16, (j - 3) * 16)); + } else { + int red = rgb[0].toInt(); + int green = rgb[1].toInt(); + int blue = rgb[2].toInt(); + QRgb color = qRgb(red, green, blue); + palette.append(color); + } + } + } else { + qDebug() << QString("Invalid JASC-PAL palette file for tileset."); + for (int j = 0; j < 16; j++) { + palette.append(qRgb(j * 16, j * 16, j * 16)); + } + } + } else { + for (int j = 0; j < 16; j++) { + palette.append(qRgb(j * 16, j * 16, j * 16)); + } + qDebug() << QString("Could not open palette path '%1'").arg(path); + } + + palettes->append(palette); + } + tileset->palettes = palettes; +} + +void Project::loadTilesetTiles(Tileset *tileset, QImage image) { QList *tiles = new QList; int w = 8; int h = 8; - for (int y = 0; y < image->height(); y += h) - for (int x = 0; x < image->width(); x += w) { - QImage tile = image->copy(x, y, w, h); + for (int y = 0; y < image.height(); y += h) + for (int x = 0; x < image.width(); x += w) { + QImage tile = image.copy(x, y, w, h); tiles->append(tile); } + tileset->tilesImage = image; tileset->tiles = tiles; +} - // metatiles - QFile metatiles_file(metatiles_path); +void Project::loadTilesetMetatiles(Tileset* tileset) { + QFile metatiles_file(tileset->metatiles_path); if (metatiles_file.open(QIODevice::ReadOnly)) { QByteArray data = metatiles_file.readAll(); int num_metatiles = data.length() / 16; @@ -837,11 +952,10 @@ void Project::loadTilesetAssets(Tileset* tileset) { tileset->metatiles = metatiles; } else { tileset->metatiles = new QList; - qDebug() << QString("Could not open '%1'").arg(metatiles_path); + qDebug() << QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path); } - QFile attrs_file(metatile_attrs_path); - //qDebug() << metatile_attrs_path; + QFile attrs_file(tileset->metatile_attrs_path); if (attrs_file.open(QIODevice::ReadOnly)) { QByteArray data = attrs_file.readAll(); int num_metatiles = tileset->metatiles->count(); @@ -851,41 +965,14 @@ void Project::loadTilesetAssets(Tileset* tileset) { if (num_metatiles > num_metatileAttrs) num_metatiles = num_metatileAttrs; } - } else { - qDebug() << QString("Could not open '%1'").arg(metatile_attrs_path); - } - - // palettes - QList> *palettes = new QList>; - for (int i = 0; i < palette_paths->length(); i++) { - QString path = palette_paths->value(i); - // the palettes are not compressed. this should never happen. it's only a precaution. - path = path.replace(QRegExp("\\.lz$"), ""); - // TODO default to .pal (JASC-PAL) - // just use .gbapal for now - QFile file(path); - QList palette; - if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); - for (int j = 0; j < 16; j++) { - uint16_t word = data[j*2] & 0xff; - word += (data[j*2 + 1] & 0xff) << 8; - int red = word & 0x1f; - int green = (word >> 5) & 0x1f; - int blue = (word >> 10) & 0x1f; - QRgb color = qRgb(red * 8, green * 8, blue * 8); - palette.append(color); - } - } else { - for (int j = 0; j < 16; j++) { - palette.append(qRgb(j * 16, j * 16, j * 16)); - } - qDebug() << QString("Could not open palette path '%1'").arg(path); + for (int i = 0; i < num_metatileAttrs; i++) { + int value = (static_cast(data.at(i * 2 + 1)) << 8) | static_cast(data.at(i * 2)); + tileset->metatiles->at(i)->behavior = value & 0xFF; + tileset->metatiles->at(i)->layerType = (value & 0xF000) >> 12; } - - palettes->append(palette); + } else { + qDebug() << QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path); } - tileset->palettes = palettes; } Blockdata* Project::readBlockdata(QString path) { @@ -913,11 +1000,16 @@ Map* Project::getMap(QString map_name) { } } -Tileset* Project::getTileset(QString label) { +Tileset* Project::getTileset(QString label, bool forceLoad) { + Tileset *existingTileset = nullptr; if (tileset_cache->contains(label)) { + existingTileset = tileset_cache->value(label); + } + + if (existingTileset && !forceLoad) { return tileset_cache->value(label); } else { - Tileset *tileset = loadTileset(label); + Tileset *tileset = loadTileset(label, existingTileset); return tileset; } } @@ -1249,6 +1341,22 @@ void Project::readBgEventFacingDirections() { readCDefinesSorted(filepath, prefixes, bgEventFacingDirections); } +void Project::readMetatileBehaviors() { + this->metatileBehaviorMap.clear(); + this->metatileBehaviorMapInverse.clear(); + QString filepath = root + "/include/constants/metatile_behaviors.h"; + QString text = readTextFile(filepath); + if (!text.isNull()) { + QStringList prefixes = (QStringList() << "MB_"); + this->metatileBehaviorMap = readCDefines(text, prefixes); + for (QString defineName : this->metatileBehaviorMap.keys()) { + this->metatileBehaviorMapInverse.insert(this->metatileBehaviorMap[defineName], defineName); + } + } else { + qDebug() << "Failed to read C defines file: " << filepath; + } +} + void Project::readCDefinesSorted(QString filepath, QStringList prefixes, QStringList* definesToSet) { QString text = readTextFile(filepath); if (!text.isNull()) { @@ -1324,6 +1432,11 @@ QMap Project::getEventObjGfxConstants() { return constants; } +QString Project::fixPalettePath(QString path) { + path = path.replace(QRegExp("\\.gbapal$"), ".pal"); + return path; +} + QString Project::fixGraphicPath(QString path) { path = path.replace(QRegExp("\\.lz$"), ""); path = path.replace(QRegExp("\\.[1248]bpp$"), ".png"); diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index d5873800..b9a6a6ad 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -13,7 +13,7 @@ QImage getCollisionMetatileImage(int collision, int elevation) { return collisionImage.toImage(); } -QImage getMetatileImage(int tile, Tileset *primaryTileset, Tileset *secondaryTileset) { +QImage getMetatileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset) { QImage metatile_image(16, 16, QImage::Format_RGBA8888); Metatile* metatile = Tileset::getMetatile(tile, primaryTileset, secondaryTileset); @@ -71,7 +71,7 @@ QImage getMetatileImage(int tile, Tileset *primaryTileset, Tileset *secondaryTil return metatile_image; } -QImage getTileImage(int tile, Tileset *primaryTileset, Tileset *secondaryTileset) { +QImage getTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset) { Tileset *tileset = Tileset::getBlockTileset(tile, primaryTileset, secondaryTileset); int local_index = Metatile::getBlockIndex(tile); if (!tileset || !tileset->tiles) { @@ -79,3 +79,19 @@ QImage getTileImage(int tile, Tileset *primaryTileset, Tileset *secondaryTileset } return tileset->tiles->value(local_index, QImage()); } + +QImage getColoredTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId) { + QList palette = Tileset::getPalette(paletteId, primaryTileset, secondaryTileset); + QImage tileImage = getTileImage(tile, primaryTileset, secondaryTileset); + if (tileImage.isNull()) { + tileImage = QImage(16, 16, QImage::Format_RGBA8888); + QPainter painter(&tileImage); + painter.fillRect(0, 0, 16, 16, palette.at(0)); + } else { + for (int i = 0; i < 16; i++) { + tileImage.setColor(i, palette.at(i)); + } + } + + return tileImage; +} diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp new file mode 100644 index 00000000..fe8b164f --- /dev/null +++ b/src/ui/metatilelayersitem.cpp @@ -0,0 +1,45 @@ +#include "metatilelayersitem.h" +#include "imageproviders.h" +#include + +void MetatileLayersItem::draw() { + const QList tileCoords = QList{ + QPoint(0, 0), + QPoint(16, 0), + QPoint(0, 16), + QPoint(16, 16), + QPoint(32, 0), + QPoint(48, 0), + QPoint(32, 16), + QPoint(48, 16), + }; + + QPixmap pixmap(64, 32); + QPainter painter(&pixmap); + for (int i = 0; i < 8; i++) { + Tile tile = this->metatile->tiles->at(i); + QImage tileImage = getColoredTileImage(tile.tile, this->primaryTileset, this->secondaryTileset, tile.palette) + .mirrored(tile.xflip, tile.yflip) + .scaled(16, 16); + painter.drawImage(tileCoords.at(i), tileImage); + } + + this->setPixmap(pixmap); +} + +void MetatileLayersItem::setMetatile(Metatile *metatile) { + this->metatile = metatile; +} + +void MetatileLayersItem::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + this->draw(); +} + +void MetatileLayersItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + QPointF pos = event->pos(); + int x = static_cast(pos.x()) / 16; + int y = static_cast(pos.y()) / 16; + emit this->tileChanged(x, y); +} diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp new file mode 100644 index 00000000..bd67fa5f --- /dev/null +++ b/src/ui/paletteeditor.cpp @@ -0,0 +1,176 @@ +#include "paletteeditor.h" +#include "ui_paletteeditor.h" + +PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::PaletteEditor) +{ + this->project = project; + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + this->ui->setupUi(this); + this->ui->spinBox_PaletteId->setMinimum(0); + this->ui->spinBox_PaletteId->setMaximum(Project::getNumPalettesTotal() - 1); + this->sliders.clear(); + for (int i = 0; i < 16; i++) { + this->sliders.append(QList()); + } + this->sliders[0].append(this->ui->horizontalSlider); + this->sliders[0].append(this->ui->horizontalSlider_2); + this->sliders[0].append(this->ui->horizontalSlider_3); + this->sliders[1].append(this->ui->horizontalSlider_4); + this->sliders[1].append(this->ui->horizontalSlider_5); + this->sliders[1].append(this->ui->horizontalSlider_6); + this->sliders[2].append(this->ui->horizontalSlider_7); + this->sliders[2].append(this->ui->horizontalSlider_8); + this->sliders[2].append(this->ui->horizontalSlider_9); + this->sliders[3].append(this->ui->horizontalSlider_10); + this->sliders[3].append(this->ui->horizontalSlider_11); + this->sliders[3].append(this->ui->horizontalSlider_12); + this->sliders[4].append(this->ui->horizontalSlider_13); + this->sliders[4].append(this->ui->horizontalSlider_14); + this->sliders[4].append(this->ui->horizontalSlider_15); + this->sliders[5].append(this->ui->horizontalSlider_16); + this->sliders[5].append(this->ui->horizontalSlider_17); + this->sliders[5].append(this->ui->horizontalSlider_18); + this->sliders[6].append(this->ui->horizontalSlider_19); + this->sliders[6].append(this->ui->horizontalSlider_20); + this->sliders[6].append(this->ui->horizontalSlider_21); + this->sliders[7].append(this->ui->horizontalSlider_22); + this->sliders[7].append(this->ui->horizontalSlider_23); + this->sliders[7].append(this->ui->horizontalSlider_24); + this->sliders[8].append(this->ui->horizontalSlider_25); + this->sliders[8].append(this->ui->horizontalSlider_26); + this->sliders[8].append(this->ui->horizontalSlider_27); + this->sliders[9].append(this->ui->horizontalSlider_28); + this->sliders[9].append(this->ui->horizontalSlider_29); + this->sliders[9].append(this->ui->horizontalSlider_30); + this->sliders[10].append(this->ui->horizontalSlider_31); + this->sliders[10].append(this->ui->horizontalSlider_32); + this->sliders[10].append(this->ui->horizontalSlider_33); + this->sliders[11].append(this->ui->horizontalSlider_34); + this->sliders[11].append(this->ui->horizontalSlider_35); + this->sliders[11].append(this->ui->horizontalSlider_36); + this->sliders[12].append(this->ui->horizontalSlider_37); + this->sliders[12].append(this->ui->horizontalSlider_38); + this->sliders[12].append(this->ui->horizontalSlider_39); + this->sliders[13].append(this->ui->horizontalSlider_40); + this->sliders[13].append(this->ui->horizontalSlider_41); + this->sliders[13].append(this->ui->horizontalSlider_42); + this->sliders[14].append(this->ui->horizontalSlider_43); + this->sliders[14].append(this->ui->horizontalSlider_44); + this->sliders[14].append(this->ui->horizontalSlider_45); + this->sliders[15].append(this->ui->horizontalSlider_46); + this->sliders[15].append(this->ui->horizontalSlider_47); + this->sliders[15].append(this->ui->horizontalSlider_48); + + this->frames.clear(); + this->frames.append(this->ui->frame); + this->frames.append(this->ui->frame_2); + this->frames.append(this->ui->frame_3); + this->frames.append(this->ui->frame_4); + this->frames.append(this->ui->frame_5); + this->frames.append(this->ui->frame_6); + this->frames.append(this->ui->frame_7); + this->frames.append(this->ui->frame_8); + this->frames.append(this->ui->frame_9); + this->frames.append(this->ui->frame_10); + this->frames.append(this->ui->frame_11); + this->frames.append(this->ui->frame_12); + this->frames.append(this->ui->frame_13); + this->frames.append(this->ui->frame_14); + this->frames.append(this->ui->frame_15); + this->frames.append(this->ui->frame_16); + + this->initColorSliders(); + this->refreshColorSliders(); + this->refreshColors(); +} + +PaletteEditor::~PaletteEditor() +{ + delete ui; +} + +void PaletteEditor::disableSliderSignals() { + for (int i = 0; i < this->sliders.length(); i++) { + this->sliders.at(i).at(0)->blockSignals(true); + this->sliders.at(i).at(1)->blockSignals(true); + this->sliders.at(i).at(2)->blockSignals(true); + } +} + +void PaletteEditor::enableSliderSignals() { + for (int i = 0; i < this->sliders.length(); i++) { + this->sliders.at(i).at(0)->blockSignals(false); + this->sliders.at(i).at(1)->blockSignals(false); + this->sliders.at(i).at(2)->blockSignals(false); + } +} + +void PaletteEditor::initColorSliders() { + for (int i = 0; i < 16; i++) { + connect(this->sliders[i][0], &QSlider::valueChanged, [=](int) { this->setColor(i); }); + connect(this->sliders[i][1], &QSlider::valueChanged, [=](int) { this->setColor(i); }); + connect(this->sliders[i][2], &QSlider::valueChanged, [=](int) { this->setColor(i); }); + } +} + +void PaletteEditor::refreshColorSliders() { + disableSliderSignals(); + int paletteNum = this->ui->spinBox_PaletteId->value(); + for (int i = 0; i < 16; i++) { + QRgb color; + if (paletteNum < Project::getNumPalettesPrimary()) { + color = this->primaryTileset->palettes->at(paletteNum).at(i); + } else { + color = this->secondaryTileset->palettes->at(paletteNum).at(i); + } + + this->sliders[i][0]->setValue(qRed(color) / 8); + this->sliders[i][1]->setValue(qGreen(color) / 8); + this->sliders[i][2]->setValue(qBlue(color) / 8); + } + enableSliderSignals(); +} + +void PaletteEditor::refreshColors() { + for (int i = 0; i < 16; i++) { + this->refreshColor(i); + } +} + +void PaletteEditor::refreshColor(int colorIndex) { + QString stylesheet = QString("background-color: rgb(%1, %2, %3);") + .arg(this->sliders[colorIndex][0]->value() * 8) + .arg(this->sliders[colorIndex][1]->value() * 8) + .arg(this->sliders[colorIndex][2]->value() * 8); + this->frames[colorIndex]->setStyleSheet(stylesheet); +} + +void PaletteEditor::setPaletteId(int paletteId) { + this->ui->spinBox_PaletteId->blockSignals(true); + this->ui->spinBox_PaletteId->setValue(paletteId); + this->refreshColorSliders(); + this->refreshColors(); + this->ui->spinBox_PaletteId->blockSignals(false); +} + +void PaletteEditor::setColor(int colorIndex) { + int paletteNum = this->ui->spinBox_PaletteId->value(); + int red = this->sliders[colorIndex][0]->value() * 8; + int green = this->sliders[colorIndex][1]->value() * 8; + int blue = this->sliders[colorIndex][2]->value() * 8; + Tileset *tileset = paletteNum < Project::getNumPalettesPrimary() + ? this->primaryTileset + : this->secondaryTileset; + (*tileset->palettes)[paletteNum][colorIndex] = qRgb(red, green, blue); + this->refreshColor(colorIndex); + emit this->changedPaletteColor(); +} + +void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) { + this->refreshColorSliders(); + this->refreshColors(); + emit this->changedPalette(paletteId); +} diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index 5dcd252f..dbfbaa34 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -80,6 +80,10 @@ void SelectablePixmapItem::updateSelection(int x, int y) QPoint SelectablePixmapItem::getCellPos(QPointF pos) { + if (pos.x() < 0) pos.setX(0); + if (pos.y() < 0) pos.setY(0); + if (pos.x() >= this->pixmap().width()) pos.setX(this->pixmap().width() - 1); + if (pos.y() >= this->pixmap().height()) pos.setY(this->pixmap().height() - 1); return QPoint(static_cast(pos.x()) / this->cellWidth, static_cast(pos.y()) / this->cellHeight); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index b4be9766..60fe30d4 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -1,14 +1,460 @@ #include "tileseteditor.h" #include "ui_tileseteditor.h" +#include "imageproviders.h" +#include +#include +#include +#include -TilesetEditor::TilesetEditor(QWidget *parent) : - QDialog(parent), +TilesetEditor::TilesetEditor(Project *project, QString primaryTilesetLabel, QString secondaryTilesetLabel, QWidget *parent) : + QMainWindow(parent), ui(new Ui::TilesetEditor) { - ui->setupUi(this); + this->init(project, primaryTilesetLabel, secondaryTilesetLabel); } TilesetEditor::~TilesetEditor() { delete ui; } + +void TilesetEditor::init(Project *project, QString primaryTilesetLabel, QString secondaryTilesetLabel) { + ui->setupUi(this); + this->project = project; + + this->hasUnsavedChanges = false; + this->tileXFlip = ui->checkBox_xFlip->isChecked(); + this->tileYFlip = ui->checkBox_yFlip->isChecked(); + this->paletteId = ui->spinBox_paletteSelector->value(); + + Tileset *primaryTileset = project->getTileset(primaryTilesetLabel); + Tileset *secondaryTileset = project->getTileset(secondaryTilesetLabel); + if (this->primaryTileset) delete this->primaryTileset; + if (this->secondaryTileset) delete this->secondaryTileset; + this->primaryTileset = primaryTileset->copy(); + this->secondaryTileset = secondaryTileset->copy(); + + QList sortedBehaviors; + for (int num : project->metatileBehaviorMapInverse.keys()) { + this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num); + } + this->ui->comboBox_layerType->addItem("Normal - Middle/Top", 0); + this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", 1); + this->ui->comboBox_layerType->addItem("Split - Bottom/Top", 2); + this->ui->spinBox_paletteSelector->setMinimum(0); + this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); + + this->initMetatileSelector(); + this->initMetatileLayersItem(); + this->initTileSelector(); + this->initSelectedTileItem(); + this->metatileSelector->select(0); +} + +void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel) { + delete this->primaryTileset; + delete this->secondaryTileset; + Tileset *primaryTileset = project->getTileset(primaryTilesetLabel); + Tileset *secondaryTileset = project->getTileset(secondaryTilesetLabel); + this->primaryTileset = primaryTileset->copy(); + this->secondaryTileset = secondaryTileset->copy(); + this->refresh(); +} + +void TilesetEditor::refresh() { + this->metatileSelector->setTilesets(this->primaryTileset, this->secondaryTileset); + this->tileSelector->setTilesets(this->primaryTileset, this->secondaryTileset); + this->metatileLayersItem->setTilesets(this->primaryTileset, this->secondaryTileset); + this->metatileSelector->select(this->metatileSelector->getSelectedMetatile()); + this->drawSelectedTiles(); + + this->ui->graphicsView_Tiles->setSceneRect(0, 0, this->tileSelector->pixmap().width() + 2, this->tileSelector->pixmap().height() + 2); + this->ui->graphicsView_Tiles->setFixedSize(this->tileSelector->pixmap().width() + 2, this->tileSelector->pixmap().height() + 2); + this->ui->graphicsView_Metatiles->setSceneRect(0, 0, this->metatileSelector->pixmap().width() + 2, this->metatileSelector->pixmap().height() + 2); + this->ui->graphicsView_Metatiles->setFixedSize(this->metatileSelector->pixmap().width() + 2, this->metatileSelector->pixmap().height() + 2); + this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width(), this->selectedTilePixmapItem->pixmap().height()); +} + +void TilesetEditor::initMetatileSelector() +{ + this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset); + connect(this->metatileSelector, SIGNAL(hoveredMetatileChanged(uint16_t)), + this, SLOT(onHoveredMetatileChanged(uint16_t))); + connect(this->metatileSelector, SIGNAL(hoveredMetatileCleared()), + this, SLOT(onHoveredMetatileCleared())); + connect(this->metatileSelector, SIGNAL(selectedMetatileChanged(uint16_t)), + this, SLOT(onSelectedMetatileChanged(uint16_t))); + + this->metatilesScene = new QGraphicsScene; + this->metatilesScene->addItem(this->metatileSelector); + this->metatileSelector->draw(); + + this->ui->graphicsView_Metatiles->setScene(this->metatilesScene); + this->ui->graphicsView_Metatiles->setFixedSize(this->metatileSelector->pixmap().width() + 2, this->metatileSelector->pixmap().height() + 2); +} + +void TilesetEditor::initTileSelector() +{ + this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset); + connect(this->tileSelector, SIGNAL(hoveredTileChanged(uint16_t)), + this, SLOT(onHoveredTileChanged(uint16_t))); + connect(this->tileSelector, SIGNAL(hoveredTileCleared()), + this, SLOT(onHoveredTileCleared())); + connect(this->tileSelector, SIGNAL(selectedTilesChanged()), + this, SLOT(onSelectedTilesChanged())); + + this->tilesScene = new QGraphicsScene; + this->tilesScene->addItem(this->tileSelector); + this->tileSelector->select(0); + this->tileSelector->draw(); + + this->ui->graphicsView_Tiles->setScene(this->tilesScene); + this->ui->graphicsView_Tiles->setFixedSize(this->tileSelector->pixmap().width() + 2, this->tileSelector->pixmap().height() + 2); +} + +void TilesetEditor::initSelectedTileItem() { + this->selectedTileScene = new QGraphicsScene; + this->drawSelectedTiles(); + this->ui->graphicsView_selectedTile->setScene(this->selectedTileScene); + this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width(), this->selectedTilePixmapItem->pixmap().height()); +} + +void TilesetEditor::drawSelectedTiles() { + if (!this->selectedTileScene) { + return; + } + + this->selectedTileScene->clear(); + QList tiles = this->tileSelector->getSelectedTiles(); + QPoint dimensions = this->tileSelector->getSelectionDimensions(); + QImage selectionImage(16 * dimensions.x(), 16 * dimensions.y(), QImage::Format_RGBA8888); + QPainter painter(&selectionImage); + int tileIndex = 0; + for (int j = 0; j < dimensions.y(); j++) { + for (int i = 0; i < dimensions.x(); i++) { + QImage tileImage = getColoredTileImage(tiles.at(tileIndex), this->primaryTileset, this->secondaryTileset, this->paletteId) + .mirrored(this->tileXFlip, this->tileYFlip) + .scaled(16, 16); + tileIndex++; + painter.drawImage(i * 16, j * 16, tileImage); + } + } + + this->selectedTilePixmapItem = new QGraphicsPixmapItem(QPixmap::fromImage(selectionImage)); + this->selectedTileScene->addItem(this->selectedTilePixmapItem); + this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width(), this->selectedTilePixmapItem->pixmap().height()); +} + +void TilesetEditor::initMetatileLayersItem() { + Metatile *metatile = Tileset::getMetatile(this->metatileSelector->getSelectedMetatile(), this->primaryTileset, this->secondaryTileset); + this->metatileLayersItem = new MetatileLayersItem(metatile, this->primaryTileset, this->secondaryTileset); + connect(this->metatileLayersItem, SIGNAL(tileChanged(int, int)), + this, SLOT(onMetatileLayerTileChanged(int, int))); + + this->metatileLayersScene = new QGraphicsScene; + this->metatileLayersScene->addItem(this->metatileLayersItem); + this->ui->graphicsView_metatileLayers->setScene(this->metatileLayersScene); +} + +void TilesetEditor::onHoveredMetatileChanged(uint16_t metatileId) { + QString message = QString("Metatile: 0x%1") + .arg(QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper()); + this->ui->statusbar->showMessage(message); +} + +void TilesetEditor::onHoveredMetatileCleared() { + this->ui->statusbar->clearMessage(); +} + +void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { + this->metatile = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset); + this->metatileLayersItem->setMetatile(metatile); + this->metatileLayersItem->draw(); + this->ui->comboBox_metatileBehaviors->setCurrentIndex(this->ui->comboBox_metatileBehaviors->findData(this->metatile->behavior)); + this->ui->comboBox_layerType->setCurrentIndex(this->ui->comboBox_layerType->findData(this->metatile->layerType)); +} + +void TilesetEditor::onHoveredTileChanged(uint16_t tile) { + QString message = QString("Tile: 0x%1") + .arg(QString("%1").arg(tile, 3, 16, QChar('0')).toUpper()); + this->ui->statusbar->showMessage(message); +} + +void TilesetEditor::onHoveredTileCleared() { + this->ui->statusbar->clearMessage(); +} + +void TilesetEditor::onSelectedTilesChanged() { + this->drawSelectedTiles(); +} + +void TilesetEditor::onMetatileLayerTileChanged(int x, int y) { + int maxTileIndex = x < 2 ? 3 : 7; + QPoint dimensions = this->tileSelector->getSelectionDimensions(); + QList tiles = this->tileSelector->getSelectedTiles(); + int selectedTileIndex = 0; + for (int j = 0; j < dimensions.y(); j++) { + for (int i = 0; i < dimensions.x(); i++) { + int tileIndex = ((x + i) / 2 * 4) + ((y + j) * 2) + ((x + i) % 2); + if (tileIndex <= maxTileIndex) { + Tile tile = this->metatile->tiles->at(tileIndex); + tile.tile = tiles.at(selectedTileIndex); + tile.xflip = this->tileXFlip; + tile.yflip = this->tileYFlip; + tile.palette = this->paletteId; + (*this->metatile->tiles)[tileIndex] = tile; + } + selectedTileIndex++; + } + } + + this->metatileSelector->draw(); + this->metatileLayersItem->draw(); + this->hasUnsavedChanges = true; +} + +void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId) +{ + this->ui->spinBox_paletteSelector->blockSignals(true); + this->ui->spinBox_paletteSelector->setValue(paletteId); + this->ui->spinBox_paletteSelector->blockSignals(false); + this->paletteId = paletteId; + this->tileSelector->setPaletteId(paletteId); + this->drawSelectedTiles(); + if (this->paletteEditor) { + this->paletteEditor->setPaletteId(paletteId); + } +} + +void TilesetEditor::on_checkBox_xFlip_stateChanged(int checked) +{ + this->tileXFlip = checked; + this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); + this->drawSelectedTiles(); +} + +void TilesetEditor::on_checkBox_yFlip_stateChanged(int checked) +{ + this->tileYFlip = checked; + this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); + this->drawSelectedTiles(); +} + +void TilesetEditor::on_comboBox_metatileBehaviors_currentIndexChanged(const QString &metatileBehavior) +{ + if (this->metatile) { + this->metatile->behavior = static_cast(project->metatileBehaviorMap[metatileBehavior]); + } +} + +void TilesetEditor::on_comboBox_layerType_currentIndexChanged(int layerType) +{ + if (this->metatile) { + this->metatile->layerType = static_cast(layerType); + } +} + +void TilesetEditor::on_actionSave_Tileset_triggered() +{ + this->project->saveTilesets(this->primaryTileset, this->secondaryTileset); + emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name); + this->ui->statusbar->showMessage(QString("Saved primary and secondary Tilesets!"), 5000); + this->hasUnsavedChanges = false; +} + +void TilesetEditor::on_actionImport_Primary_Tiles_triggered() +{ + this->importTilesetTiles(this->primaryTileset, true); +} + +void TilesetEditor::on_actionImport_Secondary_Tiles_triggered() +{ + this->importTilesetTiles(this->secondaryTileset, false); +} + +void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { + QString descriptor = primary ? "primary" : "secondary"; + QString descriptorCaps = primary ? "Primary" : "Secondary"; + + QString filepath = QFileDialog::getOpenFileName( + this, + QString("Import %1 Tileset Tiles Image").arg(descriptorCaps), + this->project->root, + "Image Files (*.png)"); + if (filepath.isEmpty()) { + return; + } + + qDebug() << QString("Importing %1 tileset tiles '%2'").arg(descriptor).arg(filepath); + + // Validate image dimensions. + QImage image = QImage(filepath); + if (image.width() == 0 || image.height() == 0 || image.width() % 8 != 0 || image.height() % 8 != 0) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import tiles."); + msgBox.setInformativeText(QString("The image dimensions (%1 x %2) are invalid. Width and height must be multiples of 8 pixels.") + .arg(image.width()) + .arg(image.height())); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + return; + } + + // Validate image is properly indexed to 16 colors. + if (image.colorCount() != 16) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import tiles."); + msgBox.setInformativeText(QString("The image must be indexed and contain 16 total colors. The provided image has %1 indexed colors.") + .arg(image.colorCount())); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + return; + } + + // Validate total number of tiles in image. + int numTilesWide = image.width() / 16; + int numTilesHigh = image.height() / 16; + int totalTiles = numTilesHigh * numTilesWide; + int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesTotal() - Project::getNumTilesPrimary(); + if (totalTiles > maxAllowedTiles) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import tiles."); + msgBox.setInformativeText(QString("The maximum number of tiles allowed in the %1 tileset is %2, but the provided image contains %3 total tiles.") + .arg(descriptor) + .arg(maxAllowedTiles) + .arg(totalTiles)); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + return; + } + + this->project->loadTilesetTiles(tileset, image); + this->project->loadTilesetMetatiles(tileset); + this->refresh(); + this->hasUnsavedChanges = true; +} + +void TilesetEditor::closeEvent(QCloseEvent *event) +{ + bool close = true; + if (this->hasUnsavedChanges) { + QMessageBox::StandardButton result = QMessageBox::question(this, "porymap", + "Discard unsaved Tileset changes?", + QMessageBox::No | QMessageBox::Yes, + QMessageBox::Yes); + close = result == QMessageBox::Yes; + } + + if (close) { + event->accept(); + emit closed(); + } else { + event->ignore(); + } +} + +void TilesetEditor::on_actionChange_Metatiles_Count_triggered() +{ + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowTitle("Change Number of Metatiles"); + dialog.setWindowModality(Qt::NonModal); + + QFormLayout form(&dialog); + + QSpinBox *primarySpinBox = new QSpinBox(); + QSpinBox *secondarySpinBox = new QSpinBox(); + primarySpinBox->setMinimum(1); + secondarySpinBox->setMinimum(1); + primarySpinBox->setMaximum(Project::getNumMetatilesPrimary()); + secondarySpinBox->setMaximum(Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()); + primarySpinBox->setValue(this->primaryTileset->metatiles->length()); + secondarySpinBox->setValue(this->secondaryTileset->metatiles->length()); + form.addRow(new QLabel("Primary Tileset"), primarySpinBox); + form.addRow(new QLabel("Secondary Tileset"), secondarySpinBox); + + QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); + connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); + form.addRow(&buttonBox); + + if (dialog.exec() == QDialog::Accepted) { + int numPrimaryMetatiles = primarySpinBox->value(); + int numSecondaryMetatiles = secondarySpinBox->value(); + while (this->primaryTileset->metatiles->length() > numPrimaryMetatiles) { + Metatile *metatile = this->primaryTileset->metatiles->takeLast(); + delete metatile; + } + while (this->primaryTileset->metatiles->length() < numPrimaryMetatiles) { + Tile tile; + tile.palette = 0; + tile.tile = 0; + tile.xflip = 0; + tile.yflip = 0; + Metatile *metatile = new Metatile; + metatile->behavior = 0; + metatile->layerType = 0; + for (int i = 0; i < 8; i++) { + metatile->tiles->append(tile); + } + this->primaryTileset->metatiles->append(metatile); + } + while (this->secondaryTileset->metatiles->length() > numSecondaryMetatiles) { + Metatile *metatile = this->secondaryTileset->metatiles->takeLast(); + delete metatile; + } + while (this->secondaryTileset->metatiles->length() < numSecondaryMetatiles) { + Tile tile; + tile.palette = 0; + tile.tile = 0; + tile.xflip = 0; + tile.yflip = 0; + Metatile *metatile = new Metatile; + metatile->behavior = 0; + metatile->layerType = 0; + for (int i = 0; i < 8; i++) { + metatile->tiles->append(tile); + } + this->secondaryTileset->metatiles->append(metatile); + } + + this->refresh(); + this->hasUnsavedChanges = true; + } +} + +void TilesetEditor::onPaletteEditorClosed() { + if (this->paletteEditor) { + delete this->paletteEditor; + this->paletteEditor = nullptr; + } +} + +void TilesetEditor::on_actionChange_Palettes_triggered() +{ + if (!this->paletteEditor) { + this->paletteEditor = new PaletteEditor(this->project, this->primaryTileset, this->secondaryTileset, this); + connect(this->paletteEditor, SIGNAL(closed()), this, SLOT(onPaletteEditorClosed())); + connect(this->paletteEditor, SIGNAL(changedPaletteColor()), this, SLOT(onPaletteEditorChangedPaletteColor())); + connect(this->paletteEditor, SIGNAL(changedPalette(int)), this, SLOT(onPaletteEditorChangedPalette(int))); + } + + if (!this->paletteEditor->isVisible()) { + this->paletteEditor->show(); + } else if (this->paletteEditor->isMinimized()) { + this->paletteEditor->showNormal(); + } else { + this->paletteEditor->activateWindow(); + } +} + +void TilesetEditor::onPaletteEditorChangedPaletteColor() { + this->refresh(); + this->hasUnsavedChanges = true; +} + +void TilesetEditor::onPaletteEditorChangedPalette(int paletteId) { + this->on_spinBox_paletteSelector_valueChanged(paletteId); +} diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp new file mode 100644 index 00000000..852ab0e9 --- /dev/null +++ b/src/ui/tileseteditormetatileselector.cpp @@ -0,0 +1,114 @@ +#include "tileseteditormetatileselector.h" +#include "imageproviders.h" +#include "project.h" +#include + +void TilesetEditorMetatileSelector::draw() { + if (!this->primaryTileset || !this->primaryTileset->metatiles + || !this->secondaryTileset || !this->secondaryTileset->metatiles) { + this->setPixmap(QPixmap()); + } + + int primaryLength = this->primaryTileset->metatiles->length(); + int length_ = primaryLength + this->secondaryTileset->metatiles->length(); + int height_ = length_ / this->numMetatilesWide; + QImage image(this->numMetatilesWide * 32, height_ * 32, QImage::Format_RGBA8888); + QPainter painter(&image); + for (int i = 0; i < length_; i++) { + int tile = i; + if (i >= primaryLength) { + tile += Project::getNumMetatilesPrimary() - primaryLength; + } + QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset).scaled(32, 32); + int map_y = i / this->numMetatilesWide; + int map_x = i % this->numMetatilesWide; + QPoint metatile_origin = QPoint(map_x * 32, map_y * 32); + painter.drawImage(metatile_origin, metatile_image); + } + + painter.end(); + this->setPixmap(QPixmap::fromImage(image)); + this->drawSelection(); +} + +void TilesetEditorMetatileSelector::select(uint16_t metatileId) { + metatileId = this->getValidMetatileId(metatileId); + QPoint coords = this->getMetatileIdCoords(metatileId); + SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); + this->selectedMetatile = metatileId; + emit selectedMetatileChanged(metatileId); +} + +void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + this->draw(); +} + +void TilesetEditorMetatileSelector::updateSelectedMetatile() { + QPoint origin = this->getSelectionStart(); + this->selectedMetatile = this->getMetatileId(origin.x(), origin.y()); +} + +uint16_t TilesetEditorMetatileSelector::getSelectedMetatile() { + return this->selectedMetatile; +} + +uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) { + int index = y * this->numMetatilesWide + x; + if (index < this->primaryTileset->metatiles->length()) { + return static_cast(index); + } else { + return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->metatiles->length()); + } +} + +void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { + SelectablePixmapItem::mousePressEvent(event); + this->updateSelectedMetatile(); + emit selectedMetatileChanged(this->selectedMetatile); +} + +void TilesetEditorMetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + SelectablePixmapItem::mouseMoveEvent(event); + this->updateSelectedMetatile(); + + QPoint pos = this->getCellPos(event->pos()); + uint16_t metatileId = this->getMetatileId(pos.x(), pos.y()); + emit hoveredMetatileChanged(metatileId); + emit selectedMetatileChanged(metatileId); +} + +void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + SelectablePixmapItem::mouseReleaseEvent(event); + this->updateSelectedMetatile(); + emit selectedMetatileChanged(this->selectedMetatile); +} + +void TilesetEditorMetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { + QPoint pos = this->getCellPos(event->pos()); + uint16_t metatileId = this->getMetatileId(pos.x(), pos.y()); + emit this->hoveredMetatileChanged(metatileId); +} + +void TilesetEditorMetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { + emit this->hoveredMetatileCleared(); +} + +QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) { + int index = metatileId < Project::getNumMetatilesPrimary() + ? metatileId + : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->metatiles->length(); + return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); +} + +uint16_t TilesetEditorMetatileSelector::getValidMetatileId(uint16_t metatileId) { + if (metatileId >= Project::getNumMetatilesTotal() + || (metatileId < Project::getNumMetatilesPrimary() && metatileId >= this->primaryTileset->metatiles->length()) + || (metatileId < Project::getNumMetatilesTotal() && metatileId >= Project::getNumMetatilesPrimary() + this->secondaryTileset->metatiles->length())) + { + return 0; + } + return metatileId; +} + diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp new file mode 100644 index 00000000..6d323f46 --- /dev/null +++ b/src/ui/tileseteditortileselector.cpp @@ -0,0 +1,130 @@ +#include "tileseteditortileselector.h" +#include "imageproviders.h" +#include "project.h" +#include + +void TilesetEditorTileSelector::draw() { + if (!this->primaryTileset || !this->primaryTileset->tiles + || !this->secondaryTileset || !this->secondaryTileset->tiles) { + this->setPixmap(QPixmap()); + } + + int totalTiles = Project::getNumTilesTotal(); + int primaryLength = this->primaryTileset->tiles->length(); + int secondaryLength = this->secondaryTileset->tiles->length(); + int height = totalTiles / this->numTilesWide; + QList palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset); + QImage image(this->numTilesWide * 16, height * 16, QImage::Format_RGBA8888); + + QPainter painter(&image); + for (uint16_t tile = 0; tile < totalTiles; tile++) { + QImage tileImage; + if (tile < primaryLength) { + tileImage = getColoredTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId).scaled(16, 16); + } else if (tile < Project::getNumTilesPrimary()) { + tileImage = QImage(16, 16, QImage::Format_RGBA8888); + tileImage.fill(palette.at(0)); + } else if (tile < Project::getNumTilesPrimary() + secondaryLength) { + tileImage = getColoredTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId).scaled(16, 16); + } else { + tileImage = QImage(16, 16, QImage::Format_RGBA8888); + QPainter painter(&tileImage); + painter.fillRect(0, 0, 16, 16, palette.at(0)); + } + + int y = tile / this->numTilesWide; + int x = tile % this->numTilesWide; + QPoint origin = QPoint(x * 16, y * 16); + painter.drawImage(origin, tileImage.mirrored(this->xFlip, this->yFlip)); + } + + painter.end(); + this->setPixmap(QPixmap::fromImage(image)); + this->drawSelection(); +} + +void TilesetEditorTileSelector::select(uint16_t tile) { + QPoint coords = this->getTileCoords(tile); + SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0); + this->updateSelectedTiles(); + emit selectedTilesChanged(); +} + +void TilesetEditorTileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { + this->primaryTileset = primaryTileset; + this->secondaryTileset = secondaryTileset; + this->draw(); +} + +void TilesetEditorTileSelector::setPaletteId(int paletteId) { + this->paletteId = paletteId; + this->draw(); +} + +void TilesetEditorTileSelector::setTileFlips(bool xFlip, bool yFlip) { + this->xFlip = xFlip; + this->yFlip = yFlip; + this->draw(); +} + +void TilesetEditorTileSelector::updateSelectedTiles() { + this->selectedTiles.clear(); + QPoint origin = this->getSelectionStart(); + QPoint dimensions = this->getSelectionDimensions(); + for (int j = 0; j < dimensions.y(); j++) { + for (int i = 0; i < dimensions.x(); i++) { + uint16_t metatileId = this->getTileId(origin.x() + i, origin.y() + j); + this->selectedTiles.append(metatileId); + } + } +} + +QList TilesetEditorTileSelector::getSelectedTiles() { + return this->selectedTiles; +} + +uint16_t TilesetEditorTileSelector::getTileId(int x, int y) { + return static_cast(y * this->numTilesWide + x); +} + +void TilesetEditorTileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { + SelectablePixmapItem::mousePressEvent(event); + this->updateSelectedTiles(); + emit selectedTilesChanged(); +} + +void TilesetEditorTileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + SelectablePixmapItem::mouseMoveEvent(event); + this->updateSelectedTiles(); + + QPoint pos = this->getCellPos(event->pos()); + uint16_t tile = this->getTileId(pos.x(), pos.y()); + emit hoveredTileChanged(tile); + emit selectedTilesChanged(); +} + +void TilesetEditorTileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + SelectablePixmapItem::mouseReleaseEvent(event); + this->updateSelectedTiles(); + emit selectedTilesChanged(); +} + +void TilesetEditorTileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { + QPoint pos = this->getCellPos(event->pos()); + uint16_t tile = this->getTileId(pos.x(), pos.y()); + emit this->hoveredTileChanged(tile); +} + +void TilesetEditorTileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { + emit this->hoveredTileCleared(); +} + +QPoint TilesetEditorTileSelector::getTileCoords(uint16_t tile) { + if (tile >= Project::getNumTilesTotal()) + { + // Invalid tile. + return QPoint(0, 0); + } + + return QPoint(tile % this->numTilesWide, tile / this->numTilesWide); +}