Add setting to show unused palette colors

This commit is contained in:
GriffinR 2025-07-24 21:26:12 -04:00
parent d3d30ae0f2
commit 2f2f71948a
15 changed files with 243 additions and 54 deletions

View File

@ -38,6 +38,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_AllColorsUsed">
<property name="text">
<string>(All colors used)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@ -133,8 +140,15 @@
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<addaction name="actionShow_Unused_Colors"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionUndo">
@ -164,6 +178,14 @@
<string>Import Palette</string>
</property>
</action>
<action name="actionShow_Unused_Colors">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Unused Colors</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@ -116,6 +116,7 @@ public:
bool showTilesetEditorLayerGrid;
bool showTilesetEditorDivider;
bool showTilesetEditorRawAttributes;
bool showPaletteEditorUnusedColors;
bool monitorFiles;
bool tilesetCheckerboardFill;
bool newMapHeaderSectionExpanded;

View File

@ -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;

View File

@ -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); }
};

View File

@ -39,6 +39,7 @@ public:
static QString stripPrefix(const QString &fullName);
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
static Tileset* getTileTileset(int, Tileset*, Tileset*);
static const Tileset* getTileTileset(int, const Tileset*, const Tileset*);
static Metatile* getMetatile(int, Tileset*, Tileset*);
static Tileset* getMetatileLabelTileset(int, Tileset*, Tileset*);
static QString getMetatileLabel(int, Tileset *, Tileset *);
@ -92,6 +93,11 @@ public:
QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); }
QSet<int> getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet<int> &searchColors = {}) const;
static constexpr int maxPalettes() { return 16; }
static constexpr int numColorsPerPalette() { return 16; }
private:
QList<Metatile*> m_metatiles;

View File

@ -109,6 +109,7 @@ public:
QStringList primaryTilesetLabels;
QStringList secondaryTilesetLabels;
QStringList tilesetLabelsOrdered;
QSet<QString> getPairedTilesetLabels(Tileset *tileset) const;
bool readMapGroups();
void addNewMapGroup(const QString &groupName);

View File

@ -35,7 +35,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
bool useTruePalettes = false);
QImage getTileImage(uint16_t, Tileset*, Tileset*);
QImage getTileImage(uint16_t, const Tileset*, const Tileset*);
QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false);
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList<QRgb> &palette);
QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset);

View File

@ -24,9 +24,14 @@ 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;
private:
Ui::PaletteEditor *ui;
Project *project = nullptr;
@ -36,13 +41,18 @@ private:
Tileset *secondaryTileset;
QList<History<PaletteHistoryItem*>> palettesHistory;
QMap<int,QSet<int>> unusedColorCache;
Tileset* getTileset(int paletteId);
Tileset* getTileset(int paletteId) const;
void refreshColorInputs();
void commitEditHistory();
void commitEditHistory(int paletteId);
void restoreWindowState();
void onWindowActivated();
void invalidateCache();
void closeEvent(QCloseEvent*);
void setColorInputTitles(bool show);
QSet<int> getUnusedColorIds() const;
void setRgb(int index, QRgb rgb);
void setPalette(int paletteId, const QList<QRgb> &palette);
@ -50,7 +60,7 @@ private:
void setBitDepth(int bits);
int bitDepth = 24;
static const int numColors = 16;
static const int numColors = Tileset::numColorsPerPalette();
signals:
void closed();

View File

@ -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<QString, QString> 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");

View File

@ -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;

View File

@ -111,6 +111,11 @@ int Tileset::maxTiles() const {
}
Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
return const_cast<Tileset*>(getTileTileset(tileId, static_cast<const Tileset*>(primaryTileset), static_cast<const Tileset*>(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()) {
@ -120,6 +125,7 @@ Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *s
}
}
// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset.
Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
if (metatileId < Project::getNumMetatilesPrimary()) {
return primaryTileset;
@ -334,7 +340,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 +552,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<QRgb> 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 +622,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 +637,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 +665,45 @@ 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<int> Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet<int> &searchColors) const {
QSet<int> 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<uint16_t> 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;
}

View File

@ -1517,7 +1517,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 +1579,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<QRgb> 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);
@ -2347,7 +2347,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 +3518,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<QString> Project::getPairedTilesetLabels(Tileset *tileset) const {
QSet<QString> 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;
}

View File

@ -346,10 +346,7 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList<QLi
return;
if (paletteIndex >= 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;

View File

@ -141,8 +141,8 @@ 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();
}
@ -155,7 +155,7 @@ 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()));
}
}

View File

@ -5,6 +5,7 @@
#include "log.h"
#include "filedialog.h"
#include "message.h"
#include "eventfilters.h"
PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) :
@ -21,7 +22,7 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
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);
@ -45,17 +46,37 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
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); });
this->ui->actionShow_Unused_Colors->setChecked(porymapConfig.showPaletteEditorUnusedColors);
connect(this->ui->actionShow_Unused_Colors, &QAction::toggled, this, &PaletteEditor::setColorInputTitles);
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::onWindowActivated);
this->installEventFilter(filter);
this->setPaletteId(paletteId);
this->commitEditHistory();
this->restoreWindowState();
}
PaletteEditor::~PaletteEditor()
{
PaletteEditor::~PaletteEditor() {
delete ui;
}
Tileset* PaletteEditor::getTileset(int paletteId) {
void PaletteEditor::onWindowActivated() {
// 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.
invalidateCache();
}
int PaletteEditor::currentPaletteId() const {
return ui->spinBox_PaletteId->value();
}
bool PaletteEditor::showingUnusedColors() const {
return ui->actionShow_Unused_Colors->isChecked();
}
Tileset* PaletteEditor::getTileset(int paletteId) const {
return (paletteId < Project::getNumPalettesPrimary())
? this->primaryTileset
: this->secondaryTileset;
@ -70,8 +91,7 @@ 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;
@ -82,21 +102,22 @@ void PaletteEditor::setRgb(int colorIndex, QRgb rgb) {
void PaletteEditor::setPalette(int paletteId, const QList<QRgb> &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) {
@ -108,6 +129,7 @@ void PaletteEditor::setPaletteId(int paletteId) {
void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
this->invalidateCache();
this->refreshColorInputs();
}
@ -120,12 +142,12 @@ void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) {
}
void PaletteEditor::commitEditHistory() {
commitEditHistory(ui->spinBox_PaletteId->value());
commitEditHistory(currentPaletteId());
}
void PaletteEditor::commitEditHistory(int paletteId) {
QList<QRgb> 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);
@ -139,24 +161,21 @@ void PaletteEditor::restoreWindowState() {
this->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,11 +190,70 @@ 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::invalidateCache() {
this->unusedColorCache.clear();
if (showingUnusedColors()) {
setColorInputTitles(true);
}
}
QSet<int> PaletteEditor::getUnusedColorIds() const {
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<int> 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<QString> 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<int> unusedColorIds = showUnused ? getUnusedColorIds() : QSet<int>();
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(),