diff --git a/forms/palettecolorsearch.ui b/forms/palettecolorsearch.ui
new file mode 100644
index 00000000..b6ae87c8
--- /dev/null
+++ b/forms/palettecolorsearch.ui
@@ -0,0 +1,152 @@
+
+
+ PaletteColorSearch
+
+
+
+ 0
+ 0
+ 547
+ 329
+
+
+
+ Palette Color Search
+
+
+ -
+
+
+
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+ -
+
+
+ QFrame::Shape::NoFrame
+
+
+ QFrame::Shadow::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+ 32
+ 32
+
+
+
+ QFrame::Shadow::Raised
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Color
+
+
+
+ -
+
+
+ 1
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Palette
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ -
+
+
+ 2
+
+
+
+
+
+ -
+
+
+ QDialogButtonBox::StandardButton::Close
+
+
+
+
+
+
+
+ NoScrollSpinBox
+ QSpinBox
+
+
+
+
+
+
diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui
index c98d49b6..ae394040 100644
--- a/forms/paletteeditor.ui
+++ b/forms/paletteeditor.ui
@@ -38,6 +38,27 @@
+ -
+
+
+ <html><head/><body><p>Opens a search dialog to find which tilesets/metatiles are using certain colors.</p></body></html>
+
+
+ ...
+
+
+
+ :/icons/magnifier.ico:/icons/magnifier.ico
+
+
+
+ -
+
+
+ (All colors used)
+
+
+
-
@@ -89,7 +110,7 @@
0
0
883
- 784
+ 779
@@ -133,8 +154,22 @@
+
+
+
+
@@ -164,7 +199,22 @@
Import Palette
+
+
+ true
+
+
+ Show Unused Colors
+
+
+
+
+ Find Color Usage...
+
+
-
+
+
+
diff --git a/include/config.h b/include/config.h
index e929d64c..25482457 100644
--- a/include/config.h
+++ b/include/config.h
@@ -116,6 +116,7 @@ public:
bool showTilesetEditorLayerGrid;
bool showTilesetEditorDivider;
bool showTilesetEditorRawAttributes;
+ bool showPaletteEditorUnusedColors;
bool monitorFiles;
bool tilesetCheckerboardFill;
bool newMapHeaderSectionExpanded;
diff --git a/include/core/events.h b/include/core/events.h
index 76a58dcf..0e8af1f7 100644
--- a/include/core/events.h
+++ b/include/core/events.h
@@ -122,8 +122,8 @@ public:
int getZ() const { return this->elevation; }
int getElevation() const { return this->elevation; }
- int getPixelX() const { return (this->x * 16) - qMax(0, (pixmap.width() - 16) / 2); }
- int getPixelY() const { return (this->y * 16) - qMax(0, pixmap.height() - 16); }
+ int getPixelX() const;
+ int getPixelY() const;
virtual EventFrame *getEventFrame();
virtual EventFrame *createEventFrame() = 0;
diff --git a/include/core/metatile.h b/include/core/metatile.h
index e35e1fa3..eba4afed 100644
--- a/include/core/metatile.h
+++ b/include/core/metatile.h
@@ -61,7 +61,7 @@ public:
static int getDefaultAttributesSize(BaseGameVersion version);
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId);
- static QString getMetatileIdStrings(const QList metatileIds);
+ static QString getMetatileIdStrings(const QList &metatileIds);
static QString getLayerName(int layerNum);
static constexpr int tileWidth() { return 2; }
diff --git a/include/core/tile.h b/include/core/tile.h
index a13e9fc7..11fd6a99 100644
--- a/include/core/tile.h
+++ b/include/core/tile.h
@@ -29,6 +29,7 @@ public:
static constexpr int pixelWidth() { return 8; }
static constexpr int pixelHeight() { return 8; }
static constexpr QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); }
+ static constexpr int numPixels() { return Tile::pixelWidth() * Tile::pixelHeight(); }
static constexpr int sizeInBytes() { return sizeof(uint16_t); }
};
diff --git a/include/core/tileset.h b/include/core/tileset.h
index be5bdace..3d137056 100644
--- a/include/core/tileset.h
+++ b/include/core/tileset.h
@@ -37,9 +37,14 @@ public:
QList> palettePreviews;
static QString stripPrefix(const QString &fullName);
+ static Tileset* getPaletteTileset(int, Tileset*, Tileset*);
+ static const Tileset* getPaletteTileset(int, const Tileset*, const Tileset*);
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
+ static const Tileset* getMetatileTileset(int, const Tileset*, const Tileset*);
static Tileset* getTileTileset(int, Tileset*, Tileset*);
+ static const Tileset* getTileTileset(int, const Tileset*, const Tileset*);
static Metatile* getMetatile(int, Tileset*, Tileset*);
+ static const Metatile* getMetatile(int, const Tileset*, const Tileset*);
static Tileset* getMetatileLabelTileset(int, Tileset*, Tileset*);
static QString getMetatileLabel(int, Tileset *, Tileset *);
static QString getOwnedMetatileLabel(int, Tileset *, Tileset *);
@@ -47,9 +52,9 @@ public:
static bool setMetatileLabel(int, QString, Tileset *, Tileset *);
QString getMetatileLabelPrefix();
static QString getMetatileLabelPrefix(const QString &name);
- static QList> getBlockPalettes(Tileset*, Tileset*, bool useTruePalettes = false);
- static QList getPalette(int, Tileset*, Tileset*, bool useTruePalettes = false);
- static bool metatileIsValid(uint16_t metatileId, Tileset *, Tileset *);
+ static QList> getBlockPalettes(const Tileset*, const Tileset*, bool useTruePalettes = false);
+ static QList getPalette(int, const Tileset*, const Tileset*, bool useTruePalettes = false);
+ static bool metatileIsValid(uint16_t metatileId, const Tileset*, const Tileset*);
static QHash getHeaderMemberMap(bool usingAsm);
static QString getExpectedDir(QString tilesetName, bool isSecondary);
QString getExpectedDir();
@@ -92,6 +97,12 @@ public:
QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); }
+ QSet getUnusedColorIds(int paletteId, const Tileset *pairedTileset, const QSet &searchColors = {}) const;
+ QList findMetatilesUsingColor(int paletteId, int colorId, const Tileset *pairedTileset) const;
+
+ static constexpr int maxPalettes() { return 16; }
+ static constexpr int numColorsPerPalette() { return 16; }
+
private:
QList m_metatiles;
diff --git a/include/core/utility.h b/include/core/utility.h
index 41c1dcde..7270b552 100644
--- a/include/core/utility.h
+++ b/include/core/utility.h
@@ -16,6 +16,7 @@ namespace Util {
QString replaceExtension(const QString &path, const QString &newExtension);
void setErrorStylesheet(QLineEdit *lineEdit, bool isError);
QString toStylesheetString(const QFont &font);
+ void show(QWidget *w);
}
#endif // UTILITY_H
diff --git a/include/editor.h b/include/editor.h
index fd9a809e..ffe628ac 100644
--- a/include/editor.h
+++ b/include/editor.h
@@ -67,8 +67,6 @@ public:
bool setLayout(QString layoutName);
void unsetMap();
- Tileset *getCurrentMapPrimaryTileset();
-
bool displayMap();
bool displayLayout();
diff --git a/include/mainwindow.h b/include/mainwindow.h
index 813370b4..841cdcf6 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -385,7 +385,6 @@ private:
void openDuplicateMapOrLayoutDialog();
void openNewMapGroupDialog();
void openNewLocationDialog();
- void openSubWindow(QWidget * window);
void scrollMapList(MapTree *list, const QString &itemName);
void scrollMapListToCurrentMap(MapTree *list);
void scrollMapListToCurrentLayout(MapTree *list);
diff --git a/include/project.h b/include/project.h
index dd66c2e5..1c14f996 100644
--- a/include/project.h
+++ b/include/project.h
@@ -104,11 +104,11 @@ public:
bool load();
QMap tilesetCache;
- Tileset* loadTileset(QString, Tileset *tileset = nullptr);
- Tileset* getTileset(QString, bool forceLoad = false);
+ Tileset* getTileset(const QString&, bool forceLoad = false);
QStringList primaryTilesetLabels;
QStringList secondaryTilesetLabels;
QStringList tilesetLabelsOrdered;
+ QSet getPairedTilesetLabels(const Tileset *tileset) const;
bool readMapGroups();
void addNewMapGroup(const QString &groupName);
@@ -342,6 +342,7 @@ private:
void resetFileCache();
void resetFileWatcher();
void logFileWatchStatus();
+ void cacheTileset(const QString &label, Tileset *tileset);
bool saveMapLayouts();
bool saveMapGroups();
diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h
index 17b823ce..f1edc04a 100644
--- a/include/ui/imageproviders.h
+++ b/include/ui/imageproviders.h
@@ -11,14 +11,14 @@ class Layout;
QImage getCollisionMetatileImage(Block);
QImage getCollisionMetatileImage(int, int);
-QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false);
-QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false);
-QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false);
-QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false);
+QImage getMetatileImage(uint16_t, const Layout*, bool useTruePalettes = false);
+QImage getMetatileImage(const Metatile*, const Layout*, bool useTruePalettes = false);
+QImage getMetatileImage(uint16_t, const Tileset*, const Tileset*, const QList& = {0,1,2}, const QList& = {}, bool useTruePalettes = false);
+QImage getMetatileImage(const Metatile*, const Tileset*, const Tileset*, const QList& = {0,1,2}, const QList& = {}, bool useTruePalettes = false);
-QImage getMetatileSheetImage(Layout *layout, int numMetatilesWIde, bool useTruePalettes = false);
-QImage getMetatileSheetImage(Tileset *primaryTileset,
- Tileset *secondaryTileset,
+QImage getMetatileSheetImage(const Layout *layout, int numMetatilesWIde, bool useTruePalettes = false);
+QImage getMetatileSheetImage(const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
uint16_t metatileIdStart,
uint16_t metatileIdEnd,
int numMetatilesWIde,
@@ -26,8 +26,8 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
const QList &layerOpacity = {},
const QSize &metatileSize = Metatile::pixelSize(),
bool useTruePalettes = false);
-QImage getMetatileSheetImage(Tileset *primaryTileset,
- Tileset *secondaryTileset,
+QImage getMetatileSheetImage(const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
int numMetatilesWide,
const QList &layerOrder,
const QList &layerOpacity = {},
@@ -35,10 +35,10 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
bool useTruePalettes = false);
-QImage getTileImage(uint16_t, Tileset*, Tileset*);
-QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false);
-QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette);
-QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset);
+QImage getTileImage(uint16_t, const Tileset*, const Tileset*);
+QImage getPalettedTileImage(uint16_t, const Tileset*, const Tileset*, int, bool useTruePalettes = false);
+QImage getColoredTileImage(uint16_t tileId, const Tileset *, const Tileset *, const QList &palette);
+QImage getGreyscaleTileImage(uint16_t tileId, const Tileset *, const Tileset *);
void flattenTo4bppImage(QImage * image);
diff --git a/include/ui/numericsorttableitem.h b/include/ui/numericsorttableitem.h
new file mode 100644
index 00000000..e33f48cb
--- /dev/null
+++ b/include/ui/numericsorttableitem.h
@@ -0,0 +1,20 @@
+#ifndef NUMERICSORTTABLEITEM_H
+#define NUMERICSORTTABLEITEM_H
+
+#include
+#include
+
+class NumericSortTableItem : public QTableWidgetItem
+{
+public:
+ explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {};
+
+protected:
+ virtual bool operator<(const QTableWidgetItem &other) const override {
+ QCollator collator;
+ collator.setNumericMode(true);
+ return collator.compare(text(), other.text()) < 0;
+ }
+};
+
+#endif // NUMERICSORTTABLEITEM_H
diff --git a/include/ui/palettecolorsearch.h b/include/ui/palettecolorsearch.h
new file mode 100644
index 00000000..967f6efa
--- /dev/null
+++ b/include/ui/palettecolorsearch.h
@@ -0,0 +1,67 @@
+#ifndef PALETTECOLORSEARCH_H
+#define PALETTECOLORSEARCH_H
+
+#include
+#include
+#include
+
+class Tileset;
+class Project;
+
+namespace Ui {
+class PaletteColorSearch;
+}
+
+class PaletteColorSearch : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit PaletteColorSearch(Project *project,
+ const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
+ QWidget *parent = nullptr);
+ ~PaletteColorSearch();
+
+ void setPaletteId(int paletteId);
+ int currentPaletteId() const;
+
+ void setColorId(int colorId);
+ int currentColorId() const;
+
+ void setTilesets(const Tileset *primaryTileset, const Tileset *secondaryTileset);
+ const Tileset* currentTileset() const;
+
+signals:
+ void metatileSelected(uint16_t metatileId);
+ void paletteIdChanged(int paletteId);
+
+private:
+ struct RowData {
+ QString tilesetName;
+ QString pairedTilesetName;
+ QString metatileId;
+ QIcon metatileIcon;
+ };
+
+ enum ResultsColumn {
+ TilesetName,
+ Metatile,
+ };
+
+ Ui::PaletteColorSearch *ui;
+ Project *m_project;
+ const Tileset *m_primaryTileset;
+ const Tileset *m_secondaryTileset;
+
+ QMap> m_resultsCache;
+
+ void addTableEntry(const RowData &rowData);
+ QList search(int colorId) const;
+ QList search(int colorId, const Tileset *tileset, const Tileset *pairedTileset) const;
+ void refresh();
+ void updateResults();
+ void cellDoubleClicked(int row, int col);
+};
+
+#endif // PALETTECOLORSEARCH_H
diff --git a/include/ui/paletteeditor.h b/include/ui/paletteeditor.h
index 63581eb1..95fc80b8 100644
--- a/include/ui/paletteeditor.h
+++ b/include/ui/paletteeditor.h
@@ -2,10 +2,12 @@
#define PALETTEEDITOR_H
#include
+#include
#include "colorinputwidget.h"
#include "project.h"
#include "history.h"
+#include "palettecolorsearch.h"
namespace Ui {
class PaletteEditor;
@@ -24,25 +26,39 @@ class PaletteEditor : public QMainWindow {
public:
explicit PaletteEditor(Project*, Tileset*, Tileset*, int paletteId, QWidget *parent = nullptr);
~PaletteEditor();
+
void setPaletteId(int);
+ int currentPaletteId() const;
+
void setTilesets(Tileset*, Tileset*);
+ bool showingUnusedColors() const;
+
+signals:
+ void metatileSelected(uint16_t metatileId);
+
private:
Ui::PaletteEditor *ui;
- Project *project = nullptr;
- QList colorInputs;
-
+ Project *project;
Tileset *primaryTileset;
Tileset *secondaryTileset;
+ QList colorInputs;
QList> palettesHistory;
+ QMap> unusedColorCache;
+ QPointer colorSearchWindow;
- Tileset* getTileset(int paletteId);
+ Tileset* getTileset(int paletteId) const;
void refreshColorInputs();
+ void refreshPaletteId();
void commitEditHistory();
void commitEditHistory(int paletteId);
void restoreWindowState();
+ void invalidateCache();
void closeEvent(QCloseEvent*);
+ void setColorInputTitles(bool show);
+ QSet getUnusedColorIds();
+ void openColorSearch();
void setRgb(int index, QRgb rgb);
void setPalette(int paletteId, const QList &palette);
@@ -50,14 +66,13 @@ private:
void setBitDepth(int bits);
int bitDepth = 24;
- static const int numColors = 16;
+ static const int numColors = Tileset::numColorsPerPalette();
signals:
void closed();
void changedPaletteColor();
void changedPalette(int);
private slots:
- void on_spinBox_PaletteId_valueChanged(int arg1);
void on_actionUndo_triggered();
void on_actionRedo_triggered();
void on_actionImport_Palette_triggered();
diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h
index dc5b935a..1266da14 100644
--- a/include/ui/tileseteditor.h
+++ b/include/ui/tileseteditor.h
@@ -2,6 +2,7 @@
#define TILESETEDITOR_H
#include
+#include
#include "project.h"
#include "history.h"
#include "paletteeditor.h"
@@ -54,6 +55,9 @@ public:
QObjectList shortcutableObjects() const;
+ void setPaletteId(int paletteId);
+ int paletteId() const;
+
public slots:
void applyUserShortcuts();
void onSelectedMetatileChanged(uint16_t);
@@ -65,13 +69,9 @@ private slots:
void onHoveredTileChanged(const Tile&);
void onHoveredTileChanged(uint16_t);
void onHoveredTileCleared();
- void onSelectedTilesChanged();
void onMetatileLayerTileChanged(int, int);
void onMetatileLayerSelectionChanged(QPoint, int, int);
void onPaletteEditorChangedPaletteColor();
- void onPaletteEditorChangedPalette(int);
-
- void on_spinBox_paletteSelector_valueChanged(int arg1);
void on_actionChange_Metatiles_Count_triggered();
@@ -136,23 +136,20 @@ private:
void commitTerrainType();
void commitLayerType();
void setRawAttributesVisible(bool visible);
- void setXFlip(bool enabled);
- void setYFlip(bool enabled);
+ void refreshTileFlips();
+ void refreshPaletteId();
Ui::TilesetEditor *ui;
History metatileHistory;
TilesetEditorMetatileSelector *metatileSelector = nullptr;
TilesetEditorTileSelector *tileSelector = nullptr;
MetatileLayersItem *metatileLayersItem = nullptr;
- PaletteEditor *paletteEditor = nullptr;
+ QPointer paletteEditor = nullptr;
Project *project = nullptr;
Layout *layout = nullptr;
Metatile *metatile = nullptr;
Metatile *copiedMetatile = nullptr;
QString copiedMetatileLabel;
- int paletteId;
- bool tileXFlip;
- bool tileYFlip;
bool hasUnsavedChanges;
Tileset *primaryTileset = nullptr;
Tileset *secondaryTileset = nullptr;
diff --git a/include/ui/wildmonsearch.h b/include/ui/wildmonsearch.h
index ccf0fe94..4fe608b8 100644
--- a/include/ui/wildmonsearch.h
+++ b/include/ui/wildmonsearch.h
@@ -2,24 +2,11 @@
#define WILDMONSEARCH_H
#include
-#include
-#include
+
+#include "numericsorttableitem.h"
class Project;
-class NumericSortTableItem : public QTableWidgetItem
-{
-public:
- explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {};
-
-protected:
- virtual bool operator<(const QTableWidgetItem &other) const override {
- QCollator collator;
- collator.setNumericMode(true);
- return collator.compare(text(), other.text()) < 0;
- }
-};
-
namespace Ui {
class WildMonSearch;
}
diff --git a/porymap.pro b/porymap.pro
index 9c6327b7..6b2245c1 100644
--- a/porymap.pro
+++ b/porymap.pro
@@ -116,6 +116,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/ui/montabwidget.cpp \
src/ui/encountertablemodel.cpp \
src/ui/encountertabledelegates.cpp \
+ src/ui/palettecolorsearch.cpp \
src/ui/paletteeditor.cpp \
src/ui/selectablepixmapitem.cpp \
src/ui/tileseteditor.cpp \
@@ -234,6 +235,7 @@ HEADERS += include/core/advancemapparser.h \
include/ui/encountertablemodel.h \
include/ui/encountertabledelegates.h \
include/ui/adjustingstackedwidget.h \
+ include/ui/palettecolorsearch.h \
include/ui/paletteeditor.h \
include/ui/selectablepixmapitem.h \
include/ui/tileseteditor.h \
@@ -287,6 +289,7 @@ FORMS += forms/mainwindow.ui \
forms/prefabcreationdialog.ui \
forms/prefabframe.ui \
forms/tileseteditor.ui \
+ forms/palettecolorsearch.ui \
forms/paletteeditor.ui \
forms/regionmapeditor.ui \
forms/newmapdialog.ui \
diff --git a/src/config.cpp b/src/config.cpp
index d5c5b5c9..b53f171b 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -346,6 +346,7 @@ void PorymapConfig::reset() {
this->showTilesetEditorLayerGrid = true;
this->showTilesetEditorDivider = false;
this->showTilesetEditorRawAttributes = false;
+ this->showPaletteEditorUnusedColors = false;
this->monitorFiles = true;
this->tilesetCheckerboardFill = true;
this->newMapHeaderSectionExpanded = false;
@@ -469,6 +470,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
this->showTilesetEditorDivider = getConfigBool(key, value);
} else if (key == "show_tileset_editor_raw_attributes") {
this->showTilesetEditorRawAttributes = getConfigBool(key, value);
+ } else if (key == "show_palette_editor_unused_colors") {
+ this->showPaletteEditorUnusedColors = getConfigBool(key, value);
} else if (key == "monitor_files") {
this->monitorFiles = getConfigBool(key, value);
} else if (key == "tileset_checkerboard_fill") {
@@ -609,6 +612,7 @@ QMap PorymapConfig::getKeyValueMap() {
map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0");
map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0");
map.insert("show_tileset_editor_raw_attributes", this->showTilesetEditorRawAttributes ? "1" : "0");
+ map.insert("show_palette_editor_unused_colors", this->showPaletteEditorUnusedColors ? "1" : "0");
map.insert("monitor_files", this->monitorFiles ? "1" : "0");
map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0");
map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0");
diff --git a/src/core/events.cpp b/src/core/events.cpp
index a4ab33a9..58a2b997 100644
--- a/src/core/events.cpp
+++ b/src/core/events.cpp
@@ -3,6 +3,7 @@
#include "eventframes.h"
#include "project.h"
#include "config.h"
+#include "metatile.h"
Event* Event::create(Event::Type type) {
switch (type) {
@@ -23,6 +24,14 @@ Event::~Event() {
delete this->eventFrame;
}
+int Event::getPixelX() const {
+ return (this->x * Metatile::pixelWidth()) - qMax(0, (this->pixmap.width() - Metatile::pixelWidth()) / 2);
+}
+
+int Event::getPixelY() const {
+ return (this->y * Metatile::pixelHeight()) - qMax(0, this->pixmap.height() - Metatile::pixelHeight());
+}
+
EventFrame *Event::getEventFrame() {
if (!this->eventFrame) createEventFrame();
return this->eventFrame;
diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp
index f6c0d382..00fff2ba 100644
--- a/src/core/metatile.cpp
+++ b/src/core/metatile.cpp
@@ -48,7 +48,7 @@ QString Metatile::getMetatileIdString(uint16_t metatileId) {
return Util::toHexString(metatileId, numMetatileIdChars);
};
-QString Metatile::getMetatileIdStrings(const QList metatileIds) {
+QString Metatile::getMetatileIdStrings(const QList &metatileIds) {
QStringList metatiles;
for (auto metatileId : metatileIds)
metatiles << Metatile::getMetatileIdString(metatileId);
diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp
index 5f7ac20b..d76b0138 100644
--- a/src/core/tileset.cpp
+++ b/src/core/tileset.cpp
@@ -8,6 +8,7 @@
#include
#include
+#include
Tileset::Tileset(const Tileset &other)
@@ -110,7 +111,26 @@ int Tileset::maxTiles() const {
return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary();
}
+Tileset* Tileset::getPaletteTileset(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset) {
+ return const_cast(getPaletteTileset(paletteId, static_cast(primaryTileset), static_cast(secondaryTileset)));
+}
+
+const Tileset* Tileset::getPaletteTileset(int paletteId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
+ if (paletteId < Project::getNumPalettesPrimary()) {
+ return primaryTileset;
+ } else if (paletteId < Project::getNumPalettesTotal()) {
+ return secondaryTileset;
+ } else {
+ return nullptr;
+ }
+}
+
Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
+ return const_cast(getTileTileset(tileId, static_cast(primaryTileset), static_cast(secondaryTileset)));
+}
+
+// Get the tileset *expected* to contain the given 'tileId'. Note that this does not mean the tile actually exists in that tileset.
+const Tileset* Tileset::getTileTileset(int tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
if (tileId < Project::getNumTilesPrimary()) {
return primaryTileset;
} else if (tileId < Project::getNumTilesTotal()) {
@@ -121,6 +141,11 @@ Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *s
}
Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
+ return const_cast(getMetatileTileset(metatileId, static_cast(primaryTileset), static_cast(secondaryTileset)));
+}
+
+// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset.
+const Tileset* Tileset::getMetatileTileset(int metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
if (metatileId < Project::getNumMetatilesPrimary()) {
return primaryTileset;
} else if (metatileId < Project::getNumMetatilesTotal()) {
@@ -131,7 +156,11 @@ Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Ti
}
Metatile* Tileset::getMetatile(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
- Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset);
+ return const_cast(getMetatile(metatileId, static_cast(primaryTileset), static_cast(secondaryTileset)));
+}
+
+const Metatile* Tileset::getMetatile(int metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
+ const Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset);
if (!tileset) {
return nullptr;
}
@@ -220,12 +249,12 @@ QString Tileset::getMetatileLabelPrefix(const QString &name)
return QString("%1%2_").arg(labelPrefix).arg(Tileset::stripPrefix(name));
}
-bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
+bool Tileset::metatileIsValid(uint16_t metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
return (primaryTileset && primaryTileset->contains(metatileId))
|| (secondaryTileset && secondaryTileset->contains(metatileId));
}
-QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) {
+QList> Tileset::getBlockPalettes(const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) {
QList> palettes;
QList> primaryPalettes;
@@ -247,9 +276,9 @@ QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *s
return palettes;
}
-QList Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) {
+QList Tileset::getPalette(int paletteId, const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) {
QList paletteTable;
- Tileset *tileset = paletteId < Project::getNumPalettesPrimary()
+ const Tileset *tileset = paletteId < Project::getNumPalettesPrimary()
? primaryTileset
: secondaryTileset;
if (!tileset) {
@@ -334,7 +363,7 @@ bool Tileset::appendToGraphics(const QString &filepath, const QString &friendlyN
dataString.append(QString("\t.incbin \"%1\"\n").arg(tilesPath));
} else {
// Append to C file
- dataString.append(QString("const u16 gTilesetPalettes_%1[][16] =\n{\n").arg(friendlyName));
+ dataString.append(QString("const u16 gTilesetPalettes_%1[][%2] =\n{\n").arg(friendlyName).arg(Tileset::numColorsPerPalette()));
for (int i = 0; i < Project::getNumPalettesTotal(); i++)
dataString.append(QString(" INCBIN_U16(\"%1%2%3\"),\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0')).arg(palettesExt));
dataString.append("};\n");
@@ -546,13 +575,13 @@ bool Tileset::loadTilesImage(QImage *importedImage) {
return false;
}
- // Validate image contains 16 colors.
+ // Validate the number of colors in the image.
int colorCount = image.colorCount();
- if (colorCount > 16) {
+ if (colorCount > Tileset::numColorsPerPalette()) {
flattenTo4bppImage(&image);
- } else if (colorCount < 16) {
+ } else if (colorCount < Tileset::numColorsPerPalette()) {
QVector colorTable = image.colorTable();
- for (int i = colorTable.length(); i < 16; i++) {
+ for (int i = colorTable.length(); i < Tileset::numColorsPerPalette(); i++) {
colorTable.append(0);
}
image.setColorTable(colorTable);
@@ -616,8 +645,9 @@ bool Tileset::loadPalettes() {
// Either the palette failed to load, or no palette exists.
// We expect tilesets to have a certain number of palettes,
// so fill this palette with dummy colors.
- for (int j = 0; j < 16; j++) {
- palette.append(qRgb(j * 16, j * 16, j * 16));
+ for (int j = 0; j < Tileset::numColorsPerPalette(); j++) {
+ int colorComponent = j * (256 / Tileset::numColorsPerPalette());
+ palette.append(qRgb(colorComponent, colorComponent, colorComponent));
}
}
this->palettes.append(palette);
@@ -630,7 +660,7 @@ bool Tileset::savePalettes() {
bool success = true;
int numPalettes = qMin(this->palettePaths.length(), this->palettes.length());
for (int i = 0; i < numPalettes; i++) {
- if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16))
+ if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, Tileset::numColorsPerPalette()))
success = false;
}
return success;
@@ -658,3 +688,85 @@ bool Tileset::save() {
QString Tileset::stripPrefix(const QString &fullName) {
return QString(fullName).replace(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix), "");
}
+
+// Find which of the specified color IDs in 'searchColors' are not used by any of this tileset's metatiles.
+// The 'pairedTileset' may be used to get the tile images for any tiles that don't belong to this tileset.
+// If 'searchColors' is empty, it will for search for all unused colors.
+QSet Tileset::getUnusedColorIds(int paletteId, const Tileset *pairedTileset, const QSet &searchColors) const {
+ QSet unusedColors = searchColors;
+ if (unusedColors.isEmpty()) {
+ // Search for all colors
+ for (int i = 0; i < Tileset::numColorsPerPalette(); i++) {
+ unusedColors.insert(i);
+ }
+ }
+ const Tileset *primaryTileset = this->is_secondary ? pairedTileset : this;
+ const Tileset *secondaryTileset = this->is_secondary ? this : pairedTileset;
+ QSet seenTileIds;
+ for (const auto &metatile : m_metatiles)
+ for (const auto &tile : metatile->tiles) {
+ if (tile.palette != paletteId)
+ continue;
+
+ // Save time by ignoring tiles we've already inspected.
+ if (seenTileIds.contains(tile.tileId))
+ continue;
+ seenTileIds.insert(tile.tileId);
+
+ QImage image = getTileImage(tile.tileId, primaryTileset, secondaryTileset);
+ if (image.isNull() || image.sizeInBytes() < Tile::numPixels())
+ continue;
+
+ const uchar * pixels = image.constBits();
+ for (int i = 0; i < Tile::numPixels(); i++) {
+ auto it = unusedColors.constFind(pixels[i]);
+ if (it != unusedColors.constEnd()) {
+ unusedColors.erase(it);
+ if (unusedColors.isEmpty()) {
+ return {};
+ }
+ }
+ }
+ }
+ return unusedColors;
+}
+
+// Returns the list of metatile IDs representing all the metatiles in this tileset that use the specified color ID.
+QList Tileset::findMetatilesUsingColor(int paletteId, int colorId, const Tileset *pairedTileset) const {
+ const Tileset *primaryTileset = this->is_secondary ? pairedTileset : this;
+ const Tileset *secondaryTileset = this->is_secondary ? this : pairedTileset;
+ QSet metatileIdSet;
+ QHash tileContainsColor;
+ uint16_t metatileIdBase = firstMetatileId();
+ for (int i = 0; i < m_metatiles.length(); i++) {
+ uint16_t metatileId = i + metatileIdBase;
+ for (const auto &tile : m_metatiles.at(i)->tiles) {
+ if (tile.palette != paletteId)
+ continue;
+
+ // Save time on tiles we've already inspected by getting the cached result.
+ auto tileIt = tileContainsColor.constFind(tile.tileId);
+ if (tileIt != tileContainsColor.constEnd()) {
+ if (tileIt.value()) metatileIdSet.insert(metatileId);
+ continue;
+ }
+ tileContainsColor[tile.tileId] = false;
+
+ QImage image = getTileImage(tile.tileId, primaryTileset, secondaryTileset);
+ if (image.isNull() || image.sizeInBytes() < Tile::numPixels())
+ continue;
+
+ const uchar * pixels = image.constBits();
+ for (int j = 0; j < Tile::numPixels(); j++) {
+ if (pixels[j] == colorId) {
+ metatileIdSet.insert(metatileId);
+ tileContainsColor[tile.tileId] = true;
+ break;
+ }
+ }
+ }
+ }
+ QList metatileIds(metatileIdSet.constBegin(), metatileIdSet.constEnd());
+ std::sort(metatileIds.begin(), metatileIds.end());
+ return metatileIds;
+}
diff --git a/src/core/utility.cpp b/src/core/utility.cpp
index ff06838f..92cd1a4a 100644
--- a/src/core/utility.cpp
+++ b/src/core/utility.cpp
@@ -112,3 +112,16 @@ void Util::setErrorStylesheet(QLineEdit *lineEdit, bool isError) {
static const QString stylesheet = QStringLiteral("QLineEdit { background-color: rgba(255, 0, 0, 25%) }");
lineEdit->setStyleSheet(isError ? stylesheet : "");
}
+
+void Util::show(QWidget *w) {
+ if (!w) return;
+
+ if (!w->isVisible()) {
+ w->show();
+ } else if (w->isMinimized()) {
+ w->showNormal();
+ } else {
+ w->raise();
+ w->activateWindow();
+ }
+}
diff --git a/src/editor.cpp b/src/editor.cpp
index 5f1dfe0f..b4f97576 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -2060,12 +2060,6 @@ void Editor::updateCustomMapAttributes()
map->modify();
}
-Tileset* Editor::getCurrentMapPrimaryTileset()
-{
- QString tilesetLabel = this->layout->tileset_primary_label;
- return project->getTileset(tilesetLabel);
-}
-
void Editor::redrawAllEvents() {
if (this->map) redrawEvents(this->map->getEvents());
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index c191ea07..ac7c2385 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -345,7 +345,7 @@ void MainWindow::checkForUpdates(bool requestedByUser) {
if (requestedByUser) {
- openSubWindow(this->updatePromoter);
+ Util::show(this->updatePromoter);
} else {
// This is an automatic update check. Only run if we haven't done one in the last 5 minutes
QDateTime lastCheck = porymapConfig.lastUpdateCheckTime;
@@ -419,7 +419,7 @@ void MainWindow::initEditor() {
}
void MainWindow::openEditHistory() {
- openSubWindow(this->undoView);
+ Util::show(this->undoView);
}
void MainWindow::initMiscHeapObjects() {
@@ -936,19 +936,6 @@ void MainWindow::refreshRecentProjectsMenu() {
clearAction->setEnabled(!recentProjects.isEmpty());
}
-void MainWindow::openSubWindow(QWidget * window) {
- if (!window) return;
-
- if (!window->isVisible()) {
- window->show();
- } else if (window->isMinimized()) {
- window->showNormal();
- } else {
- window->raise();
- window->activateWindow();
- }
-}
-
void MainWindow::showFileWatcherWarning() {
if (!porymapConfig.monitorFiles || !isProjectOpen())
return;
@@ -2266,7 +2253,7 @@ void MainWindow::on_actionGrid_Settings_triggered() {
connect(this->gridSettingsDialog, &GridSettingsDialog::changedGridSettings, this->editor, &Editor::updateMapGrid);
connect(this->gridSettingsDialog, &GridSettingsDialog::accepted, [this] { porymapConfig.gridSettings = this->editor->gridSettings; });
}
- openSubWindow(this->gridSettingsDialog);
+ Util::show(this->gridSettingsDialog);
}
void MainWindow::on_actionShortcuts_triggered()
@@ -2274,7 +2261,7 @@ void MainWindow::on_actionShortcuts_triggered()
if (!shortcutsEditor)
initShortcutsEditor();
- openSubWindow(shortcutsEditor);
+ Util::show(shortcutsEditor);
}
void MainWindow::initShortcutsEditor() {
@@ -2698,7 +2685,7 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) {
}
}
- openSubWindow(this->mapImageExporter);
+ Util::show(this->mapImageExporter);
}
void MainWindow::on_pushButton_AddConnection_clicked() {
@@ -2726,7 +2713,7 @@ void MainWindow::on_pushButton_SummaryChart_clicked() {
connect(this->editor, &Editor::wildMonTableClosed, this->wildMonChart, &WildMonChart::clearTable);
connect(this->editor, &Editor::wildMonTableEdited, this->wildMonChart, &WildMonChart::refresh);
}
- openSubWindow(this->wildMonChart);
+ Util::show(this->wildMonChart);
}
void MainWindow::on_toolButton_WildMonSearch_clicked() {
@@ -2735,7 +2722,7 @@ void MainWindow::on_toolButton_WildMonSearch_clicked() {
connect(this->wildMonSearch, &WildMonSearch::openWildMonTableRequested, this, &MainWindow::openWildMonTable);
connect(this->editor, &Editor::wildMonTableEdited, this->wildMonSearch, &WildMonSearch::refresh);
}
- openSubWindow(this->wildMonSearch);
+ Util::show(this->wildMonSearch);
}
void MainWindow::openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName) {
@@ -2843,7 +2830,7 @@ void MainWindow::on_actionTileset_Editor_triggered()
initTilesetEditor();
}
- openSubWindow(this->tilesetEditor);
+ Util::show(this->tilesetEditor);
MetatileSelection selection = this->editor->metatile_selector_item->getMetatileSelection();
if (!selection.metatileItems.isEmpty()) {
@@ -2895,7 +2882,7 @@ void MainWindow::on_actionAbout_Porymap_triggered()
{
if (!this->aboutWindow)
this->aboutWindow = new AboutPorymap(this);
- openSubWindow(this->aboutWindow);
+ Util::show(this->aboutWindow);
}
void MainWindow::on_actionOpen_Log_File_triggered() {
@@ -2927,7 +2914,7 @@ void MainWindow::on_actionPreferences_triggered() {
connect(preferenceEditor, &PreferenceEditor::reloadProjectRequested, this, &MainWindow::on_action_Reload_Project_triggered);
}
- openSubWindow(preferenceEditor);
+ Util::show(preferenceEditor);
}
void MainWindow::togglePreferenceSpecificUi() {
@@ -2944,7 +2931,7 @@ void MainWindow::openProjectSettingsEditor(int tab) {
this, &MainWindow::on_action_Reload_Project_triggered);
}
this->projectSettingsEditor->setTab(tab);
- openSubWindow(this->projectSettingsEditor);
+ Util::show(this->projectSettingsEditor);
}
void MainWindow::on_actionProject_Settings_triggered() {
@@ -2982,7 +2969,7 @@ void MainWindow::on_actionCustom_Scripts_triggered() {
if (!this->customScriptsEditor)
initCustomScriptsEditor();
- openSubWindow(this->customScriptsEditor);
+ Util::show(this->customScriptsEditor);
}
void MainWindow::initCustomScriptsEditor() {
@@ -3062,7 +3049,7 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() {
}
}
- openSubWindow(this->regionMapEditor);
+ Util::show(this->regionMapEditor);
}
void MainWindow::on_pushButton_CreatePrefab_clicked() {
@@ -3120,7 +3107,7 @@ bool MainWindow::closeSupplementaryWindows() {
if (widget != this && widget->isWindow()) {
// Make sure the window is raised and activated before closing in case it has a confirmation prompt.
if (widget->isVisible()) {
- openSubWindow(widget);
+ Util::show(widget);
}
if (!widget->close()) {
QString message = QStringLiteral("Aborted project close");
diff --git a/src/project.cpp b/src/project.cpp
index b76821f3..9c5fd9af 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -186,6 +186,8 @@ bool Project::load() {
this->parser.setUpdatesSplashScreen(true);
resetFileWatcher();
resetFileCache();
+ QPixmapCache::clear();
+
this->disabledSettingsNames.clear();
bool success = readGlobalConstants()
&& readMapLayouts()
@@ -273,6 +275,17 @@ void Project::clearTilesetCache() {
this->tilesetCache.clear();
}
+void Project::cacheTileset(const QString &name, Tileset *tileset) {
+ auto it = this->tilesetCache.constFind(name);
+ if (it != this->tilesetCache.constEnd() && it.value() && tileset != it.value()) {
+ // Callers of this function should ensure this doesn't happen,
+ // but in case it does we should avoid leaking memory.
+ logWarn(QString("New tileset %1 overwrote existing tileset.").arg(name));
+ delete it.value();
+ }
+ this->tilesetCache.insert(name, tileset);
+}
+
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.
@@ -1180,7 +1193,21 @@ bool Project::loadLayoutTilesets(Layout *layout) {
return layout->tileset_primary && layout->tileset_secondary;
}
-Tileset* Project::loadTileset(QString label, Tileset *tileset) {
+Tileset* Project::getTileset(const QString &label, bool forceLoad) {
+ Tileset *tileset = nullptr;
+
+ auto it = this->tilesetCache.constFind(label);
+ if (it != this->tilesetCache.constEnd()) {
+ tileset = it.value();
+ if (!forceLoad) {
+ return tileset;
+ }
+ } else {
+ // Create a cache entry even if we don't end up loading the tileset successfully.
+ // This will prevent repeated file reads if the tileset fails to load.
+ cacheTileset(label, nullptr);
+ }
+
auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets);
if (this->usingAsmTilesets) {
// Read asm tileset header. Backwards compatibility
@@ -1225,7 +1252,7 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) {
return nullptr;
}
- tilesetCache.insert(label, tileset);
+ cacheTileset(tileset->name, tileset);
return tileset;
}
@@ -1517,7 +1544,7 @@ void Project::readTilesetPaths(Tileset* tileset) {
tileset->metatile_attrs_path = defaultPath + "/metatile_attributes.bin";
if (tileset->palettePaths.isEmpty()) {
QString palettes_dir_path = defaultPath + "/palettes/";
- for (int i = 0; i < 16; i++) {
+ for (int i = 0; i < Tileset::maxPalettes(); i++) {
tileset->palettePaths.append(palettes_dir_path + QString("%1").arg(i, 2, 10, QLatin1Char('0')) + ".pal");
}
}
@@ -1579,9 +1606,9 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
}
// Create default palettes
- for(int i = 0; i < 16; ++i) {
+ for(int i = 0; i < Tileset::maxPalettes(); ++i) {
QList currentPal;
- for(int i = 0; i < 16;++i) {
+ for(int i = 0; i < Tileset::numColorsPerPalette();++i) {
currentPal.append(qRgb(0,0,0));
}
tileset->palettes.append(currentPal);
@@ -1617,15 +1644,14 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles));
}
ignoreWatchedFilesTemporarily({headersFilepath, graphicsFilepath, metatilesFilepath});
- name = Tileset::stripPrefix(name);
- tileset->appendToHeaders(headersFilepath, name, this->usingAsmTilesets);
- tileset->appendToGraphics(graphicsFilepath, name, this->usingAsmTilesets);
- tileset->appendToMetatiles(metatilesFilepath, name, this->usingAsmTilesets);
+ QString baseName = Tileset::stripPrefix(name);
+ tileset->appendToHeaders(headersFilepath, baseName, this->usingAsmTilesets);
+ tileset->appendToGraphics(graphicsFilepath, baseName, this->usingAsmTilesets);
+ tileset->appendToMetatiles(metatilesFilepath, baseName, this->usingAsmTilesets);
tileset->save();
- this->tilesetCache.insert(tileset->name, tileset);
-
+ cacheTileset(tileset->name, tileset);
emit tilesetCreated(tileset);
return tileset;
}
@@ -1680,20 +1706,6 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) {
}
}
-Tileset* Project::getTileset(QString label, bool forceLoad) {
- Tileset *existingTileset = nullptr;
- if (tilesetCache.contains(label)) {
- existingTileset = tilesetCache.value(label);
- }
-
- if (existingTileset && !forceLoad) {
- return existingTileset;
- } else {
- Tileset *tileset = loadTileset(label, existingTileset);
- return tileset;
- }
-}
-
bool Project::saveTextFile(const QString &path, const QString &text) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
@@ -2266,6 +2278,7 @@ bool Project::readTilesetLabels() {
this->primaryTilesetLabels.clear();
this->secondaryTilesetLabels.clear();
this->tilesetLabelsOrdered.clear();
+ clearTilesetCache();
QString filename = projectConfig.getFilePath(ProjectFilePath::tilesets_headers);
QFileInfo fileInfo(this->root + "/" + filename);
@@ -2347,7 +2360,7 @@ bool Project::readFieldmapProperties() {
logWarn(QString("Value for '%1' not found. Using default (%2) instead.").arg(name).arg(*dest));
}
};
- loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter.
+ loadDefine(numPalsTotalName, &Project::num_pals_total, 2, Tileset::maxPalettes());
loadDefine(numTilesTotalName, &Project::num_tiles_total, 2, 1024); // 1024 is fixed because we store tile IDs in a 10-bit field.
loadDefine(numPalsPrimaryName, &Project::num_pals_primary, 1, Project::num_pals_total - 1);
loadDefine(numTilesPrimaryName, &Project::num_tiles_primary, 1, Project::num_tiles_total - 1);
@@ -3518,3 +3531,18 @@ bool Project::hasUnsavedChanges() {
}
return false;
}
+
+// Searches the project's map layouts to find the names of the tilesets that the provided tileset gets paired with.
+QSet Project::getPairedTilesetLabels(const Tileset *tileset) const {
+ QSet pairedLabels;
+ for (const auto &layout : this->mapLayouts) {
+ if (tileset->is_secondary) {
+ if (layout->tileset_secondary_label == tileset->name) {
+ pairedLabels.insert(layout->tileset_primary_label);
+ }
+ } else if (layout->tileset_primary_label == tileset->name) {
+ pairedLabels.insert(layout->tileset_secondary_label);
+ }
+ }
+ return pairedLabels;
+}
diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp
index a1730791..472dd23a 100644
--- a/src/scriptapi/apimap.cpp
+++ b/src/scriptapi/apimap.cpp
@@ -346,10 +346,7 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList= tileset->palettes.size())
return;
- if (colors.size() != 16)
- return;
-
- for (int i = 0; i < 16; i++) {
+ for (int i = 0; i < qMin(colors.length(), Tileset::numColorsPerPalette()); i++) {
if (colors[i].size() != 3)
continue;
tileset->palettes[paletteIndex][i] = qRgb(colors[i][0], colors[i][1], colors[i][2]);
@@ -457,10 +454,7 @@ void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QL
return;
if (paletteIndex >= tileset->palettePreviews.size())
return;
- if (colors.size() != 16)
- return;
-
- for (int i = 0; i < 16; i++) {
+ for (int i = 0; i < qMin(colors.length(), Tileset::numColorsPerPalette()); i++) {
if (colors[i].size() != 3)
continue;
tileset->palettePreviews[paletteIndex][i] = qRgb(colors[i][0], colors[i][1], colors[i][2]);
@@ -798,14 +792,13 @@ QJSValue MainWindow::getTilePixels(int tileId) {
if (tileId < 0 || !this->editor || !this->editor->layout)
return QJSValue();
- const int numPixels = Tile::pixelWidth() * Tile::pixelHeight();
QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary);
- if (tileImage.isNull() || tileImage.sizeInBytes() < numPixels)
+ if (tileImage.isNull() || tileImage.sizeInBytes() < Tile::numPixels())
return QJSValue();
const uchar * pixels = tileImage.constBits();
- QJSValue pixelArray = Scripting::getEngine()->newArray(numPixels);
- for (int i = 0; i < numPixels; i++) {
+ QJSValue pixelArray = Scripting::getEngine()->newArray(Tile::numPixels());
+ for (int i = 0; i < Tile::numPixels(); i++) {
pixelArray.setProperty(i, pixels[i]);
}
return pixelArray;
diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp
index ce233066..5efa1e91 100644
--- a/src/ui/imageproviders.cpp
+++ b/src/ui/imageproviders.cpp
@@ -12,14 +12,14 @@ QImage getCollisionMetatileImage(int collision, int elevation) {
return image ? *image : QImage();
}
-QImage getMetatileImage(uint16_t metatileId, Layout *layout, bool useTruePalettes) {
+QImage getMetatileImage(uint16_t metatileId, const Layout *layout, bool useTruePalettes) {
Metatile* metatile = Tileset::getMetatile(metatileId,
layout ? layout->tileset_primary : nullptr,
layout ? layout->tileset_secondary : nullptr);
return getMetatileImage(metatile, layout, useTruePalettes);
}
-QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes) {
+QImage getMetatileImage(const Metatile *metatile, const Layout *layout, bool useTruePalettes) {
if (!layout) {
return getMetatileImage(metatile, nullptr, nullptr, {}, {}, useTruePalettes);
}
@@ -33,8 +33,8 @@ QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes
QImage getMetatileImage(
uint16_t metatileId,
- Tileset *primaryTileset,
- Tileset *secondaryTileset,
+ const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
const QList &layerOrder,
const QList &layerOpacity,
bool useTruePalettes)
@@ -54,9 +54,9 @@ QColor getInvalidImageColor() {
}
QImage getMetatileImage(
- Metatile *metatile,
- Tileset *primaryTileset,
- Tileset *secondaryTileset,
+ const Metatile *metatile,
+ const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
const QList &layerOrder,
const QList &layerOpacity,
bool useTruePalettes)
@@ -141,12 +141,12 @@ QImage getMetatileImage(
return metatileImage;
}
-QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
- Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset);
+QImage getTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
+ const Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset);
return tileset ? tileset->tileImage(tileId) : QImage();
}
-QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) {
+QImage getColoredTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset, const QList &palette) {
QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset);
if (tileImage.isNull()) {
// Some tiles specify tile IDs or palette IDs that are outside the valid range.
@@ -155,19 +155,19 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se
tileImage = QImage(Tile::pixelSize(), QImage::Format_RGBA8888);
tileImage.fill(getInvalidImageColor());
} else {
- for (int i = 0; i < 16; i++) {
+ for (int i = 0; i < Tileset::numColorsPerPalette(); i++) {
tileImage.setColor(i, palette.value(i, getInvalidImageColor().rgb()));
}
}
return tileImage;
}
-QImage getPalettedTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, bool useTruePalettes) {
+QImage getPalettedTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset, int paletteId, bool useTruePalettes) {
QList palette = Tileset::getPalette(paletteId, primaryTileset, secondaryTileset, useTruePalettes);
return getColoredTileImage(tileId, primaryTileset, secondaryTileset, palette);
}
-QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
+QImage getGreyscaleTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
return getColoredTileImage(tileId, primaryTileset, secondaryTileset, greyscalePalette);
}
@@ -181,8 +181,8 @@ void flattenTo4bppImage(QImage * image) {
}
// Constructs a grid image of the metatiles in the specified ID range.
-QImage getMetatileSheetImage(Tileset *primaryTileset,
- Tileset *secondaryTileset,
+QImage getMetatileSheetImage(const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
uint16_t metatileIdStart,
uint16_t metatileIdEnd,
int numMetatilesWide,
@@ -219,15 +219,15 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
// Constructs a grid image of the metatiles in the primary and secondary tileset,
// rounding as necessary to keep the two tilesets on separate rows.
// The unused metatiles (if any) between the primary and secondary tilesets are skipped.
-QImage getMetatileSheetImage(Tileset *primaryTileset,
- Tileset *secondaryTileset,
+QImage getMetatileSheetImage(const Tileset *primaryTileset,
+ const Tileset *secondaryTileset,
int numMetatilesWide,
const QList &layerOrder,
const QList &layerOpacity,
const QSize &metatileSize,
bool useTruePalettes)
{
- auto createSheetImage = [=](uint16_t start, Tileset *tileset) {
+ auto createSheetImage = [=](uint16_t start, const Tileset *tileset) {
uint16_t end = start;
if (tileset) {
if (tileset->numMetatiles() == 0)
@@ -259,7 +259,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
return image;
}
-QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) {
+QImage getMetatileSheetImage(const Layout *layout, int numMetatilesWide, bool useTruePalettes) {
if (!layout)
return QImage();
return getMetatileSheetImage(layout->tileset_primary,
diff --git a/src/ui/palettecolorsearch.cpp b/src/ui/palettecolorsearch.cpp
new file mode 100644
index 00000000..e1f47a90
--- /dev/null
+++ b/src/ui/palettecolorsearch.cpp
@@ -0,0 +1,207 @@
+#include "palettecolorsearch.h"
+#include "ui_palettecolorsearch.h"
+#include "project.h"
+#include "tileset.h"
+#include "imageproviders.h"
+#include "eventfilters.h"
+#include "log.h"
+#include "numericsorttableitem.h"
+
+enum ResultsDataRole {
+ PairedTilesetName = Qt::UserRole,
+};
+
+PaletteColorSearch::PaletteColorSearch(Project *project, const Tileset *primaryTileset, const Tileset *secondaryTileset, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::PaletteColorSearch),
+ m_project(project),
+ m_primaryTileset(primaryTileset),
+ m_secondaryTileset(secondaryTileset)
+{
+ setAttribute(Qt::WA_DeleteOnClose);
+ ui->setupUi(this);
+
+ ui->buttonBox->setVisible(isModal());
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
+
+ // Rather than try to keep track of metatile/tile changes that affect which colors are used,
+ // we'll just refresh when the window is activated.
+ ActiveWindowFilter *filter = new ActiveWindowFilter(this);
+ connect(filter, &ActiveWindowFilter::activated, this, &PaletteColorSearch::refresh);
+ this->installEventFilter(filter);
+
+ ui->spinBox_ColorId->setRange(0, Tileset::numColorsPerPalette() - 1);
+ connect(ui->spinBox_ColorId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::updateResults);
+
+ ui->spinBox_PaletteId->setRange(0, Project::getNumPalettesTotal() - 1);
+ connect(ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::updateResults);
+ connect(ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::paletteIdChanged);
+
+ // Set up table header
+ static const QStringList labels = {"Tileset", "Metatile"};
+ ui->table_Results->setHorizontalHeaderLabels(labels);
+ ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::TilesetName, QHeaderView::ResizeToContents);
+ ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Metatile, QHeaderView::Stretch);
+
+ // Table is read-only
+ ui->table_Results->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ ui->table_Results->setSelectionMode(QAbstractItemView::NoSelection);
+
+ connect(ui->table_Results, &QTableWidget::cellDoubleClicked, this, &PaletteColorSearch::cellDoubleClicked);
+}
+
+PaletteColorSearch::~PaletteColorSearch() {
+ delete ui;
+}
+
+void PaletteColorSearch::setPaletteId(int paletteId) {
+ ui->spinBox_PaletteId->setValue(paletteId);
+}
+
+int PaletteColorSearch::currentPaletteId() const {
+ return ui->spinBox_PaletteId->value();
+}
+
+void PaletteColorSearch::setColorId(int colorId) {
+ ui->spinBox_ColorId->setValue(colorId);
+}
+
+int PaletteColorSearch::currentColorId() const {
+ return ui->spinBox_ColorId->value();
+}
+
+void PaletteColorSearch::setTilesets(const Tileset *primaryTileset, const Tileset *secondaryTileset) {
+ m_primaryTileset = primaryTileset;
+ m_secondaryTileset = secondaryTileset;
+ refresh();
+}
+
+const Tileset* PaletteColorSearch::currentTileset() const {
+ return Tileset::getPaletteTileset(currentPaletteId(), m_primaryTileset, m_secondaryTileset);
+}
+
+void PaletteColorSearch::addTableEntry(const RowData &rowData) {
+ int row = ui->table_Results->rowCount();
+ ui->table_Results->insertRow(row);
+
+ auto tilesetNameItem = new NumericSortTableItem(rowData.tilesetName);
+ tilesetNameItem->setData(ResultsDataRole::PairedTilesetName, rowData.pairedTilesetName);
+
+ ui->table_Results->setItem(row, ResultsColumn::TilesetName, tilesetNameItem);
+ ui->table_Results->setItem(row, ResultsColumn::Metatile, new QTableWidgetItem(rowData.metatileIcon, rowData.metatileId));
+}
+
+QList PaletteColorSearch::search(int colorId) const {
+ QList results;
+
+ // Check our current tilesets for color usage.
+ results.append(search(colorId, m_primaryTileset, m_secondaryTileset));
+ results.append(search(colorId, m_secondaryTileset, m_primaryTileset));
+
+ // The current palette comes from either the primary or secondary tileset.
+ // We need to check all the other tilesets that are paired with the tileset that owns this palette.
+ const Tileset *paletteTileset = currentTileset();
+ QSet tilesetsToSearch = m_project->getPairedTilesetLabels(paletteTileset);
+
+ // We exclude the currently-loaded pair (we already checked them, and because they're being
+ // edited in the Tileset Editor they may differ from their copies saved in the layout).
+ tilesetsToSearch.remove(m_primaryTileset->name);
+ tilesetsToSearch.remove(m_secondaryTileset->name);
+
+ for (const auto &label : tilesetsToSearch) {
+ Tileset *searchTileset = m_project->getTileset(label);
+ if (searchTileset) {
+ results.append(search(colorId, searchTileset, paletteTileset));
+ }
+ }
+ return results;
+}
+
+QList PaletteColorSearch::search(int colorId, const Tileset *tileset, const Tileset *pairedTileset) const {
+ QList results;
+ QList metatileIds = tileset->findMetatilesUsingColor(currentPaletteId(), colorId, pairedTileset);
+ auto primaryTileset = tileset->is_secondary ? pairedTileset : tileset;
+ auto secondaryTileset = tileset->is_secondary ? tileset : pairedTileset;
+ for (const auto &metatileId : metatileIds) {
+ QImage metatileImage = getMetatileImage(metatileId, primaryTileset, secondaryTileset);
+ RowData rowData = {
+ .tilesetName = tileset->name,
+ .pairedTilesetName = pairedTileset->name,
+ .metatileId = Metatile::getMetatileIdString(metatileId),
+ .metatileIcon = QIcon(QPixmap::fromImage(metatileImage)),
+ };
+ results.append(rowData);
+ }
+ return results;
+}
+
+void PaletteColorSearch::refresh() {
+ m_resultsCache.clear();
+ updateResults();
+}
+
+void PaletteColorSearch::updateResults() {
+ const Tileset *tileset = currentTileset();
+ int paletteId = currentPaletteId();
+ int colorId = currentColorId();
+
+ // Update color icon
+ QRgb color = tileset->palettePreviews.value(paletteId).value(colorId);
+ ui->frame_Color->setStyleSheet(QString("background-color: rgb(%1, %2, %3);").arg(qRed(color)).arg(qGreen(color)).arg(qBlue(color)));
+
+ // Update title
+ ui->label_Title->setText(QString("Searching for usage of %1's palette %2.").arg(tileset->name).arg(paletteId));
+
+ // Update table
+ ui->table_Results->clearContents();
+ ui->table_Results->setRowCount(0);
+
+ QString cacheKey = QString("%1#%2").arg(paletteId).arg(colorId);
+ auto it = m_resultsCache.constFind(cacheKey);
+ bool inCache = (it != m_resultsCache.constEnd());
+ const QList results = inCache ? it.value() : search(colorId);
+
+ if (results.isEmpty()) {
+ static const RowData noResults = {
+ .tilesetName = QStringLiteral("This color is unused."),
+ .pairedTilesetName = "",
+ .metatileId = QStringLiteral("--"),
+ .metatileIcon = QIcon(),
+ };
+ addTableEntry(noResults);
+ } else {
+ for (const auto &entry : results) {
+ addTableEntry(entry);
+ }
+ }
+
+ ui->table_Results->sortByColumn(ResultsColumn::TilesetName, Qt::AscendingOrder);
+
+ if (!inCache) m_resultsCache.insert(cacheKey, results);
+}
+
+// Double-clicking row data selects the corresponding metatile in the Tileset Editor.
+void PaletteColorSearch::cellDoubleClicked(int row, int) {
+ auto tilesetNameItem = ui->table_Results->item(row, ResultsColumn::TilesetName);
+ auto metatileItem = ui->table_Results->item(row, ResultsColumn::Metatile);
+ if (!tilesetNameItem || !metatileItem)
+ return;
+
+ // The Tileset Editor (as of writing) has no way to change the selected tilesets independently of
+ // the main editor's layout, so if the metatile is not in the current tileset we do nothing.
+ // To compare the tileset names, rather than sort out which was the primary or secondary we
+ // just make sure it's the same set of names.
+ QSet currentTilesets;
+ currentTilesets.insert(m_primaryTileset->name);
+ currentTilesets.insert(m_secondaryTileset->name);
+
+ QSet metatileTilesets;
+ metatileTilesets.insert(tilesetNameItem->text());
+ metatileTilesets.insert(tilesetNameItem->data(ResultsDataRole::PairedTilesetName).toString());
+ if (currentTilesets != metatileTilesets)
+ return;
+
+ bool ok;
+ uint16_t metatileId = metatileItem->text().toUInt(&ok, 0);
+ if (ok) emit metatileSelected(metatileId);
+}
diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp
index 1078ecc6..571185d7 100644
--- a/src/ui/paletteeditor.cpp
+++ b/src/ui/paletteeditor.cpp
@@ -5,23 +5,23 @@
#include "log.h"
#include "filedialog.h"
#include "message.h"
+#include "eventfilters.h"
+#include "utility.h"
PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) :
QMainWindow(parent),
- ui(new Ui::PaletteEditor)
+ ui(new Ui::PaletteEditor),
+ project(project),
+ primaryTileset(primaryTileset),
+ secondaryTileset(secondaryTileset)
{
- this->project = project;
- this->primaryTileset = primaryTileset;
- this->secondaryTileset = secondaryTileset;
+ setAttribute(Qt::WA_DeleteOnClose);
this->ui->setupUi(this);
- this->ui->spinBox_PaletteId->setMinimum(0);
- this->ui->spinBox_PaletteId->setMaximum(Project::getNumPalettesTotal() - 1);
- this->colorInputs.clear();
const int numColorsPerRow = 4;
for (int i = 0; i < this->numColors; i++) {
- auto colorInput = new ColorInputWidget(QString("Color %1").arg(i));
+ auto colorInput = new ColorInputWidget;
connect(colorInput, &ColorInputWidget::colorChanged, [this, i](QRgb color) { setRgb(i, color); });
connect(colorInput, &ColorInputWidget::editingFinished, [this] { commitEditHistory(); });
this->colorInputs.append(colorInput);
@@ -42,23 +42,48 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
setBitDepth(bitDepth);
// Connect bit depth buttons
- connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(15); });
- connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(24); });
+ connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) setBitDepth(15); });
+ connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) setBitDepth(24); });
- this->setPaletteId(paletteId);
- this->commitEditHistory();
- this->restoreWindowState();
+ this->ui->actionShow_Unused_Colors->setChecked(porymapConfig.showPaletteEditorUnusedColors);
+ connect(this->ui->actionShow_Unused_Colors, &QAction::toggled, this, &PaletteEditor::setColorInputTitles);
+
+ connect(this->ui->toolButton_ColorSearch, &QToolButton::clicked, this, &PaletteEditor::openColorSearch);
+ connect(this->ui->actionFind_Color_Usage, &QAction::triggered, this, &PaletteEditor::openColorSearch);
+
+ // Rather than try to keep track of metatile/tile changes that affect which colors are used,
+ // we'll just refresh when the window is activated.
+ ActiveWindowFilter *filter = new ActiveWindowFilter(this);
+ connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::invalidateCache);
+ this->installEventFilter(filter);
+
+ this->ui->spinBox_PaletteId->setRange(0, Project::getNumPalettesTotal() - 1);
+ this->ui->spinBox_PaletteId->setValue(paletteId);
+ connect(this->ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteEditor::refreshPaletteId);
+ connect(this->ui->spinBox_PaletteId, QOverload::of(&QSpinBox::valueChanged), this, &PaletteEditor::changedPalette);
+
+ refreshPaletteId();
+ restoreWindowState();
}
-PaletteEditor::~PaletteEditor()
-{
+PaletteEditor::~PaletteEditor() {
delete ui;
}
-Tileset* PaletteEditor::getTileset(int paletteId) {
- return (paletteId < Project::getNumPalettesPrimary())
- ? this->primaryTileset
- : this->secondaryTileset;
+int PaletteEditor::currentPaletteId() const {
+ return ui->spinBox_PaletteId->value();
+}
+
+void PaletteEditor::setPaletteId(int paletteId) {
+ ui->spinBox_PaletteId->setValue(paletteId);
+}
+
+bool PaletteEditor::showingUnusedColors() const {
+ return ui->actionShow_Unused_Colors->isChecked();
+}
+
+Tileset* PaletteEditor::getTileset(int paletteId) const {
+ return Tileset::getPaletteTileset(paletteId, this->primaryTileset, this->secondaryTileset);
}
void PaletteEditor::setBitDepth(int bits) {
@@ -70,62 +95,63 @@ void PaletteEditor::setBitDepth(int bits) {
}
void PaletteEditor::setRgb(int colorIndex, QRgb rgb) {
- const int paletteId = this->ui->spinBox_PaletteId->value();
-
+ const int paletteId = currentPaletteId();
Tileset *tileset = getTileset(paletteId);
tileset->palettes[paletteId][colorIndex] = rgb;
tileset->palettePreviews[paletteId][colorIndex] = rgb;
-
emit changedPaletteColor();
}
void PaletteEditor::setPalette(int paletteId, const QList &palette) {
Tileset *tileset = getTileset(paletteId);
for (int i = 0; i < this->numColors; i++) {
- tileset->palettes[paletteId][i] = palette.at(i);
- tileset->palettePreviews[paletteId][i] = palette.at(i);
+ tileset->palettes[paletteId][i] = palette.value(i);
+ tileset->palettePreviews[paletteId][i] = palette.value(i);
}
refreshColorInputs();
emit changedPaletteColor();
}
void PaletteEditor::refreshColorInputs() {
- const int paletteId = ui->spinBox_PaletteId->value();
+ const int paletteId = currentPaletteId();
Tileset *tileset = getTileset(paletteId);
- for (int i = 0; i < this->numColors; i++) {
+ for (int i = 0; i < this->colorInputs.length(); i++) {
auto colorInput = this->colorInputs.at(i);
const QSignalBlocker b(colorInput);
- colorInput->setColor(tileset->palettes.at(paletteId).at(i));
+ colorInput->setColor(tileset->palettes.value(paletteId).value(i));
}
+ setColorInputTitles(showingUnusedColors());
}
-void PaletteEditor::setPaletteId(int paletteId) {
- const QSignalBlocker b(ui->spinBox_PaletteId);
- this->ui->spinBox_PaletteId->setValue(paletteId);
- this->refreshColorInputs();
+void PaletteEditor::refreshPaletteId() {
+ refreshColorInputs();
+
+ int paletteId = currentPaletteId();
+ if (!this->palettesHistory[paletteId].current()) {
+ commitEditHistory(paletteId);
+ }
+ if (this->colorSearchWindow) {
+ this->colorSearchWindow->setPaletteId(paletteId);
+ }
}
void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
- this->refreshColorInputs();
-}
-
-void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) {
- this->refreshColorInputs();
- if (!this->palettesHistory[paletteId].current()) {
- this->commitEditHistory(paletteId);
+ invalidateCache();
+ if (this->colorSearchWindow) {
+ this->colorSearchWindow->setTilesets(primaryTileset, secondaryTileset);
}
- emit this->changedPalette(paletteId);
+ refreshColorInputs();
}
void PaletteEditor::commitEditHistory() {
- commitEditHistory(ui->spinBox_PaletteId->value());
+ commitEditHistory(currentPaletteId());
}
void PaletteEditor::commitEditHistory(int paletteId) {
QList colors;
- for (int i = 0; i < this->numColors; i++) {
+ for (int i = 0; i < this->colorInputs.length(); i++) {
colors.append(this->colorInputs.at(i)->color());
}
PaletteHistoryItem *commit = new PaletteHistoryItem(colors);
@@ -135,28 +161,25 @@ void PaletteEditor::commitEditHistory(int paletteId) {
void PaletteEditor::restoreWindowState() {
logInfo("Restoring palette editor geometry from previous session.");
QMap geometry = porymapConfig.getPaletteEditorGeometry();
- this->restoreGeometry(geometry.value("palette_editor_geometry"));
- this->restoreState(geometry.value("palette_editor_state"));
+ restoreGeometry(geometry.value("palette_editor_geometry"));
+ restoreState(geometry.value("palette_editor_state"));
}
-void PaletteEditor::on_actionUndo_triggered()
-{
- int paletteId = this->ui->spinBox_PaletteId->value();
+void PaletteEditor::on_actionUndo_triggered() {
+ int paletteId = currentPaletteId();
PaletteHistoryItem *prev = this->palettesHistory[paletteId].back();
if (prev)
setPalette(paletteId, prev->colors);
}
-void PaletteEditor::on_actionRedo_triggered()
-{
- int paletteId = this->ui->spinBox_PaletteId->value();
+void PaletteEditor::on_actionRedo_triggered() {
+ int paletteId = currentPaletteId();
PaletteHistoryItem *next = this->palettesHistory[paletteId].next();
if (next)
setPalette(paletteId, next->colors);
}
-void PaletteEditor::on_actionImport_Palette_triggered()
-{
+void PaletteEditor::on_actionImport_Palette_triggered() {
QString filepath = FileDialog::getOpenFileName(this, "Import Tileset Palette", "", "Palette Files (*.pal *.act *tpl *gpl)");
if (filepath.isEmpty()) {
return;
@@ -171,14 +194,83 @@ void PaletteEditor::on_actionImport_Palette_triggered()
palette.append(0);
}
- const int paletteId = ui->spinBox_PaletteId->value();
+ const int paletteId = currentPaletteId();
setPalette(paletteId, palette);
commitEditHistory(paletteId);
}
+void PaletteEditor::openColorSearch() {
+ if (!this->colorSearchWindow) {
+ this->colorSearchWindow = new PaletteColorSearch(this->project, this->primaryTileset, this->secondaryTileset, this);
+ this->colorSearchWindow->setPaletteId(currentPaletteId());
+ connect(this->colorSearchWindow, &PaletteColorSearch::metatileSelected, this, &PaletteEditor::metatileSelected);
+ connect(this->colorSearchWindow, &PaletteColorSearch::paletteIdChanged, this, &PaletteEditor::setPaletteId);
+ }
+ Util::show(this->colorSearchWindow);
+}
+
+void PaletteEditor::invalidateCache() {
+ this->unusedColorCache.clear();
+ if (showingUnusedColors()) {
+ setColorInputTitles(true);
+ }
+}
+
+QSet PaletteEditor::getUnusedColorIds() {
+ const int paletteId = currentPaletteId();
+
+ if (this->unusedColorCache.contains(paletteId)) {
+ return this->unusedColorCache.value(paletteId);
+ }
+ this->unusedColorCache[paletteId] = {};
+
+ // Check our current tilesets for color usage.
+ QSet unusedColorIds = this->primaryTileset->getUnusedColorIds(paletteId, this->secondaryTileset);
+ if (unusedColorIds.isEmpty())
+ return {};
+ unusedColorIds = this->secondaryTileset->getUnusedColorIds(paletteId, this->primaryTileset, unusedColorIds);
+ if (unusedColorIds.isEmpty())
+ return {};
+
+ // The current palette comes from either the primary or secondary tileset.
+ // We need to check all the other tilesets that are paired with the tileset that owns this palette.
+ Tileset *paletteTileset = getTileset(paletteId);
+ QSet tilesetsToSearch = this->project->getPairedTilesetLabels(paletteTileset);
+
+ // We exclude the currently-loaded pair (we already checked them, and because they're being
+ // edited in the Tileset Editor they may differ from their copies saved in the layout).
+ tilesetsToSearch.remove(this->primaryTileset->name);
+ tilesetsToSearch.remove(this->secondaryTileset->name);
+
+ for (const auto &label : tilesetsToSearch) {
+ Tileset *searchTileset = this->project->getTileset(label);
+ if (!searchTileset) continue;
+ unusedColorIds = searchTileset->getUnusedColorIds(paletteId, paletteTileset, unusedColorIds);
+ if (unusedColorIds.isEmpty())
+ return {};
+ }
+
+ this->unusedColorCache[paletteId] = unusedColorIds;
+ return unusedColorIds;
+}
+
+void PaletteEditor::setColorInputTitles(bool showUnused) {
+ porymapConfig.showPaletteEditorUnusedColors = showUnused;
+
+ QSet unusedColorIds = showUnused ? getUnusedColorIds() : QSet();
+ ui->label_AllColorsUsed->setVisible(showUnused && unusedColorIds.isEmpty());
+ for (int i = 0; i < this->colorInputs.length(); i++) {
+ QString title = QString("Color %1").arg(i);
+ if (unusedColorIds.contains(i)) {
+ title.append(QStringLiteral(" (Unused)"));
+ }
+ this->colorInputs.at(i)->setTitle(title);
+ }
+}
+
void PaletteEditor::closeEvent(QCloseEvent*) {
porymapConfig.setPaletteEditorGeometry(
- this->saveGeometry(),
- this->saveState()
+ saveGeometry(),
+ saveState()
);
}
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index 4df24065..b676c9ee 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -26,14 +26,23 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
+ ui->spinBox_paletteSelector->setRange(0, Project::getNumPalettesTotal() - 1);
+
+ auto validator = new IdentifierValidator(this);
+ validator->setAllowEmpty(true);
+ ui->lineEdit_metatileLabel->setValidator(validator);
+
+ ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
+ ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
+
+ ActiveWindowFilter *filter = new ActiveWindowFilter(this);
+ connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
+ this->installEventFilter(filter);
+
setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
- connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip);
- connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip);
-
- this->tileXFlip = ui->checkBox_xFlip->isChecked();
- this->tileYFlip = ui->checkBox_yFlip->isChecked();
- this->paletteId = ui->spinBox_paletteSelector->value();
+ connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::refreshTileFlips);
+ connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::refreshTileFlips);
connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save);
@@ -51,19 +60,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
connect(ui->actionExport_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); });
- ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
- ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
-
- ui->spinBox_paletteSelector->setMinimum(0);
- ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
-
- auto validator = new IdentifierValidator(this);
- validator->setAllowEmpty(true);
- ui->lineEdit_metatileLabel->setValidator(validator);
-
- ActiveWindowFilter *filter = new ActiveWindowFilter(this);
- connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
- this->installEventFilter(filter);
+ connect(ui->spinBox_paletteSelector, QOverload::of(&QSpinBox::valueChanged), this, &TilesetEditor::refreshPaletteId);
initAttributesUi();
initMetatileSelector();
@@ -131,11 +128,11 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
this->metatileReloadQueue.clear();
Tileset *primaryTileset = project->getTileset(primaryTilesetLabel);
Tileset *secondaryTileset = project->getTileset(secondaryTilesetLabel);
- if (this->primaryTileset) delete this->primaryTileset;
- if (this->secondaryTileset) delete this->secondaryTileset;
+ delete this->primaryTileset;
+ delete this->secondaryTileset;
this->primaryTileset = new Tileset(*primaryTileset);
this->secondaryTileset = new Tileset(*secondaryTileset);
- if (paletteEditor) paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
+ if (this->paletteEditor) this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
this->initMetatileHistory();
}
@@ -265,10 +262,8 @@ void TilesetEditor::initTileSelector()
connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) {
onHoveredTileChanged(tileId);
});
- connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared,
- this, &TilesetEditor::onHoveredTileCleared);
- connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged,
- this, &TilesetEditor::onSelectedTilesChanged);
+ connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared);
+ connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, this, &TilesetEditor::drawSelectedTiles);
this->tileSelector->showDivider = this->ui->actionShow_Tileset_Divider->isChecked();
@@ -475,10 +470,6 @@ void TilesetEditor::onHoveredTileCleared() {
this->ui->statusbar->clearMessage();
}
-void TilesetEditor::onSelectedTilesChanged() {
- this->drawSelectedTiles();
-}
-
void TilesetEditor::onMetatileLayerTileChanged(int x, int y) {
static const QList tileCoords = QList{
QPoint(0, 0),
@@ -543,39 +534,32 @@ void TilesetEditor::onMetatileLayerSelectionChanged(QPoint selectionOrigin, int
this->tileSelector->setExternalSelection(width, height, tiles, tileIdxs);
if (width == 1 && height == 1) {
- ui->spinBox_paletteSelector->setValue(tiles[0].palette);
+ setPaletteId(tiles[0].palette);
this->tileSelector->highlight(static_cast(tiles[0].tileId));
this->redrawTileSelector();
}
this->metatileLayersItem->clearLastModifiedCoords();
}
-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);
+void TilesetEditor::setPaletteId(int paletteId) {
+ ui->spinBox_paletteSelector->setValue(paletteId);
+}
+
+int TilesetEditor::paletteId() const {
+ return ui->spinBox_paletteSelector->value();
+}
+
+void TilesetEditor::refreshPaletteId() {
+ this->tileSelector->setPaletteId(paletteId());
this->drawSelectedTiles();
if (this->paletteEditor) {
- this->paletteEditor->setPaletteId(paletteId);
+ this->paletteEditor->setPaletteId(paletteId());
}
this->metatileLayersItem->clearLastModifiedCoords();
}
-void TilesetEditor::setXFlip(bool enabled)
-{
- this->tileXFlip = enabled;
- this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip);
- this->drawSelectedTiles();
- this->metatileLayersItem->clearLastModifiedCoords();
-}
-
-void TilesetEditor::setYFlip(bool enabled)
-{
- this->tileYFlip = enabled;
- this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip);
+void TilesetEditor::refreshTileFlips() {
+ this->tileSelector->setTileFlips(ui->checkBox_xFlip->isChecked(), ui->checkBox_yFlip->isChecked());
this->drawSelectedTiles();
this->metatileLayersItem->clearLastModifiedCoords();
}
@@ -872,21 +856,12 @@ void TilesetEditor::on_actionChange_Palettes_triggered()
{
if (!this->paletteEditor) {
this->paletteEditor = new PaletteEditor(this->project, this->primaryTileset,
- this->secondaryTileset, this->paletteId, this);
- connect(this->paletteEditor, &PaletteEditor::changedPaletteColor,
- this, &TilesetEditor::onPaletteEditorChangedPaletteColor);
- connect(this->paletteEditor, &PaletteEditor::changedPalette,
- this, &TilesetEditor::onPaletteEditorChangedPalette);
- }
-
- if (!this->paletteEditor->isVisible()) {
- this->paletteEditor->show();
- } else if (this->paletteEditor->isMinimized()) {
- this->paletteEditor->showNormal();
- } else {
- this->paletteEditor->raise();
- this->paletteEditor->activateWindow();
+ this->secondaryTileset, this->paletteId(), this);
+ connect(this->paletteEditor, &PaletteEditor::changedPaletteColor, this, &TilesetEditor::onPaletteEditorChangedPaletteColor);
+ connect(this->paletteEditor, &PaletteEditor::changedPalette, this, &TilesetEditor::setPaletteId);
+ connect(this->paletteEditor, &PaletteEditor::metatileSelected, this, &TilesetEditor::selectMetatile);
}
+ Util::show(this->paletteEditor);
}
void TilesetEditor::onPaletteEditorChangedPaletteColor() {
@@ -894,10 +869,6 @@ void TilesetEditor::onPaletteEditorChangedPaletteColor() {
this->hasUnsavedChanges = true;
}
-void TilesetEditor::onPaletteEditorChangedPalette(int paletteId) {
- this->on_spinBox_paletteSelector_valueChanged(paletteId);
-}
-
bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, QString newLabel)
{
Metatile * dest = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset);
@@ -996,7 +967,7 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel)
void TilesetEditor::exportTilesImage(Tileset *tileset) {
bool primary = !tileset->is_secondary;
- QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId);
+ QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId());
QString filepath = FileDialog::getSaveFileName(this, QString("Export %1 Tiles Image").arg(primary ? "Primary" : "Secondary"), defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
QImage image = primary ? this->tileSelector->buildPrimaryTilesIndexedImage() : this->tileSelector->buildSecondaryTilesIndexedImage();
diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp
index 9957f33a..c8053bdb 100644
--- a/src/ui/wildmonsearch.cpp
+++ b/src/ui/wildmonsearch.cpp
@@ -122,7 +122,10 @@ void WildMonSearch::updateResults(const QString &species) {
if (ui->comboBox_Search->findText(species) < 0)
return; // Not a species name, no need to search wild encounter data.
- const QList results = this->resultsCache.value(species, search(species));
+ auto it = this->resultsCache.constFind(species);
+ bool inCache = (it != this->resultsCache.constEnd());
+ const QList results = inCache ? it.value() : search(species);
+
if (results.isEmpty()) {
static const RowData noResults = {
.mapName = "",
@@ -140,7 +143,7 @@ void WildMonSearch::updateResults(const QString &species) {
ui->table_Results->setSortingEnabled(true);
- this->resultsCache.insert(species, results);
+ if (!inCache) this->resultsCache.insert(species, results);
}
// Double-clicking row data opens the corresponding map/table on the Wild Pokémon tab.